Merge inbound to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 04 Feb 2014 14:23:11 -0500
changeset 172136 2e58809404699b53b88bd3b0382ca0e009d8c3b7
parent 172080 f550b112a19b9eb3ceb0eb728cef11433cdf9bf7 (current diff)
parent 172135 8b8ecfe02a90c76097b24c0da16dca4567e125d9 (diff)
child 172157 55614a949bde77159110a40ec0c73a5b75df37c5
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-esr52@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone30.0a1
Merge inbound to m-c.
browser/components/places/tests/unit/test_browserGlue_shutdown.js
security/patches/bug-950129.patch
--- a/accessible/src/generic/HyperTextAccessible.cpp
+++ b/accessible/src/generic/HyperTextAccessible.cpp
@@ -226,43 +226,38 @@ HyperTextAccessible::TextSubstring(int32
   int32_t endChildOffset =  GetChildOffset(endChildIdx);
   if (endChildOffset == -1)
     return;
 
   Accessible* endChild = GetChildAt(endChildIdx);
   endChild->AppendTextTo(aText, 0, endOffset - endChildOffset);
 }
 
-Accessible*
-HyperTextAccessible::DOMPointToHypertextOffset(nsINode* aNode,
-                                               int32_t aNodeOffset,
-                                               int32_t* aHyperTextOffset,
-                                               bool aIsEndOffset) const
+int32_t
+HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset,
+                                      bool aIsEndOffset) const
 {
-  if (!aHyperTextOffset)
-    return nullptr;
-  *aHyperTextOffset = 0;
-
   if (!aNode)
-    return nullptr;
+    return 0;
 
   uint32_t addTextOffset = 0;
   nsINode* findNode = nullptr;
 
   if (aNodeOffset == -1) {
     findNode = aNode;
 
   } else if (aNode->IsNodeOfType(nsINode::eTEXT)) {
     // For text nodes, aNodeOffset comes in as a character offset
     // Text offset will be added at the end, if we find the offset in this hypertext
     // We want the "skipped" offset into the text (rendered text without the extra whitespace)
-    nsIFrame *frame = aNode->AsContent()->GetPrimaryFrame();
-    NS_ENSURE_TRUE(frame, nullptr);
+    nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
+    NS_ENSURE_TRUE(frame, 0);
+
     nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &addTextOffset);
-    NS_ENSURE_SUCCESS(rv, nullptr);
+    NS_ENSURE_SUCCESS(rv, 0);
     // Get the child node and 
     findNode = aNode;
 
   } else {
     // findNode could be null if aNodeOffset == # of child nodes, which means
     // one of two things:
     // 1) there are no children, and the passed-in node is not mContent -- use
     //    parentContent for the node to find
@@ -271,18 +266,17 @@ HyperTextAccessible::DOMPointToHypertext
     // 3) there are children and we're at the end of the children
 
     findNode = aNode->GetChildAt(aNodeOffset);
     if (!findNode) {
       if (aNodeOffset == 0) {
         if (aNode == GetNode()) {
           // Case #1: this accessible has no children and thus has empty text,
           // we can only be at hypertext offset 0.
-          *aHyperTextOffset = 0;
-          return nullptr;
+          return 0;
         }
 
         // Case #2: there are no children, we're at this node.
         findNode = aNode;
       } else if (aNodeOffset == aNode->GetChildCount()) {
         // Case #3: we're after the last child, get next node to this one.
         for (nsINode* tmpNode = aNode;
              !findNode && tmpNode && tmpNode != mContent;
@@ -290,85 +284,59 @@ HyperTextAccessible::DOMPointToHypertext
           findNode = tmpNode->GetNextSibling();
         }
       }
     }
   }
 
   // Get accessible for this findNode, or if that node isn't accessible, use the
   // accessible for the next DOM node which has one (based on forward depth first search)
-  Accessible* descendantAcc = nullptr;
+  Accessible* descendant = nullptr;
   if (findNode) {
     nsCOMPtr<nsIContent> findContent(do_QueryInterface(findNode));
     if (findContent && findContent->IsHTML() &&
         findContent->NodeInfo()->Equals(nsGkAtoms::br) &&
         findContent->AttrValueIs(kNameSpaceID_None,
                                  nsGkAtoms::mozeditorbogusnode,
                                  nsGkAtoms::_true,
                                  eIgnoreCase)) {
       // This <br> is the hacky "bogus node" used when there is no text in a control
-      *aHyperTextOffset = 0;
-      return nullptr;
+      return 0;
     }
-    descendantAcc = GetFirstAvailableAccessible(findNode);
+    descendant = GetFirstAvailableAccessible(findNode);
   }
 
   // From the descendant, go up and get the immediate child of this hypertext
-  Accessible* childAccAtOffset = nullptr;
-  while (descendantAcc) {
-    Accessible* parentAcc = descendantAcc->Parent();
-    if (parentAcc == this) {
-      childAccAtOffset = descendantAcc;
+  Accessible* childAtOffset = nullptr;
+  while (descendant) {
+    Accessible* parent = descendant->Parent();
+    if (parent == this) {
+      childAtOffset = descendant;
       break;
     }
 
     // This offset no longer applies because the passed-in text object is not
     // a child of the hypertext. This happens when there are nested hypertexts,
     // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
     // to make it relative the hypertext.
     // If the end offset is not supposed to be inclusive and the original point
     // is not at 0 offset then the returned offset should be after an embedded
     // character the original point belongs to.
     if (aIsEndOffset)
-      addTextOffset = (addTextOffset > 0 || descendantAcc->IndexInParent() > 0) ? 1 : 0;
+      addTextOffset = (addTextOffset > 0 || descendant->IndexInParent() > 0) ? 1 : 0;
     else
       addTextOffset = 0;
 
-    descendantAcc = parentAcc;
+    descendant = parent;
   }
 
-  // Loop through, adding offsets until we reach childAccessible
-  // If childAccessible is null we will end up adding up the entire length of
-  // the hypertext, which is good -- it just means our offset node
-  // came after the last accessible child's node
-  uint32_t childCount = ChildCount();
-
-  uint32_t childIdx = 0;
-  Accessible* childAcc = nullptr;
-  for (; childIdx < childCount; childIdx++) {
-    childAcc = mChildren[childIdx];
-    if (childAcc == childAccAtOffset)
-      break;
-
-    *aHyperTextOffset += nsAccUtils::TextLength(childAcc);
-  }
-
-  if (childIdx < childCount) {
-    *aHyperTextOffset += addTextOffset;
-    NS_ASSERTION(childAcc == childAccAtOffset,
-                 "These should be equal whenever we exit loop and childAcc != nullptr");
-
-    if (childIdx < childCount - 1 ||
-        addTextOffset < nsAccUtils::TextLength(childAccAtOffset)) {
-      // If not at end of last text node, we will return the accessible we were in
-      return childAccAtOffset;
-    }
-  }
-
-  return nullptr;
+  // If the given DOM point cannot be mapped into offset relative this hypertext
+  // offset then return length as fallback value.
+  return childAtOffset ?
+    GetChildOffset(childAtOffset) + addTextOffset : CharacterCount();
 }
 
 bool
 HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset,
                                        nsRange* aRange)
 {
   DOMPoint startPoint = OffsetToDOMPoint(aStartOffset);
   if (!startPoint.node)
@@ -485,27 +453,24 @@ HyperTextAccessible::FindOffset(int32_t 
   // PeekOffset fails on last/first lines of the text in certain cases.
   if (NS_FAILED(rv) && aAmount == eSelectLine) {
     pos.mAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine;
     frameAtOffset->PeekOffset(&pos);
   }
   if (!pos.mResultContent)
     return -1;
 
-  // Turn the resulting node and offset into a hyperTextOffset
-  // If finalAccessible is nullptr, then DOMPointToHypertextOffset() searched
-  // through the hypertext children without finding the node/offset position.
-  int32_t hyperTextOffset = 0;
-  Accessible* finalAccessible =
-    DOMPointToHypertextOffset(pos.mResultContent, pos.mContentOffset,
-                              &hyperTextOffset, aDirection == eDirNext);
+  // Turn the resulting DOM point into an offset.
+  int32_t hyperTextOffset = DOMPointToOffset(pos.mResultContent,
+                                             pos.mContentOffset,
+                                             aDirection == eDirNext);
 
   // If we reached the end during search, this means we didn't find the DOM point
   // and we're actually at the start of the paragraph
-  if (!finalAccessible && aDirection == eDirPrevious)
+  if (hyperTextOffset == CharacterCount() && aDirection == eDirPrevious)
     return 0;
 
   return hyperTextOffset;
 }
 
 int32_t
 HyperTextAccessible::FindLineBoundary(int32_t aOffset,
                                       EWhichLineBoundary aWhichLineBoundary)
@@ -1168,19 +1133,17 @@ HyperTextAccessible::CaretOffset() const
       nsCoreUtils::GetDOMNodeFromDOMPoint(focusNode, focusOffset);
 
     nsINode* thisNode = GetNode();
     if (resultNode != thisNode &&
         !nsCoreUtils::IsAncestorOf(thisNode, resultNode))
       return -1;
   }
 
-  int32_t caretOffset = -1;
-  DOMPointToHypertextOffset(focusNode, focusOffset, &caretOffset);
-  return caretOffset;
+  return DOMPointToOffset(focusNode, focusOffset);
 }
 
 int32_t
 HyperTextAccessible::CaretLineNumber()
 {
   // Provide the line number for the caret, relative to the
   // currently focused node. Use a 1-based index
   nsRefPtr<nsFrameSelection> frameSelection = FrameSelection();
@@ -1379,23 +1342,18 @@ HyperTextAccessible::SelectionBoundsAt(i
     nsINode* tempNode = startNode;
     startNode = endNode;
     endNode = tempNode;
     int32_t tempOffset = startOffset;
     startOffset = endOffset;
     endOffset = tempOffset;
   }
 
-  Accessible* startAccessible =
-    DOMPointToHypertextOffset(startNode, startOffset, aStartOffset);
-  if (!startAccessible) {
-    *aStartOffset = 0; // Could not find start point within this hypertext, so starts before
-  }
-
-  DOMPointToHypertextOffset(endNode, endOffset, aEndOffset, true);
+  *aStartOffset = DOMPointToOffset(startNode, startOffset);
+  *aEndOffset = DOMPointToOffset(endNode, endOffset, true);
   return true;
 }
 
 bool
 HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum,
                                           int32_t aStartOffset,
                                           int32_t aEndOffset)
 {
@@ -1776,35 +1734,30 @@ HyperTextAccessible::GetDOMPointByFrameO
   return NS_OK;
 }
 
 // HyperTextAccessible
 nsresult
 HyperTextAccessible::RangeBoundToHypertextOffset(nsRange* aRange,
                                                  bool aIsStartBound,
                                                  bool aIsStartHTOffset,
-                                                 int32_t* aHTOffset)
+                                                 int32_t* aOffset)
 {
   nsINode* node = nullptr;
   int32_t nodeOffset = 0;
 
   if (aIsStartBound) {
     node = aRange->GetStartParent();
     nodeOffset = aRange->StartOffset();
   } else {
     node = aRange->GetEndParent();
     nodeOffset = aRange->EndOffset();
   }
 
-  Accessible* startAcc =
-    DOMPointToHypertextOffset(node, nodeOffset, aHTOffset);
-
-  if (aIsStartHTOffset && !startAcc)
-    *aHTOffset = 0;
-
+  *aOffset = DOMPointToOffset(node, nodeOffset);
   return NS_OK;
 }
 
 // HyperTextAccessible
 nsresult
 HyperTextAccessible::GetSpellTextAttribute(nsINode* aNode,
                                            int32_t aNodeOffset,
                                            int32_t* aHTStartOffset,
--- a/accessible/src/generic/HyperTextAccessible.h
+++ b/accessible/src/generic/HyperTextAccessible.h
@@ -94,44 +94,36 @@ public:
     Accessible* child = GetChildAtOffset(aOffset);
     return child ? LinkIndexOf(child) : -1;
   }
 
   //////////////////////////////////////////////////////////////////////////////
   // HyperTextAccessible: DOM point to text offset conversions.
 
   /**
-    * Turn a DOM Node and offset into a character offset into this hypertext.
-    * Will look for closest match when the DOM node does not have an accessible
-    * object associated with it. Will return an offset for the end of
-    * the string if the node is not found.
-    *
-    * @param aNode - the node to look for
-    * @param aNodeOffset - the offset to look for
-    *                      if -1 just look directly for the node
-    *                      if >=0 and aNode is text, this represents a char offset
-    *                      if >=0 and aNode is not text, this represents a child node offset
-    * @param aResultOffset - the character offset into the current
-    *                        HyperTextAccessible
-    * @param aIsEndOffset - if true, then then this offset is not inclusive. The character
-    *                       indicated by the offset returned is at [offset - 1]. This means
-    *                       if the passed-in offset is really in a descendant, then the offset returned
-    *                       will come just after the relevant embedded object characer.
-    *                       If false, then the offset is inclusive. The character indicated
-    *                       by the offset returned is at [offset]. If the passed-in offset in inside a
-    *                       descendant, then the returned offset will be on the relevant embedded object char.
-    *
-    * @return               the accessible child which contained the offset, if
-    *                       it is within the current HyperTextAccessible,
-    *                       otherwise nullptr
-    */
-  Accessible* DOMPointToHypertextOffset(nsINode *aNode,
-                                        int32_t aNodeOffset,
-                                        int32_t* aHypertextOffset,
-                                        bool aIsEndOffset = false) const;
+   * Turn a DOM point (node and offset) into a character offset of this
+   * hypertext. Will look for closest match when the DOM node does not have
+   * an accessible object associated with it. Will return an offset for the end
+   * of the string if the node is not found.
+   *
+   * @param aNode         [in] the node to look for
+   * @param aNodeOffset   [in] the offset to look for
+   *                       if -1 just look directly for the node
+   *                       if >=0 and aNode is text, this represents a char offset
+   *                       if >=0 and aNode is not text, this represents a child node offset
+   * @param aIsEndOffset  [in] if true, then then this offset is not inclusive. The character
+   *                       indicated by the offset returned is at [offset - 1]. This means
+   *                       if the passed-in offset is really in a descendant, then the offset returned
+   *                       will come just after the relevant embedded object characer.
+   *                       If false, then the offset is inclusive. The character indicated
+   *                       by the offset returned is at [offset]. If the passed-in offset in inside a
+   *                       descendant, then the returned offset will be on the relevant embedded object char.
+   */
+  int32_t DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset,
+                           bool aIsEndOffset = false) const;
 
   /**
    * Convert start and end hypertext offsets into DOM range.
    *
    * @param  aStartOffset  [in] the given start hypertext offset
    * @param  aEndOffset    [in] the given end hypertext offset
    * @param  aRange        [in, out] the range whose bounds to set
    * @return true   if conversion was successful
@@ -475,17 +467,17 @@ protected:
                                     Accessible* aAccessible,
                                     mozilla::a11y::DOMPoint* aPoint);
 
 
   /**
    * Return hyper text offset for the specified bound of the given DOM range.
    * If the bound is outside of the hyper text then offset value is either
    * 0 or number of characters of hyper text, it depends on type of requested
-   * offset. The method is a wrapper for DOMPointToHypertextOffset.
+   * offset. The method is a wrapper for DOMPointToOffset.
    *
    * @param aRange          [in] the given range
    * @param aIsStartBound   [in] specifies whether the required range bound is
    *                        start bound
    * @param aIsStartOffset  [in] the offset type, used when the range bound is
    *                        outside of hyper text
    * @param aHTOffset       [out] the result offset
    */
--- a/accessible/src/jsat/TraversalRules.jsm
+++ b/accessible/src/jsat/TraversalRules.jsm
@@ -127,18 +127,22 @@ var gSimpleMatchFunc = function gSimpleM
 
       return Filters.MATCH;
     }
   case Roles.GRAPHIC:
     return TraversalRules._shouldSkipImage(aAccessible);
   case Roles.LINK:
   case Roles.HEADER:
   case Roles.HEADING:
-    return hasZeroOrSingleChildDescendants() ?
-      (Filters.MATCH | Filters.IGNORE_SUBTREE) : (Filters.IGNORE);
+    if ((aAccessible.childCount > 0 || aAccessible.name) &&
+        hasZeroOrSingleChildDescendants()) {
+      return Filters.MATCH | Filters.IGNORE_SUBTREE;
+    } else {
+      return Filters.IGNORE;
+    }
   default:
     // Ignore the subtree, if there is one. So that we don't land on
     // the same content that was already presented by its parent.
     return Filters.MATCH |
       Filters.IGNORE_SUBTREE;
   }
 };
 
@@ -212,17 +216,20 @@ this.TraversalRules = {
 
   Graphic: new BaseTraversalRule(
     [Roles.GRAPHIC],
     function Graphic_match(aAccessible) {
       return TraversalRules._shouldSkipImage(aAccessible);
     }),
 
   Heading: new BaseTraversalRule(
-    [Roles.HEADING]),
+    [Roles.HEADING],
+    function Heading_match(aAccessible) {
+      return aAccessible.childCount > 0 ? Filters.MATCH : Filters.IGNORE;
+    }),
 
   ListItem: new BaseTraversalRule(
     [Roles.LISTITEM,
      Roles.TERM]),
 
   Link: new BaseTraversalRule(
     [Roles.LINK],
     function Link_match(aAccessible)
--- a/accessible/tests/mochitest/jsat/doc_traversal.html
+++ b/accessible/tests/mochitest/jsat/doc_traversal.html
@@ -77,16 +77,17 @@
   <div id="image-3" tabindex="0" role="img">Not actually an image</div>
   <h4 id="heading-6" aria-hidden="true">Hidden header</h4>
   <a id="link-1" href="http://www.mozilla.org">Link</a>
   <a id="anchor-1">Words</a>
   <a id="link-2" href="http://www.mozilla.org">Link the second</a>
   <a id="anchor-2">Sentences</a>
   <a id="link-3" href="http://www.example.com">Link the third</a>
   <hr id="separator-1">
+  <h6 id="heading-6"></h6>
   <table id="table-1">
   <tr>
     <td>3</td>
     <td>1</td>
   </tr>
   <tr>
     <td>4</td>
     <td>1</td>
--- a/accessible/tests/mochitest/jsat/test_traversal.html
+++ b/accessible/tests/mochitest/jsat/test_traversal.html
@@ -90,16 +90,40 @@
                              ['anchor-1', 'anchor-2']);
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.Separator, null,
                              ['separator-1', 'separator-2']);
 
       queueTraversalSequence(gQueue, docAcc, TraversalRules.Table, null,
                              ['table-1', 'table-2']);
 
+      queueTraversalSequence(gQueue, docAcc, TraversalRules.Simple, null,
+                             ['heading-1', 'Name:', 'input-1-1', 'label-1-2',
+                              'button-1-1', 'Radios are old: ', 'radio-1-1',
+                              'Radios are new: ', 'radio-1-2', 'Password:',
+                              'input-1-3', 'Unlucky number:', 'input-1-4',
+                              'button-1-2', 'Check me: ', 'checkbox-1-1',
+                              'select-1-1', 'Value 1', 'Value 2', 'Value 3',
+                              'Check me too: ', 'checkbox-1-2', 'But not me: ',
+                              'Or me! ', 'Value 1', 'Value 2', 'Value 3',
+                              'Electronic mailing address:', 'input-1-5',
+                              'button-1-3', 'heading-2', 'heading-3',
+                              'button-2-1', 'button-2-2', 'button-2-3',
+                              'button-2-4', 'Programming Language',
+                              'A esoteric weapon wielded by only the most ' +
+                              'formidable warriors, for its unrelenting strict' +
+                              ' power is unfathomable.',
+                              'Lists of Programming Languages', 'Lisp ',
+                              'Scheme', 'Racket', 'Clojure', 'JavaScript', 'heading-5',
+                              'image-2', 'image-3', 'Not actually an image',
+                              'link-1', 'anchor-1', 'link-2', 'anchor-2', 'link-3',
+                              '3', '1', '4', '1', 'Just an innocuous separator',
+                              'Dirty Words', 'Meaning', 'Mud', 'Wet Dirt',
+                              'Dirt', 'Messy Stuff']);
+
       gQueue.invoke();
     }
 
     SimpleTest.waitForExplicitFinish();
     addLoadEvent(function () {
       /* We open a new browser because we need to test with a top-level content
          document. */
       openBrowserWindow(
--- a/accessible/tests/mochitest/pivot.js
+++ b/accessible/tests/mochitest/pivot.js
@@ -260,17 +260,19 @@ function setVCTextInvoker(aDocAcc, aPivo
     var moved = aDocAcc.virtualCursor[aPivotMoveMethod](aBoundary);
     SimpleTest.is(!!moved, !!expectMove,
                   "moved pivot by text with " + aPivotMoveMethod +
                   " to " + aIdOrNameOrAcc);
   };
 
   this.getID = function setVCPosInvoker_getID()
   {
-    return "Do " + (expectMove ? "" : "no-op ") + aPivotMoveMethod;
+    return "Do " + (expectMove ? "" : "no-op ") + aPivotMoveMethod + " in " +
+      prettyName(aIdOrNameOrAcc) + ", " + boundaryToString(aBoundary) +
+      ", [" + aTextOffsets + "]";
   };
 
   if (expectMove) {
     this.eventSeq = [
       new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod)
     ];
   } else {
     this.eventSeq = [];
@@ -374,17 +376,19 @@ function setModalRootInvoker(aDocAcc, aM
  */
 function queueTraversalSequence(aQueue, aDocAcc, aRule, aModalRoot, aSequence)
 {
   aDocAcc.virtualCursor.position = null;
 
   // Add modal root (if any)
   aQueue.push(new setModalRootInvoker(aDocAcc, aModalRoot, 0));
 
-  for (var i = 0; i < aSequence.length; i++) {
+  aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0]));
+
+  for (var i = 1; i < aSequence.length; i++) {
     var invoker =
       new setVCPosInvoker(aDocAcc, "moveNext", aRule, aSequence[i]);
     aQueue.push(invoker);
   }
 
   // No further more matches for given rule, expect no virtual cursor changes.
   aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false));
 
--- a/accessible/tests/mochitest/pivot/test_virtualcursor_text.html
+++ b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html
@@ -8,16 +8,17 @@
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
   </script>
   <script type="application/javascript"
           src="chrome://mochikit/content/chrome-harness.js">
   </script>
 
   <script type="application/javascript" src="../common.js"></script>
+  <script type="application/javascript" src="../text.js"></script>
   <script type="application/javascript" src="../browser.js"></script>
   <script type="application/javascript" src="../events.js"></script>
   <script type="application/javascript" src="../role.js"></script>
   <script type="application/javascript" src="../states.js"></script>
   <script type="application/javascript" src="../pivot.js"></script>
   <script type="application/javascript" src="../layout.js"></script>
 
   <script type="application/javascript">
--- a/configure.in
+++ b/configure.in
@@ -3670,17 +3670,17 @@ dnl = If NSS was not detected in the sys
 dnl = use the one in the source tree (mozilla/security/nss)
 dnl ========================================================
 
 MOZ_ARG_WITH_BOOL(system-nss,
 [  --with-system-nss       Use system installed NSS],
     _USE_SYSTEM_NSS=1 )
 
 if test -n "$_USE_SYSTEM_NSS"; then
-    AM_PATH_NSS(3.15.4, [MOZ_NATIVE_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])])
+    AM_PATH_NSS(3.15.5, [MOZ_NATIVE_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])])
 fi
 
 if test -n "$MOZ_NATIVE_NSS"; then
    NSS_LIBS="$NSS_LIBS -lcrmf"
 else
    NSS_CFLAGS='-I$(LIBXUL_DIST)/include/nss'
 
    if test -z "$GNU_CC" -a "$OS_ARCH" = "WINNT" -o "$OS_ARCH" = "OS2"; then
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -6910,16 +6910,24 @@ nsDocument::GetViewportInfo(const Screen
       // render correctly in FirefoxOS only. For a smooth transition the above
       // code force installed apps to render as if they have a viewport with
       // content="width=device-width, height=device-height, user-scalable=no".
       // This could be safely remove once it is known that most apps in the
       // marketplace use it and that users does not use an old version of the
       // app that does not use it.
       nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
       if (docShell && docShell->GetIsApp()) {
+        nsString uri;
+        GetDocumentURI(uri);
+        if (!uri.EqualsLiteral("about:blank")) {
+          nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                          NS_LITERAL_CSTRING("DOM"), this,
+                                          nsContentUtils::eDOM_PROPERTIES,
+                                          "ImplicitMetaViewportTagFallback");
+        }
         mViewportType = DisplayWidthHeightNoZoom;
         return nsViewportInfo(aDisplaySize, /* allowZoom */ false);
       }
     }
 
     nsAutoString minScaleStr;
     GetHeaderData(nsGkAtoms::viewport_minimum_scale, minScaleStr);
 
--- a/content/canvas/test/webgl/skipped_tests_linux_mesa.txt
+++ b/content/canvas/test/webgl/skipped_tests_linux_mesa.txt
@@ -1,7 +1,8 @@
 conformance/limits/gl-max-texture-dimensions.html
 conformance/misc/type-conversion-test.html
 conformance/more/conformance/quickCheckAPI-B2.html
 conformance/more/conformance/quickCheckAPI-B3.html
 conformance/more/conformance/quickCheckAPI-B4.html
+conformance/more/conformance/quickCheckAPI-C.html
 conformance/reading/read-pixels-test.html
 conformance/textures/texture-mips.html
--- a/content/html/content/test/browser_bug649778.js
+++ b/content/html/content/test/browser_bug649778.js
@@ -18,17 +18,17 @@ function checkCache(url, inMemory, shoul
     this.shouldExist = shouldExist;
     this.onCacheEntryCheck = function() {
       return Components.interfaces.nsICacheEntryOpenCallback.ENTRY_WANTED;
     };
 
     this.onCacheEntryAvailable = function oCEA(entry, isNew, appCache, status) {
       if (shouldExist) {
         ok(entry, "Entry not found");
-        is(this.inMemory, !entry.persistToDisk, "Entry is " + (inMemory ? "" : " not ") + " in memory as expected");
+        is(this.inMemory, !entry.persistent, "Entry is " + (inMemory ? "" : " not ") + " in memory as expected");
         is(status, Components.results.NS_OK, "Entry not found");
       } else {
         ok(!entry, "Entry found");
         is(status, Components.results.NS_ERROR_CACHE_KEY_NOT_FOUND,
            "Invalid error code");
       }
 
       setTimeout(cb, 0);
--- a/content/html/content/test/mochitest.ini
+++ b/content/html/content/test/mochitest.ini
@@ -425,8 +425,9 @@ support-files =
 [test_style_attributes_reflection.html]
 [test_track.html]
 [test_track_disabled.html]
 [test_ul_attributes_reflection.html]
 [test_undoManager.html]
 [test_video_wakelock.html]
 [test_input_files_not_nsIFile.html]
 [test_ignoreuserfocus.html]
+[test_fragment_form_pointer.html]
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_fragment_form_pointer.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=946585
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 946585</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=946585">Mozilla Bug 946585</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<form><div id="formdiv"></div></form>
+</div>
+<pre id="test">
+</pre>
+<script type="application/javascript">
+/** Test for Bug 946585 **/
+var formDiv = document.getElementById("formdiv");
+formDiv.innerHTML = '<form>';
+is(formDiv.firstChild, null, "InnerHTML should not produce form element because the div has a form pointer.");
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/MediaData.cpp
@@ -0,0 +1,335 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "MediaData.h"
+#include "MediaInfo.h"
+#ifdef MOZ_OMX_DECODER
+#include "GrallocImages.h"
+#endif
+#include "VideoUtils.h"
+#include "ImageContainer.h"
+
+namespace mozilla {
+
+using namespace mozilla::gfx;
+using layers::ImageContainer;
+using layers::PlanarYCbCrImage;
+using layers::PlanarYCbCrData;
+
+void
+AudioData::EnsureAudioBuffer()
+{
+  if (mAudioBuffer)
+    return;
+  mAudioBuffer = SharedBuffer::Create(mFrames*mChannels*sizeof(AudioDataValue));
+
+  AudioDataValue* data = static_cast<AudioDataValue*>(mAudioBuffer->Data());
+  for (uint32_t i = 0; i < mFrames; ++i) {
+    for (uint32_t j = 0; j < mChannels; ++j) {
+      data[j*mFrames + i] = mAudioData[i*mChannels + j];
+    }
+  }
+}
+
+static bool
+ValidatePlane(const VideoData::YCbCrBuffer::Plane& aPlane)
+{
+  return aPlane.mWidth <= PlanarYCbCrImage::MAX_DIMENSION &&
+         aPlane.mHeight <= PlanarYCbCrImage::MAX_DIMENSION &&
+         aPlane.mWidth * aPlane.mHeight < MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
+         aPlane.mStride > 0;
+}
+
+#if 0
+static bool
+IsYV12Format(const VideoData::YCbCrBuffer::Plane& aYPlane,
+             const VideoData::YCbCrBuffer::Plane& aCbPlane,
+             const VideoData::YCbCrBuffer::Plane& aCrPlane)
+{
+  return
+    aYPlane.mWidth % 2 == 0 &&
+    aYPlane.mHeight % 2 == 0 &&
+    aYPlane.mWidth / 2 == aCbPlane.mWidth &&
+    aYPlane.mHeight / 2 == aCbPlane.mHeight &&
+    aCbPlane.mWidth == aCrPlane.mWidth &&
+    aCbPlane.mHeight == aCrPlane.mHeight;
+}
+#endif
+
+VideoData::VideoData(int64_t aOffset, int64_t aTime, int64_t aDuration, int64_t aTimecode)
+  : MediaData(VIDEO_FRAME, aOffset, aTime, aDuration),
+    mTimecode(aTimecode),
+    mDuplicate(true),
+    mKeyframe(false)
+{
+  MOZ_COUNT_CTOR(VideoData);
+  NS_ASSERTION(mDuration >= 0, "Frame must have non-negative duration.");
+}
+
+VideoData::VideoData(int64_t aOffset,
+                     int64_t aTime,
+                     int64_t aDuration,
+                     bool aKeyframe,
+                     int64_t aTimecode,
+                     nsIntSize aDisplay)
+  : MediaData(VIDEO_FRAME, aOffset, aTime, aDuration),
+    mDisplay(aDisplay),
+    mTimecode(aTimecode),
+    mDuplicate(false),
+    mKeyframe(aKeyframe)
+{
+  MOZ_COUNT_CTOR(VideoData);
+  NS_ASSERTION(mDuration >= 0, "Frame must have non-negative duration.");
+}
+
+VideoData::~VideoData()
+{
+  MOZ_COUNT_DTOR(VideoData);
+}
+
+/* static */
+VideoData* VideoData::ShallowCopyUpdateDuration(VideoData* aOther,
+                                                int64_t aDuration)
+{
+  VideoData* v = new VideoData(aOther->mOffset,
+                               aOther->mTime,
+                               aDuration,
+                               aOther->mKeyframe,
+                               aOther->mTimecode,
+                               aOther->mDisplay);
+  v->mImage = aOther->mImage;
+  return v;
+}
+
+VideoData* VideoData::Create(VideoInfo& aInfo,
+                             ImageContainer* aContainer,
+                             Image* aImage,
+                             int64_t aOffset,
+                             int64_t aTime,
+                             int64_t aDuration,
+                             const YCbCrBuffer& aBuffer,
+                             bool aKeyframe,
+                             int64_t aTimecode,
+                             nsIntRect aPicture)
+{
+  if (!aImage && !aContainer) {
+    // Create a dummy VideoData with no image. This gives us something to
+    // send to media streams if necessary.
+    nsAutoPtr<VideoData> v(new VideoData(aOffset,
+                                         aTime,
+                                         aDuration,
+                                         aKeyframe,
+                                         aTimecode,
+                                         aInfo.mDisplay));
+    return v.forget();
+  }
+
+  // The following situation should never happen unless there is a bug
+  // in the decoder
+  if (aBuffer.mPlanes[1].mWidth != aBuffer.mPlanes[2].mWidth ||
+      aBuffer.mPlanes[1].mHeight != aBuffer.mPlanes[2].mHeight) {
+    NS_ERROR("C planes with different sizes");
+    return nullptr;
+  }
+
+  // The following situations could be triggered by invalid input
+  if (aPicture.width <= 0 || aPicture.height <= 0) {
+    NS_WARNING("Empty picture rect");
+    return nullptr;
+  }
+  if (!ValidatePlane(aBuffer.mPlanes[0]) || !ValidatePlane(aBuffer.mPlanes[1]) ||
+      !ValidatePlane(aBuffer.mPlanes[2])) {
+    NS_WARNING("Invalid plane size");
+    return nullptr;
+  }
+
+  // Ensure the picture size specified in the headers can be extracted out of
+  // the frame we've been supplied without indexing out of bounds.
+  CheckedUint32 xLimit = aPicture.x + CheckedUint32(aPicture.width);
+  CheckedUint32 yLimit = aPicture.y + CheckedUint32(aPicture.height);
+  if (!xLimit.isValid() || xLimit.value() > aBuffer.mPlanes[0].mStride ||
+      !yLimit.isValid() || yLimit.value() > aBuffer.mPlanes[0].mHeight)
+  {
+    // The specified picture dimensions can't be contained inside the video
+    // frame, we'll stomp memory if we try to copy it. Fail.
+    NS_WARNING("Overflowing picture rect");
+    return nullptr;
+  }
+
+  nsAutoPtr<VideoData> v(new VideoData(aOffset,
+                                       aTime,
+                                       aDuration,
+                                       aKeyframe,
+                                       aTimecode,
+                                       aInfo.mDisplay));
+  const YCbCrBuffer::Plane &Y = aBuffer.mPlanes[0];
+  const YCbCrBuffer::Plane &Cb = aBuffer.mPlanes[1];
+  const YCbCrBuffer::Plane &Cr = aBuffer.mPlanes[2];
+
+  if (!aImage) {
+    // Currently our decoder only knows how to output to ImageFormat::PLANAR_YCBCR
+    // format.
+#if 0
+    if (IsYV12Format(Y, Cb, Cr)) {
+      v->mImage = aContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR);
+    }
+#endif
+    if (!v->mImage) {
+      v->mImage = aContainer->CreateImage(ImageFormat::PLANAR_YCBCR);
+    }
+  } else {
+    v->mImage = aImage;
+  }
+
+  if (!v->mImage) {
+    return nullptr;
+  }
+  NS_ASSERTION(v->mImage->GetFormat() == ImageFormat::PLANAR_YCBCR ||
+               v->mImage->GetFormat() == ImageFormat::GRALLOC_PLANAR_YCBCR,
+               "Wrong format?");
+  PlanarYCbCrImage* videoImage = static_cast<PlanarYCbCrImage*>(v->mImage.get());
+
+  PlanarYCbCrData data;
+  data.mYChannel = Y.mData + Y.mOffset;
+  data.mYSize = IntSize(Y.mWidth, Y.mHeight);
+  data.mYStride = Y.mStride;
+  data.mYSkip = Y.mSkip;
+  data.mCbChannel = Cb.mData + Cb.mOffset;
+  data.mCrChannel = Cr.mData + Cr.mOffset;
+  data.mCbCrSize = IntSize(Cb.mWidth, Cb.mHeight);
+  data.mCbCrStride = Cb.mStride;
+  data.mCbSkip = Cb.mSkip;
+  data.mCrSkip = Cr.mSkip;
+  data.mPicX = aPicture.x;
+  data.mPicY = aPicture.y;
+  data.mPicSize = aPicture.Size().ToIntSize();
+  data.mStereoMode = aInfo.mStereoMode;
+
+  videoImage->SetDelayedConversion(true);
+  if (!aImage) {
+    videoImage->SetData(data);
+  } else {
+    videoImage->SetDataNoCopy(data);
+  }
+
+  return v.forget();
+}
+
+VideoData* VideoData::Create(VideoInfo& aInfo,
+                             ImageContainer* aContainer,
+                             int64_t aOffset,
+                             int64_t aTime,
+                             int64_t aDuration,
+                             const YCbCrBuffer& aBuffer,
+                             bool aKeyframe,
+                             int64_t aTimecode,
+                             nsIntRect aPicture)
+{
+  return Create(aInfo, aContainer, nullptr, aOffset, aTime, aDuration, aBuffer,
+                aKeyframe, aTimecode, aPicture);
+}
+
+VideoData* VideoData::Create(VideoInfo& aInfo,
+                             Image* aImage,
+                             int64_t aOffset,
+                             int64_t aTime,
+                             int64_t aDuration,
+                             const YCbCrBuffer& aBuffer,
+                             bool aKeyframe,
+                             int64_t aTimecode,
+                             nsIntRect aPicture)
+{
+  return Create(aInfo, nullptr, aImage, aOffset, aTime, aDuration, aBuffer,
+                aKeyframe, aTimecode, aPicture);
+}
+
+VideoData* VideoData::CreateFromImage(VideoInfo& aInfo,
+                                      ImageContainer* aContainer,
+                                      int64_t aOffset,
+                                      int64_t aTime,
+                                      int64_t aDuration,
+                                      const nsRefPtr<Image>& aImage,
+                                      bool aKeyframe,
+                                      int64_t aTimecode,
+                                      nsIntRect aPicture)
+{
+  nsAutoPtr<VideoData> v(new VideoData(aOffset,
+                                       aTime,
+                                       aDuration,
+                                       aKeyframe,
+                                       aTimecode,
+                                       aInfo.mDisplay));
+  v->mImage = aImage;
+  return v.forget();
+}
+
+#ifdef MOZ_OMX_DECODER
+VideoData* VideoData::Create(VideoInfo& aInfo,
+                             ImageContainer* aContainer,
+                             int64_t aOffset,
+                             int64_t aTime,
+                             int64_t aDuration,
+                             mozilla::layers::GraphicBufferLocked* aBuffer,
+                             bool aKeyframe,
+                             int64_t aTimecode,
+                             nsIntRect aPicture)
+{
+  if (!aContainer) {
+    // Create a dummy VideoData with no image. This gives us something to
+    // send to media streams if necessary.
+    nsAutoPtr<VideoData> v(new VideoData(aOffset,
+                                         aTime,
+                                         aDuration,
+                                         aKeyframe,
+                                         aTimecode,
+                                         aInfo.mDisplay));
+    return v.forget();
+  }
+
+  // The following situations could be triggered by invalid input
+  if (aPicture.width <= 0 || aPicture.height <= 0) {
+    NS_WARNING("Empty picture rect");
+    return nullptr;
+  }
+
+  // Ensure the picture size specified in the headers can be extracted out of
+  // the frame we've been supplied without indexing out of bounds.
+  CheckedUint32 xLimit = aPicture.x + CheckedUint32(aPicture.width);
+  CheckedUint32 yLimit = aPicture.y + CheckedUint32(aPicture.height);
+  if (!xLimit.isValid() || !yLimit.isValid())
+  {
+    // The specified picture dimensions can't be contained inside the video
+    // frame, we'll stomp memory if we try to copy it. Fail.
+    NS_WARNING("Overflowing picture rect");
+    return nullptr;
+  }
+
+  nsAutoPtr<VideoData> v(new VideoData(aOffset,
+                                       aTime,
+                                       aDuration,
+                                       aKeyframe,
+                                       aTimecode,
+                                       aInfo.mDisplay));
+
+  v->mImage = aContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR);
+  if (!v->mImage) {
+    return nullptr;
+  }
+  NS_ASSERTION(v->mImage->GetFormat() == ImageFormat::GRALLOC_PLANAR_YCBCR,
+               "Wrong format?");
+  typedef mozilla::layers::GrallocImage GrallocImage;
+  GrallocImage* videoImage = static_cast<GrallocImage*>(v->mImage.get());
+  GrallocImage::GrallocData data;
+
+  data.mPicSize = aPicture.Size().ToIntSize();
+  data.mGraphicBuffer = aBuffer;
+
+  videoImage->SetData(data);
+
+  return v.forget();
+}
+#endif  // MOZ_OMX_DECODER
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/MediaData.h
@@ -0,0 +1,249 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#if !defined(MediaData_h)
+#define MediaData_h
+
+#include "nsSize.h"
+#include "nsRect.h"
+#include "AudioSampleFormat.h"
+#include "nsIMemoryReporter.h"
+#include "SharedBuffer.h"
+
+namespace mozilla {
+
+namespace layers {
+class Image;
+class ImageContainer;
+}
+
+// Container that holds media samples.
+class MediaData {
+public:
+
+  enum Type {
+    AUDIO_SAMPLES = 0,
+    VIDEO_FRAME = 1
+  };
+
+  MediaData(Type aType,
+            int64_t aOffset,
+            int64_t aTimestamp,
+            int64_t aDuration)
+    : mType(aType)
+    , mOffset(aOffset)
+    , mTime(aTimestamp)
+    , mDuration(aDuration)
+  {}
+
+  virtual ~MediaData() {}
+
+  // Type of contained data.
+  const Type mType;
+
+  // Approximate byte offset where this data was demuxed from its media.
+  const int64_t mOffset;
+
+  // Start time of sample, in microseconds.
+  const int64_t mTime;
+
+  // Duration of sample, in microseconds.
+  const int64_t mDuration;
+
+  int64_t GetEndTime() const { return mTime + mDuration; }
+
+};
+
+// Holds chunk a decoded audio frames.
+class AudioData : public MediaData {
+public:
+
+  AudioData(int64_t aOffset,
+            int64_t aTime,
+            int64_t aDuration,
+            uint32_t aFrames,
+            AudioDataValue* aData,
+            uint32_t aChannels)
+    : MediaData(AUDIO_SAMPLES, aOffset, aTime, aDuration)
+    , mFrames(aFrames)
+    , mChannels(aChannels)
+    , mAudioData(aData)
+  {
+    MOZ_COUNT_CTOR(AudioData);
+  }
+
+  ~AudioData()
+  {
+    MOZ_COUNT_DTOR(AudioData);
+  }
+
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+    size_t size = aMallocSizeOf(this) + aMallocSizeOf(mAudioData);
+    if (mAudioBuffer) {
+      size += mAudioBuffer->SizeOfIncludingThis(aMallocSizeOf);
+    }
+    return size;
+  }
+
+  // If mAudioBuffer is null, creates it from mAudioData.
+  void EnsureAudioBuffer();
+
+  const uint32_t mFrames;
+  const uint32_t mChannels;
+  // At least one of mAudioBuffer/mAudioData must be non-null.
+  // mChannels channels, each with mFrames frames
+  nsRefPtr<SharedBuffer> mAudioBuffer;
+  // mFrames frames, each with mChannels values
+  nsAutoArrayPtr<AudioDataValue> mAudioData;
+};
+
+namespace layers {
+class GraphicBufferLocked;
+}
+
+class VideoInfo;
+
+// Holds a decoded video frame, in YCbCr format. These are queued in the reader.
+class VideoData : public MediaData {
+public:
+  typedef layers::ImageContainer ImageContainer;
+  typedef layers::Image Image;
+
+  // YCbCr data obtained from decoding the video. The index's are:
+  //   0 = Y
+  //   1 = Cb
+  //   2 = Cr
+  struct YCbCrBuffer {
+    struct Plane {
+      uint8_t* mData;
+      uint32_t mWidth;
+      uint32_t mHeight;
+      uint32_t mStride;
+      uint32_t mOffset;
+      uint32_t mSkip;
+    };
+
+    Plane mPlanes[3];
+  };
+
+  // Constructs a VideoData object. If aImage is nullptr, creates a new Image
+  // holding a copy of the YCbCr data passed in aBuffer. If aImage is not
+  // nullptr, it's stored as the underlying video image and aBuffer is assumed
+  // to point to memory within aImage so no copy is made. aTimecode is a codec
+  // specific number representing the timestamp of the frame of video data.
+  // Returns nsnull if an error occurs. This may indicate that memory couldn't
+  // be allocated to create the VideoData object, or it may indicate some
+  // problem with the input data (e.g. negative stride).
+  static VideoData* Create(VideoInfo& aInfo,
+                           ImageContainer* aContainer,
+                           Image* aImage,
+                           int64_t aOffset,
+                           int64_t aTime,
+                           int64_t aDuration,
+                           const YCbCrBuffer &aBuffer,
+                           bool aKeyframe,
+                           int64_t aTimecode,
+                           nsIntRect aPicture);
+
+  // Variant that always makes a copy of aBuffer
+  static VideoData* Create(VideoInfo& aInfo,
+                           ImageContainer* aContainer,
+                           int64_t aOffset,
+                           int64_t aTime,
+                           int64_t aDuration,
+                           const YCbCrBuffer &aBuffer,
+                           bool aKeyframe,
+                           int64_t aTimecode,
+                           nsIntRect aPicture);
+
+  // Variant to create a VideoData instance given an existing aImage
+  static VideoData* Create(VideoInfo& aInfo,
+                           Image* aImage,
+                           int64_t aOffset,
+                           int64_t aTime,
+                           int64_t aDuration,
+                           const YCbCrBuffer &aBuffer,
+                           bool aKeyframe,
+                           int64_t aTimecode,
+                           nsIntRect aPicture);
+
+  static VideoData* Create(VideoInfo& aInfo,
+                           ImageContainer* aContainer,
+                           int64_t aOffset,
+                           int64_t aTime,
+                           int64_t aDuration,
+                           layers::GraphicBufferLocked* aBuffer,
+                           bool aKeyframe,
+                           int64_t aTimecode,
+                           nsIntRect aPicture);
+
+  static VideoData* CreateFromImage(VideoInfo& aInfo,
+                                    ImageContainer* aContainer,
+                                    int64_t aOffset,
+                                    int64_t aTime,
+                                    int64_t aDuration,
+                                    const nsRefPtr<Image>& aImage,
+                                    bool aKeyframe,
+                                    int64_t aTimecode,
+                                    nsIntRect aPicture);
+
+  // Creates a new VideoData identical to aOther, but with a different
+  // specified duration. All data from aOther is copied into the new
+  // VideoData. The new VideoData's mImage field holds a reference to
+  // aOther's mImage, i.e. the Image is not copied. This function is useful
+  // in reader backends that can't determine the duration of a VideoData
+  // until the next frame is decoded, i.e. it's a way to change the const
+  // duration field on a VideoData.
+  static VideoData* ShallowCopyUpdateDuration(VideoData* aOther,
+                                              int64_t aDuration);
+
+  // Constructs a duplicate VideoData object. This intrinsically tells the
+  // player that it does not need to update the displayed frame when this
+  // frame is played; this frame is identical to the previous.
+  static VideoData* CreateDuplicate(int64_t aOffset,
+                                    int64_t aTime,
+                                    int64_t aDuration,
+                                    int64_t aTimecode)
+  {
+    return new VideoData(aOffset, aTime, aDuration, aTimecode);
+  }
+
+  ~VideoData();
+
+  // Dimensions at which to display the video frame. The picture region
+  // will be scaled to this size. This is should be the picture region's
+  // dimensions scaled with respect to its aspect ratio.
+  const nsIntSize mDisplay;
+
+  // Codec specific internal time code. For Ogg based codecs this is the
+  // granulepos.
+  const int64_t mTimecode;
+
+  // This frame's image.
+  nsRefPtr<Image> mImage;
+
+  // When true, denotes that this frame is identical to the frame that
+  // came before; it's a duplicate. mBuffer will be empty.
+  const bool mDuplicate;
+  const bool mKeyframe;
+
+public:
+  VideoData(int64_t aOffset,
+            int64_t aTime,
+            int64_t aDuration,
+            int64_t aTimecode);
+
+  VideoData(int64_t aOffset,
+            int64_t aTime,
+            int64_t aDuration,
+            bool aKeyframe,
+            int64_t aTimecode,
+            nsIntSize aDisplay);
+
+};
+
+} // namespace mozilla
+
+#endif // MediaData_h
--- a/content/media/MediaDecoderReader.cpp
+++ b/content/media/MediaDecoderReader.cpp
@@ -1,396 +1,41 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaDecoderReader.h"
-#ifdef MOZ_OMX_DECODER
-#include "GrallocImages.h"
-#endif
 #include "AbstractMediaDecoder.h"
 #include "VideoUtils.h"
 #include "ImageContainer.h"
 
 #include "mozilla/mozalloc.h"
 #include <stdint.h>
 #include <algorithm>
 
 namespace mozilla {
 
-using namespace mozilla::gfx;
-using layers::ImageContainer;
-using layers::PlanarYCbCrImage;
-using layers::PlanarYCbCrData;
-
-// Verify these values are sane. Once we've checked the frame sizes, we then
-// can do less integer overflow checking.
-static_assert(MAX_VIDEO_WIDTH < PlanarYCbCrImage::MAX_DIMENSION,
-              "MAX_VIDEO_WIDTH is too large");
-static_assert(MAX_VIDEO_HEIGHT < PlanarYCbCrImage::MAX_DIMENSION,
-              "MAX_VIDEO_HEIGHT is too large");
-static_assert(PlanarYCbCrImage::MAX_DIMENSION < UINT32_MAX / PlanarYCbCrImage::MAX_DIMENSION,
-              "MAX_DIMENSION*MAX_DIMENSION doesn't fit in 32 bits");
-
 // Un-comment to enable logging of seek bisections.
 //#define SEEK_LOGGING
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define DECODER_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
 #ifdef SEEK_LOGGING
 #define SEEK_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
 #else
 #define SEEK_LOG(type, msg)
 #endif
 #else
 #define DECODER_LOG(type, msg)
 #define SEEK_LOG(type, msg)
 #endif
 
-void
-AudioData::EnsureAudioBuffer()
-{
-  if (mAudioBuffer)
-    return;
-  mAudioBuffer = SharedBuffer::Create(mFrames*mChannels*sizeof(AudioDataValue));
-
-  AudioDataValue* data = static_cast<AudioDataValue*>(mAudioBuffer->Data());
-  for (uint32_t i = 0; i < mFrames; ++i) {
-    for (uint32_t j = 0; j < mChannels; ++j) {
-      data[j*mFrames + i] = mAudioData[i*mChannels + j];
-    }
-  }
-}
-
-static bool
-ValidatePlane(const VideoData::YCbCrBuffer::Plane& aPlane)
-{
-  return aPlane.mWidth <= PlanarYCbCrImage::MAX_DIMENSION &&
-         aPlane.mHeight <= PlanarYCbCrImage::MAX_DIMENSION &&
-         aPlane.mWidth * aPlane.mHeight < MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
-         aPlane.mStride > 0;
-}
-
-#if 0
-static bool
-IsYV12Format(const VideoData::YCbCrBuffer::Plane& aYPlane,
-             const VideoData::YCbCrBuffer::Plane& aCbPlane,
-             const VideoData::YCbCrBuffer::Plane& aCrPlane)
-{
-  return
-    aYPlane.mWidth % 2 == 0 &&
-    aYPlane.mHeight % 2 == 0 &&
-    aYPlane.mWidth / 2 == aCbPlane.mWidth &&
-    aYPlane.mHeight / 2 == aCbPlane.mHeight &&
-    aCbPlane.mWidth == aCrPlane.mWidth &&
-    aCbPlane.mHeight == aCrPlane.mHeight;
-}
-#endif
-
-bool
-VideoInfo::ValidateVideoRegion(const nsIntSize& aFrame,
-                               const nsIntRect& aPicture,
-                               const nsIntSize& aDisplay)
-{
-  return
-    aFrame.width <= PlanarYCbCrImage::MAX_DIMENSION &&
-    aFrame.height <= PlanarYCbCrImage::MAX_DIMENSION &&
-    aFrame.width * aFrame.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
-    aFrame.width * aFrame.height != 0 &&
-    aPicture.width <= PlanarYCbCrImage::MAX_DIMENSION &&
-    aPicture.x < PlanarYCbCrImage::MAX_DIMENSION &&
-    aPicture.x + aPicture.width < PlanarYCbCrImage::MAX_DIMENSION &&
-    aPicture.height <= PlanarYCbCrImage::MAX_DIMENSION &&
-    aPicture.y < PlanarYCbCrImage::MAX_DIMENSION &&
-    aPicture.y + aPicture.height < PlanarYCbCrImage::MAX_DIMENSION &&
-    aPicture.width * aPicture.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
-    aPicture.width * aPicture.height != 0 &&
-    aDisplay.width <= PlanarYCbCrImage::MAX_DIMENSION &&
-    aDisplay.height <= PlanarYCbCrImage::MAX_DIMENSION &&
-    aDisplay.width * aDisplay.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
-    aDisplay.width * aDisplay.height != 0;
-}
-
-VideoData::VideoData(int64_t aOffset, int64_t aTime, int64_t aDuration, int64_t aTimecode)
-  : MediaData(VIDEO_FRAME, aOffset, aTime, aDuration),
-    mTimecode(aTimecode),
-    mDuplicate(true),
-    mKeyframe(false)
-{
-  MOZ_COUNT_CTOR(VideoData);
-  NS_ASSERTION(mDuration >= 0, "Frame must have non-negative duration.");
-}
-
-VideoData::VideoData(int64_t aOffset,
-                     int64_t aTime,
-                     int64_t aDuration,
-                     bool aKeyframe,
-                     int64_t aTimecode,
-                     nsIntSize aDisplay)
-  : MediaData(VIDEO_FRAME, aOffset, aTime, aDuration),
-    mDisplay(aDisplay),
-    mTimecode(aTimecode),
-    mDuplicate(false),
-    mKeyframe(aKeyframe)
-{
-  MOZ_COUNT_CTOR(VideoData);
-  NS_ASSERTION(mDuration >= 0, "Frame must have non-negative duration.");
-}
-
-VideoData::~VideoData()
-{
-  MOZ_COUNT_DTOR(VideoData);
-}
-
-/* static */
-VideoData* VideoData::ShallowCopyUpdateDuration(VideoData* aOther,
-                                                int64_t aDuration)
-{
-  VideoData* v = new VideoData(aOther->mOffset,
-                               aOther->mTime,
-                               aDuration,
-                               aOther->mKeyframe,
-                               aOther->mTimecode,
-                               aOther->mDisplay);
-  v->mImage = aOther->mImage;
-  return v;
-}
-
-VideoData* VideoData::Create(VideoInfo& aInfo,
-                             ImageContainer* aContainer,
-                             Image* aImage,
-                             int64_t aOffset,
-                             int64_t aTime,
-                             int64_t aDuration,
-                             const YCbCrBuffer& aBuffer,
-                             bool aKeyframe,
-                             int64_t aTimecode,
-                             nsIntRect aPicture)
-{
-  if (!aImage && !aContainer) {
-    // Create a dummy VideoData with no image. This gives us something to
-    // send to media streams if necessary.
-    nsAutoPtr<VideoData> v(new VideoData(aOffset,
-                                         aTime,
-                                         aDuration,
-                                         aKeyframe,
-                                         aTimecode,
-                                         aInfo.mDisplay));
-    return v.forget();
-  }
-
-  // The following situation should never happen unless there is a bug
-  // in the decoder
-  if (aBuffer.mPlanes[1].mWidth != aBuffer.mPlanes[2].mWidth ||
-      aBuffer.mPlanes[1].mHeight != aBuffer.mPlanes[2].mHeight) {
-    NS_ERROR("C planes with different sizes");
-    return nullptr;
-  }
-
-  // The following situations could be triggered by invalid input
-  if (aPicture.width <= 0 || aPicture.height <= 0) {
-    NS_WARNING("Empty picture rect");
-    return nullptr;
-  }
-  if (!ValidatePlane(aBuffer.mPlanes[0]) || !ValidatePlane(aBuffer.mPlanes[1]) ||
-      !ValidatePlane(aBuffer.mPlanes[2])) {
-    NS_WARNING("Invalid plane size");
-    return nullptr;
-  }
-
-  // Ensure the picture size specified in the headers can be extracted out of
-  // the frame we've been supplied without indexing out of bounds.
-  CheckedUint32 xLimit = aPicture.x + CheckedUint32(aPicture.width);
-  CheckedUint32 yLimit = aPicture.y + CheckedUint32(aPicture.height);
-  if (!xLimit.isValid() || xLimit.value() > aBuffer.mPlanes[0].mStride ||
-      !yLimit.isValid() || yLimit.value() > aBuffer.mPlanes[0].mHeight)
-  {
-    // The specified picture dimensions can't be contained inside the video
-    // frame, we'll stomp memory if we try to copy it. Fail.
-    NS_WARNING("Overflowing picture rect");
-    return nullptr;
-  }
-
-  nsAutoPtr<VideoData> v(new VideoData(aOffset,
-                                       aTime,
-                                       aDuration,
-                                       aKeyframe,
-                                       aTimecode,
-                                       aInfo.mDisplay));
-  const YCbCrBuffer::Plane &Y = aBuffer.mPlanes[0];
-  const YCbCrBuffer::Plane &Cb = aBuffer.mPlanes[1];
-  const YCbCrBuffer::Plane &Cr = aBuffer.mPlanes[2];
-
-  if (!aImage) {
-    // Currently our decoder only knows how to output to ImageFormat::PLANAR_YCBCR
-    // format.
-#if 0
-    if (IsYV12Format(Y, Cb, Cr)) {
-      v->mImage = aContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR);
-    }
-#endif
-    if (!v->mImage) {
-      v->mImage = aContainer->CreateImage(ImageFormat::PLANAR_YCBCR);
-    }
-  } else {
-    v->mImage = aImage;
-  }
-
-  if (!v->mImage) {
-    return nullptr;
-  }
-  NS_ASSERTION(v->mImage->GetFormat() == ImageFormat::PLANAR_YCBCR ||
-               v->mImage->GetFormat() == ImageFormat::GRALLOC_PLANAR_YCBCR,
-               "Wrong format?");
-  PlanarYCbCrImage* videoImage = static_cast<PlanarYCbCrImage*>(v->mImage.get());
-
-  PlanarYCbCrData data;
-  data.mYChannel = Y.mData + Y.mOffset;
-  data.mYSize = IntSize(Y.mWidth, Y.mHeight);
-  data.mYStride = Y.mStride;
-  data.mYSkip = Y.mSkip;
-  data.mCbChannel = Cb.mData + Cb.mOffset;
-  data.mCrChannel = Cr.mData + Cr.mOffset;
-  data.mCbCrSize = IntSize(Cb.mWidth, Cb.mHeight);
-  data.mCbCrStride = Cb.mStride;
-  data.mCbSkip = Cb.mSkip;
-  data.mCrSkip = Cr.mSkip;
-  data.mPicX = aPicture.x;
-  data.mPicY = aPicture.y;
-  data.mPicSize = aPicture.Size().ToIntSize();
-  data.mStereoMode = aInfo.mStereoMode;
-
-  videoImage->SetDelayedConversion(true);
-  if (!aImage) {
-    videoImage->SetData(data);
-  } else {
-    videoImage->SetDataNoCopy(data);
-  }
-
-  return v.forget();
-}
-
-VideoData* VideoData::Create(VideoInfo& aInfo,
-                             ImageContainer* aContainer,
-                             int64_t aOffset,
-                             int64_t aTime,
-                             int64_t aDuration,
-                             const YCbCrBuffer& aBuffer,
-                             bool aKeyframe,
-                             int64_t aTimecode,
-                             nsIntRect aPicture)
-{
-  return Create(aInfo, aContainer, nullptr, aOffset, aTime, aDuration, aBuffer,
-                aKeyframe, aTimecode, aPicture);
-}
-
-VideoData* VideoData::Create(VideoInfo& aInfo,
-                             Image* aImage,
-                             int64_t aOffset,
-                             int64_t aTime,
-                             int64_t aDuration,
-                             const YCbCrBuffer& aBuffer,
-                             bool aKeyframe,
-                             int64_t aTimecode,
-                             nsIntRect aPicture)
-{
-  return Create(aInfo, nullptr, aImage, aOffset, aTime, aDuration, aBuffer,
-                aKeyframe, aTimecode, aPicture);
-}
-
-VideoData* VideoData::CreateFromImage(VideoInfo& aInfo,
-                                      ImageContainer* aContainer,
-                                      int64_t aOffset,
-                                      int64_t aTime,
-                                      int64_t aDuration,
-                                      const nsRefPtr<Image>& aImage,
-                                      bool aKeyframe,
-                                      int64_t aTimecode,
-                                      nsIntRect aPicture)
-{
-  nsAutoPtr<VideoData> v(new VideoData(aOffset,
-                                       aTime,
-                                       aDuration,
-                                       aKeyframe,
-                                       aTimecode,
-                                       aInfo.mDisplay));
-  v->mImage = aImage;
-  return v.forget();
-}
-
-#ifdef MOZ_OMX_DECODER
-VideoData* VideoData::Create(VideoInfo& aInfo,
-                             ImageContainer* aContainer,
-                             int64_t aOffset,
-                             int64_t aTime,
-                             int64_t aDuration,
-                             mozilla::layers::GraphicBufferLocked* aBuffer,
-                             bool aKeyframe,
-                             int64_t aTimecode,
-                             nsIntRect aPicture)
-{
-  if (!aContainer) {
-    // Create a dummy VideoData with no image. This gives us something to
-    // send to media streams if necessary.
-    nsAutoPtr<VideoData> v(new VideoData(aOffset,
-                                         aTime,
-                                         aDuration,
-                                         aKeyframe,
-                                         aTimecode,
-                                         aInfo.mDisplay));
-    return v.forget();
-  }
-
-  // The following situations could be triggered by invalid input
-  if (aPicture.width <= 0 || aPicture.height <= 0) {
-    NS_WARNING("Empty picture rect");
-    return nullptr;
-  }
-
-  // Ensure the picture size specified in the headers can be extracted out of
-  // the frame we've been supplied without indexing out of bounds.
-  CheckedUint32 xLimit = aPicture.x + CheckedUint32(aPicture.width);
-  CheckedUint32 yLimit = aPicture.y + CheckedUint32(aPicture.height);
-  if (!xLimit.isValid() || !yLimit.isValid())
-  {
-    // The specified picture dimensions can't be contained inside the video
-    // frame, we'll stomp memory if we try to copy it. Fail.
-    NS_WARNING("Overflowing picture rect");
-    return nullptr;
-  }
-
-  nsAutoPtr<VideoData> v(new VideoData(aOffset,
-                                       aTime,
-                                       aDuration,
-                                       aKeyframe,
-                                       aTimecode,
-                                       aInfo.mDisplay));
-
-  v->mImage = aContainer->CreateImage(ImageFormat::GRALLOC_PLANAR_YCBCR);
-  if (!v->mImage) {
-    return nullptr;
-  }
-  NS_ASSERTION(v->mImage->GetFormat() == ImageFormat::GRALLOC_PLANAR_YCBCR,
-               "Wrong format?");
-  typedef mozilla::layers::GrallocImage GrallocImage;
-  GrallocImage* videoImage = static_cast<GrallocImage*>(v->mImage.get());
-  GrallocImage::GrallocData data;
-
-  data.mPicSize = aPicture.Size().ToIntSize();
-  data.mGraphicBuffer = aBuffer;
-
-  videoImage->SetData(data);
-
-  return v.forget();
-}
-#endif  // MOZ_OMX_DECODER
-
 void* MediaDecoderReader::VideoQueueMemoryFunctor::operator()(void* anObject) {
   const VideoData* v = static_cast<const VideoData*>(anObject);
   if (!v->mImage) {
     return nullptr;
   }
 
   if (v->mImage->GetFormat() == ImageFormat::PLANAR_YCBCR) {
     mozilla::layers::PlanarYCbCrImage* vi = static_cast<mozilla::layers::PlanarYCbCrImage*>(v->mImage.get());
--- a/content/media/MediaDecoderReader.h
+++ b/content/media/MediaDecoderReader.h
@@ -1,476 +1,27 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(MediaDecoderReader_h_)
 #define MediaDecoderReader_h_
 
-#include <nsDeque.h>
-#include "nsSize.h"
-#include "mozilla/ReentrantMonitor.h"
-#include "SharedBuffer.h"
-#include "AudioSampleFormat.h"
 #include "AbstractMediaDecoder.h"
-#include "ImageTypes.h"
-#include "nsIMemoryReporter.h"
-
-struct nsIntRect;
+#include "MediaInfo.h"
+#include "MediaData.h"
+#include "MediaQueue.h"
 
 namespace mozilla {
 
-namespace layers {
-class Image;
-class ImageContainer;
-}
-
 namespace dom {
 class TimeRanges;
 }
 
-// Stores info relevant to presenting media frames.
-class VideoInfo {
-public:
-  VideoInfo()
-    : mDisplay(0,0),
-      mStereoMode(StereoMode::MONO),
-      mHasVideo(false)
-  {}
-
-  // Returns true if it's safe to use aPicture as the picture to be
-  // extracted inside a frame of size aFrame, and scaled up to and displayed
-  // at a size of aDisplay. You should validate the frame, picture, and
-  // display regions before using them to display video frames.
-  static bool ValidateVideoRegion(const nsIntSize& aFrame,
-                                  const nsIntRect& aPicture,
-                                  const nsIntSize& aDisplay);
-
-  // Size in pixels at which the video is rendered. This is after it has
-  // been scaled by its aspect ratio.
-  nsIntSize mDisplay;
-
-  // Indicates the frame layout for single track stereo videos.
-  StereoMode mStereoMode;
-
-  // True if we have an active video bitstream.
-  bool mHasVideo;
-};
-
-class AudioInfo {
-public:
-  AudioInfo()
-    : mRate(44100),
-      mChannels(2),
-      mHasAudio(false)
-  {}
-
-  // Sample rate.
-  uint32_t mRate;
-
-  // Number of audio channels.
-  uint32_t mChannels;
-
-  // True if we have an active audio bitstream.
-  bool mHasAudio;
-};
-
-class MediaInfo {
-public:
-  bool HasVideo() const
-  {
-    return mVideo.mHasVideo;
-  }
-
-  bool HasAudio() const
-  {
-    return mAudio.mHasAudio;
-  }
-
-  bool HasValidMedia() const
-  {
-    return HasVideo() || HasAudio();
-  }
-
-  VideoInfo mVideo;
-  AudioInfo mAudio;
-};
-
-// Container that holds media samples.
-class MediaData {
-public:
-
-  enum Type {
-    AUDIO_SAMPLES = 0,
-    VIDEO_FRAME = 1
-  };
-
-  MediaData(Type aType,
-            int64_t aOffset,
-            int64_t aTimestamp,
-            int64_t aDuration)
-    : mType(aType),
-      mOffset(aOffset),
-      mTime(aTimestamp),
-      mDuration(aDuration)
-  {}
-
-  virtual ~MediaData() {}
-
-  // Type of contained data.
-  const Type mType;
-
-  // Approximate byte offset where this data was demuxed from its media.
-  const int64_t mOffset;
-
-  // Start time of sample, in microseconds.
-  const int64_t mTime;
-
-  // Duration of sample, in microseconds.
-  const int64_t mDuration;
-
-  int64_t GetEndTime() const { return mTime + mDuration; }
-
-};
-
-// Holds chunk a decoded audio frames.
-class AudioData : public MediaData {
-public:
-
-  AudioData(int64_t aOffset,
-            int64_t aTime,
-            int64_t aDuration,
-            uint32_t aFrames,
-            AudioDataValue* aData,
-            uint32_t aChannels)
-  : MediaData(AUDIO_SAMPLES, aOffset, aTime, aDuration),
-    mFrames(aFrames),
-    mChannels(aChannels),
-    mAudioData(aData)
-  {
-    MOZ_COUNT_CTOR(AudioData);
-  }
-
-  ~AudioData()
-  {
-    MOZ_COUNT_DTOR(AudioData);
-  }
-
-  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
-    size_t size = aMallocSizeOf(this) + aMallocSizeOf(mAudioData);
-    if (mAudioBuffer) {
-      size += mAudioBuffer->SizeOfIncludingThis(aMallocSizeOf);
-    }
-    return size;
-  }
-
-  // If mAudioBuffer is null, creates it from mAudioData.
-  void EnsureAudioBuffer();
-
-  const uint32_t mFrames;
-  const uint32_t mChannels;
-  // At least one of mAudioBuffer/mAudioData must be non-null.
-  // mChannels channels, each with mFrames frames
-  nsRefPtr<SharedBuffer> mAudioBuffer;
-  // mFrames frames, each with mChannels values
-  nsAutoArrayPtr<AudioDataValue> mAudioData;
-};
-
-namespace layers {
-class GraphicBufferLocked;
-}
-
-// Holds a decoded video frame, in YCbCr format. These are queued in the reader.
-class VideoData : public MediaData {
-public:
-  typedef layers::ImageContainer ImageContainer;
-  typedef layers::Image Image;
-
-  // YCbCr data obtained from decoding the video. The index's are:
-  //   0 = Y
-  //   1 = Cb
-  //   2 = Cr
-  struct YCbCrBuffer {
-    struct Plane {
-      uint8_t* mData;
-      uint32_t mWidth;
-      uint32_t mHeight;
-      uint32_t mStride;
-      uint32_t mOffset;
-      uint32_t mSkip;
-    };
-
-    Plane mPlanes[3];
-  };
-
-  // Constructs a VideoData object. If aImage is nullptr, creates a new Image
-  // holding a copy of the YCbCr data passed in aBuffer. If aImage is not
-  // nullptr, it's stored as the underlying video image and aBuffer is assumed
-  // to point to memory within aImage so no copy is made. aTimecode is a codec
-  // specific number representing the timestamp of the frame of video data.
-  // Returns nsnull if an error occurs. This may indicate that memory couldn't
-  // be allocated to create the VideoData object, or it may indicate some
-  // problem with the input data (e.g. negative stride).
-  static VideoData* Create(VideoInfo& aInfo,
-                           ImageContainer* aContainer,
-                           Image* aImage,
-                           int64_t aOffset,
-                           int64_t aTime,
-                           int64_t aDuration,
-                           const YCbCrBuffer &aBuffer,
-                           bool aKeyframe,
-                           int64_t aTimecode,
-                           nsIntRect aPicture);
-
-  // Variant that always makes a copy of aBuffer
-  static VideoData* Create(VideoInfo& aInfo,
-                           ImageContainer* aContainer,
-                           int64_t aOffset,
-                           int64_t aTime,
-                           int64_t aDuration,
-                           const YCbCrBuffer &aBuffer,
-                           bool aKeyframe,
-                           int64_t aTimecode,
-                           nsIntRect aPicture);
-
-  // Variant to create a VideoData instance given an existing aImage
-  static VideoData* Create(VideoInfo& aInfo,
-                           Image* aImage,
-                           int64_t aOffset,
-                           int64_t aTime,
-                           int64_t aDuration,
-                           const YCbCrBuffer &aBuffer,
-                           bool aKeyframe,
-                           int64_t aTimecode,
-                           nsIntRect aPicture);
-
-  static VideoData* Create(VideoInfo& aInfo,
-                           ImageContainer* aContainer,
-                           int64_t aOffset,
-                           int64_t aTime,
-                           int64_t aDuration,
-                           layers::GraphicBufferLocked* aBuffer,
-                           bool aKeyframe,
-                           int64_t aTimecode,
-                           nsIntRect aPicture);
-
-  static VideoData* CreateFromImage(VideoInfo& aInfo,
-                                    ImageContainer* aContainer,
-                                    int64_t aOffset,
-                                    int64_t aTime,
-                                    int64_t aDuration,
-                                    const nsRefPtr<Image>& aImage,
-                                    bool aKeyframe,
-                                    int64_t aTimecode,
-                                    nsIntRect aPicture);
-
-  // Creates a new VideoData identical to aOther, but with a different
-  // specified duration. All data from aOther is copied into the new
-  // VideoData. The new VideoData's mImage field holds a reference to
-  // aOther's mImage, i.e. the Image is not copied. This function is useful
-  // in reader backends that can't determine the duration of a VideoData
-  // until the next frame is decoded, i.e. it's a way to change the const
-  // duration field on a VideoData.
-  static VideoData* ShallowCopyUpdateDuration(VideoData* aOther,
-                                              int64_t aDuration);
-
-  // Constructs a duplicate VideoData object. This intrinsically tells the
-  // player that it does not need to update the displayed frame when this
-  // frame is played; this frame is identical to the previous.
-  static VideoData* CreateDuplicate(int64_t aOffset,
-                                    int64_t aTime,
-                                    int64_t aDuration,
-                                    int64_t aTimecode)
-  {
-    return new VideoData(aOffset, aTime, aDuration, aTimecode);
-  }
-
-  ~VideoData();
-
-  // Dimensions at which to display the video frame. The picture region
-  // will be scaled to this size. This is should be the picture region's
-  // dimensions scaled with respect to its aspect ratio.
-  const nsIntSize mDisplay;
-
-  // Codec specific internal time code. For Ogg based codecs this is the
-  // granulepos.
-  const int64_t mTimecode;
-
-  // This frame's image.
-  nsRefPtr<Image> mImage;
-
-  // When true, denotes that this frame is identical to the frame that
-  // came before; it's a duplicate. mBuffer will be empty.
-  const bool mDuplicate;
-  const bool mKeyframe;
-
-public:
-  VideoData(int64_t aOffset,
-            int64_t aTime,
-            int64_t aDuration,
-            int64_t aTimecode);
-
-  VideoData(int64_t aOffset,
-            int64_t aTime,
-            int64_t aDuration,
-            bool aKeyframe,
-            int64_t aTimecode,
-            nsIntSize aDisplay);
-
-};
-
-// Thread and type safe wrapper around nsDeque.
-template <class T>
-class MediaQueueDeallocator : public nsDequeFunctor {
-  virtual void* operator() (void* anObject) {
-    delete static_cast<T*>(anObject);
-    return nullptr;
-  }
-};
-
-template <class T> class MediaQueue : private nsDeque {
- public:
-
-   MediaQueue()
-     : nsDeque(new MediaQueueDeallocator<T>()),
-       mReentrantMonitor("mediaqueue"),
-       mEndOfStream(false)
-   {}
-
-  ~MediaQueue() {
-    Reset();
-  }
-
-  inline int32_t GetSize() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    return nsDeque::GetSize();
-  }
-
-  inline void Push(T* aItem) {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    nsDeque::Push(aItem);
-  }
-
-  inline void PushFront(T* aItem) {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    nsDeque::PushFront(aItem);
-  }
-
-  inline T* Pop() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    return static_cast<T*>(nsDeque::Pop());
-  }
-
-  inline T* PopFront() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    return static_cast<T*>(nsDeque::PopFront());
-  }
-
-  inline T* Peek() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    return static_cast<T*>(nsDeque::Peek());
-  }
-
-  inline T* PeekFront() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    return static_cast<T*>(nsDeque::PeekFront());
-  }
-
-  inline void Empty() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    nsDeque::Empty();
-  }
-
-  inline void Erase() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    nsDeque::Erase();
-  }
-
-  void Reset() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    while (GetSize() > 0) {
-      T* x = PopFront();
-      delete x;
-    }
-    mEndOfStream = false;
-  }
-
-  bool AtEndOfStream() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    return GetSize() == 0 && mEndOfStream;
-  }
-
-  // Returns true if the media queue has had its last item added to it.
-  // This happens when the media stream has been completely decoded. Note this
-  // does not mean that the corresponding stream has finished playback.
-  bool IsFinished() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    return mEndOfStream;
-  }
-
-  // Informs the media queue that it won't be receiving any more items.
-  void Finish() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    mEndOfStream = true;
-  }
-
-  // Returns the approximate number of microseconds of items in the queue.
-  int64_t Duration() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    if (GetSize() < 2) {
-      return 0;
-    }
-    T* last = Peek();
-    T* first = PeekFront();
-    return last->mTime - first->mTime;
-  }
-
-  void LockedForEach(nsDequeFunctor& aFunctor) const {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    ForEach(aFunctor);
-  }
-
-  // Extracts elements from the queue into aResult, in order.
-  // Elements whose start time is before aTime are ignored.
-  void GetElementsAfter(int64_t aTime, nsTArray<T*>* aResult) {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    if (!GetSize())
-      return;
-    int32_t i;
-    for (i = GetSize() - 1; i > 0; --i) {
-      T* v = static_cast<T*>(ObjectAt(i));
-      if (v->GetEndTime() < aTime)
-        break;
-    }
-    // Elements less than i have a end time before aTime. It's also possible
-    // that the element at i has a end time before aTime, but that's OK.
-    for (; i < GetSize(); ++i) {
-      aResult->AppendElement(static_cast<T*>(ObjectAt(i)));
-    }
-  }
-
-  uint32_t FrameCount() {
-    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    uint32_t frames = 0;
-    for (int32_t i = 0; i < GetSize(); ++i) {
-      T* v = static_cast<T*>(ObjectAt(i));
-      frames += v->mFrames;
-    }
-    return frames;
-  }
-
-private:
-  mutable ReentrantMonitor mReentrantMonitor;
-
-  // True when we've decoded the last frame of data in the
-  // bitstream for which we're queueing frame data.
-  bool mEndOfStream;
-};
-
 // Encapsulates the decoding and reading of media data. Reading can only be
 // done on the decode thread. Never hold the decoder monitor when
 // calling into this class. Unless otherwise specified, methods and fields of
 // this class can only be accessed on the decode thread.
 class MediaDecoderReader {
 public:
   MediaDecoderReader(AbstractMediaDecoder* aDecoder);
   virtual ~MediaDecoderReader();
new file mode 100644
--- /dev/null
+++ b/content/media/MediaInfo.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#if !defined(MediaInfo_h)
+#define MediaInfo_h
+
+#include "nsSize.h"
+#include "nsRect.h"
+#include "ImageTypes.h"
+
+namespace mozilla {
+
+// Stores info relevant to presenting media frames.
+class VideoInfo {
+public:
+  VideoInfo()
+    : mDisplay(0,0)
+    , mStereoMode(StereoMode::MONO)
+    , mHasVideo(false)
+  {}
+
+  // Size in pixels at which the video is rendered. This is after it has
+  // been scaled by its aspect ratio.
+  nsIntSize mDisplay;
+
+  // Indicates the frame layout for single track stereo videos.
+  StereoMode mStereoMode;
+
+  // True if we have an active video bitstream.
+  bool mHasVideo;
+};
+
+class AudioInfo {
+public:
+  AudioInfo()
+    : mRate(44100)
+    , mChannels(2)
+    , mHasAudio(false)
+  {}
+
+  // Sample rate.
+  uint32_t mRate;
+
+  // Number of audio channels.
+  uint32_t mChannels;
+
+  // True if we have an active audio bitstream.
+  bool mHasAudio;
+};
+
+class MediaInfo {
+public:
+  bool HasVideo() const
+  {
+    return mVideo.mHasVideo;
+  }
+
+  bool HasAudio() const
+  {
+    return mAudio.mHasAudio;
+  }
+
+  bool HasValidMedia() const
+  {
+    return HasVideo() || HasAudio();
+  }
+
+  VideoInfo mVideo;
+  AudioInfo mAudio;
+};
+
+} // namespace mozilla
+
+#endif // MediaInfo_h
new file mode 100644
--- /dev/null
+++ b/content/media/MediaQueue.h
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#if !defined(MediaQueue_h_)
+#define MediaQueue_h_
+
+#include "nsDeque.h"
+#include "nsTArray.h"
+#include "mozilla/ReentrantMonitor.h"
+
+namespace mozilla {
+
+// Thread and type safe wrapper around nsDeque.
+template <class T>
+class MediaQueueDeallocator : public nsDequeFunctor {
+  virtual void* operator() (void* anObject) {
+    delete static_cast<T*>(anObject);
+    return nullptr;
+  }
+};
+
+template <class T> class MediaQueue : private nsDeque {
+ public:
+
+   MediaQueue()
+     : nsDeque(new MediaQueueDeallocator<T>()),
+       mReentrantMonitor("mediaqueue"),
+       mEndOfStream(false)
+   {}
+
+  ~MediaQueue() {
+    Reset();
+  }
+
+  inline int32_t GetSize() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return nsDeque::GetSize();
+  }
+
+  inline void Push(T* aItem) {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    nsDeque::Push(aItem);
+  }
+
+  inline void PushFront(T* aItem) {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    nsDeque::PushFront(aItem);
+  }
+
+  inline T* Pop() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return static_cast<T*>(nsDeque::Pop());
+  }
+
+  inline T* PopFront() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return static_cast<T*>(nsDeque::PopFront());
+  }
+
+  inline T* Peek() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return static_cast<T*>(nsDeque::Peek());
+  }
+
+  inline T* PeekFront() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return static_cast<T*>(nsDeque::PeekFront());
+  }
+
+  inline void Empty() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    nsDeque::Empty();
+  }
+
+  inline void Erase() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    nsDeque::Erase();
+  }
+
+  void Reset() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    while (GetSize() > 0) {
+      T* x = PopFront();
+      delete x;
+    }
+    mEndOfStream = false;
+  }
+
+  bool AtEndOfStream() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return GetSize() == 0 && mEndOfStream;
+  }
+
+  // Returns true if the media queue has had its last item added to it.
+  // This happens when the media stream has been completely decoded. Note this
+  // does not mean that the corresponding stream has finished playback.
+  bool IsFinished() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    return mEndOfStream;
+  }
+
+  // Informs the media queue that it won't be receiving any more items.
+  void Finish() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    mEndOfStream = true;
+  }
+
+  // Returns the approximate number of microseconds of items in the queue.
+  int64_t Duration() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    if (GetSize() < 2) {
+      return 0;
+    }
+    T* last = Peek();
+    T* first = PeekFront();
+    return last->mTime - first->mTime;
+  }
+
+  void LockedForEach(nsDequeFunctor& aFunctor) const {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    ForEach(aFunctor);
+  }
+
+  // Extracts elements from the queue into aResult, in order.
+  // Elements whose start time is before aTime are ignored.
+  void GetElementsAfter(int64_t aTime, nsTArray<T*>* aResult) {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    if (!GetSize())
+      return;
+    int32_t i;
+    for (i = GetSize() - 1; i > 0; --i) {
+      T* v = static_cast<T*>(ObjectAt(i));
+      if (v->GetEndTime() < aTime)
+        break;
+    }
+    // Elements less than i have a end time before aTime. It's also possible
+    // that the element at i has a end time before aTime, but that's OK.
+    for (; i < GetSize(); ++i) {
+      aResult->AppendElement(static_cast<T*>(ObjectAt(i)));
+    }
+  }
+
+  uint32_t FrameCount() {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    uint32_t frames = 0;
+    for (int32_t i = 0; i < GetSize(); ++i) {
+      T* v = static_cast<T*>(ObjectAt(i));
+      frames += v->mFrames;
+    }
+    return frames;
+  }
+
+private:
+  mutable ReentrantMonitor mReentrantMonitor;
+
+  // True when we've decoded the last frame of data in the
+  // bitstream for which we're queueing frame data.
+  bool mEndOfStream;
+};
+
+} // namespace mozilla
+
+#endif
--- a/content/media/VideoUtils.cpp
+++ b/content/media/VideoUtils.cpp
@@ -3,21 +3,24 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "VideoUtils.h"
 #include "MediaResource.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "nsMathUtils.h"
 #include "nsSize.h"
 #include "VorbisUtils.h"
+#include "ImageContainer.h"
 
 #include <stdint.h>
 
 namespace mozilla {
 
+using layers::PlanarYCbCrImage;
+
 // Converts from number of audio frames to microseconds, given the specified
 // audio rate.
 CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate) {
   return (CheckedInt64(aFrames) * USECS_PER_S) / aRate;
 }
 
 // Converts from microseconds to number of audio frames, given the specified
 // audio rate.
@@ -151,9 +154,32 @@ IsVideoContentType(const nsCString& aCon
 {
   NS_NAMED_LITERAL_CSTRING(video, "video");
   if (FindInReadable(video, aContentType)) {
     return true;
   }
   return false;
 }
 
-} // end namespace mozilla
\ No newline at end of file
+bool
+IsValidVideoRegion(const nsIntSize& aFrame, const nsIntRect& aPicture,
+                   const nsIntSize& aDisplay)
+{
+  return
+    aFrame.width <= PlanarYCbCrImage::MAX_DIMENSION &&
+    aFrame.height <= PlanarYCbCrImage::MAX_DIMENSION &&
+    aFrame.width * aFrame.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
+    aFrame.width * aFrame.height != 0 &&
+    aPicture.width <= PlanarYCbCrImage::MAX_DIMENSION &&
+    aPicture.x < PlanarYCbCrImage::MAX_DIMENSION &&
+    aPicture.x + aPicture.width < PlanarYCbCrImage::MAX_DIMENSION &&
+    aPicture.height <= PlanarYCbCrImage::MAX_DIMENSION &&
+    aPicture.y < PlanarYCbCrImage::MAX_DIMENSION &&
+    aPicture.y + aPicture.height < PlanarYCbCrImage::MAX_DIMENSION &&
+    aPicture.width * aPicture.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
+    aPicture.width * aPicture.height != 0 &&
+    aDisplay.width <= PlanarYCbCrImage::MAX_DIMENSION &&
+    aDisplay.height <= PlanarYCbCrImage::MAX_DIMENSION &&
+    aDisplay.width * aDisplay.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
+    aDisplay.width * aDisplay.height != 0;
+}
+
+} // end namespace mozilla
--- a/content/media/VideoUtils.h
+++ b/content/media/VideoUtils.h
@@ -21,16 +21,17 @@
 #include "AudioSampleFormat.h"
 
 using mozilla::CheckedInt64;
 using mozilla::CheckedUint64;
 using mozilla::CheckedInt32;
 using mozilla::CheckedUint32;
 
 struct nsIntSize;
+struct nsIntRect;
 
 // This file contains stuff we'd rather put elsewhere, but which is
 // dependent on other changes which we don't want to wait for. We plan to
 // remove this file in the near future.
 
 
 // This belongs in xpcom/monitor/Monitor.h, once we've made
 // mozilla::Monitor non-reentrant.
@@ -130,17 +131,17 @@ static const int64_t USECS_PER_MS = 1000
 // The maximum height and width of the video. Used for
 // sanitizing the memory allocation of the RGB buffer.
 // The maximum resolution we anticipate encountering in the
 // wild is 2160p - 3840x2160 pixels.
 static const int32_t MAX_VIDEO_WIDTH = 4000;
 static const int32_t MAX_VIDEO_HEIGHT = 3000;
 
 // Scales the display rect aDisplay by aspect ratio aAspectRatio.
-// Note that aDisplay must be validated by VideoInfo::ValidateVideoRegion()
+// Note that aDisplay must be validated by IsValidVideoRegion()
 // before being used!
 void ScaleDisplayByAspectRatio(nsIntSize& aDisplay, float aAspectRatio);
 
 // The amount of virtual memory reserved for thread stacks.
 #if (defined(XP_WIN) || defined(XP_MACOSX) || defined(LINUX)) && \
     !defined(MOZ_ASAN)
 #define MEDIA_THREAD_STACK_SIZE (128 * 1024)
 #else
@@ -152,11 +153,18 @@ void ScaleDisplayByAspectRatio(nsIntSize
 // Input are the buffer contains multichannel data,
 // the number of channels and the number of frames.
 int DownmixAudioToStereo(mozilla::AudioDataValue* buffer,
                          int channels,
                          uint32_t frames);
 
 bool IsVideoContentType(const nsCString& aContentType);
 
+// Returns true if it's safe to use aPicture as the picture to be
+// extracted inside a frame of size aFrame, and scaled up to and displayed
+// at a size of aDisplay. You should validate the frame, picture, and
+// display regions before using them to display video frames.
+bool IsValidVideoRegion(const nsIntSize& aFrame, const nsIntRect& aPicture,
+                        const nsIntSize& aDisplay);
+
 } // end namespace mozilla
 
 #endif
--- a/content/media/fmp4/wmf/WMFVideoDecoder.cpp
+++ b/content/media/fmp4/wmf/WMFVideoDecoder.cpp
@@ -166,17 +166,17 @@ WMFVideoDecoder::ConfigureVideoFrameGeom
                            &aspectDenom);
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   // Calculate and validate the picture region and frame dimensions after
   // scaling by the pixel aspect ratio.
   nsIntSize frameSize = nsIntSize(width, height);
   nsIntSize displaySize = nsIntSize(pictureRegion.width, pictureRegion.height);
   ScaleDisplayByAspectRatio(displaySize, float(aspectNum) / float(aspectDenom));
-  if (!VideoInfo::ValidateVideoRegion(frameSize, pictureRegion, displaySize)) {
+  if (!IsValidVideoRegion(frameSize, pictureRegion, displaySize)) {
     // Video track's frame sizes will overflow. Ignore the video track.
     return E_FAIL;
   }
 
   // Success! Save state.
   mVideoInfo.mDisplay = displaySize;
   mVideoInfo.mHasVideo = true;
   GetDefaultStride(mediaType, &mVideoStride);
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -68,21 +68,24 @@ EXPORTS += [
     'BufferDecoder.h',
     'BufferMediaResource.h',
     'DecoderTraits.h',
     'DOMMediaStream.h',
     'EncodedBufferCache.h',
     'FileBlockCache.h',
     'Latency.h',
     'MediaCache.h',
+    'MediaData.h',
     'MediaDecoder.h',
     'MediaDecoderOwner.h',
     'MediaDecoderReader.h',
     'MediaDecoderStateMachine.h',
+    'MediaInfo.h',
     'MediaMetadataManager.h',
+    'MediaQueue.h',
     'MediaRecorder.h',
     'MediaResource.h',
     'MediaSegment.h',
     'MediaStreamGraph.h',
     'MP3FrameParser.h',
     'RtspMediaResource.h',
     'SharedBuffer.h',
     'SharedThreadPool.h',
@@ -117,16 +120,17 @@ UNIFIED_SOURCES += [
     'AudioSegment.cpp',
     'AudioStream.cpp',
     'AudioStreamTrack.cpp',
     'BufferDecoder.cpp',
     'DOMMediaStream.cpp',
     'EncodedBufferCache.cpp',
     'FileBlockCache.cpp',
     'MediaCache.cpp',
+    'MediaData.cpp',
     'MediaDecoder.cpp',
     'MediaDecoderReader.cpp',
     'MediaDecoderStateMachine.cpp',
     'MediaRecorder.cpp',
     'MediaResource.cpp',
     'MediaShutdownManager.cpp',
     'MediaStreamGraph.cpp',
     'MediaStreamTrack.cpp',
--- a/content/media/ogg/OggCodecState.cpp
+++ b/content/media/ogg/OggCodecState.cpp
@@ -271,17 +271,17 @@ bool TheoraState::Init() {
 
   mPixelAspectRatio = (n == 0 || d == 0) ?
     1.0f : static_cast<float>(n) / static_cast<float>(d);
 
   // Ensure the frame and picture regions aren't larger than our prescribed
   // maximum, or zero sized.
   nsIntSize frame(mInfo.frame_width, mInfo.frame_height);
   nsIntRect picture(mInfo.pic_x, mInfo.pic_y, mInfo.pic_width, mInfo.pic_height);
-  if (!VideoInfo::ValidateVideoRegion(frame, picture, frame)) {
+  if (!IsValidVideoRegion(frame, picture, frame)) {
     return mActive = false;
   }
 
   mCtx = th_decode_alloc(&mInfo, mSetup);
   if (mCtx == nullptr) {
     return mActive = false;
   }
 
--- a/content/media/ogg/OggReader.cpp
+++ b/content/media/ogg/OggReader.cpp
@@ -283,17 +283,17 @@ nsresult OggReader::ReadMetadata(MediaIn
                                       mTheoraState->mInfo.pic_height);
 
     // Apply the aspect ratio to produce the intrinsic display size we report
     // to the element.
     ScaleDisplayByAspectRatio(displaySize, mTheoraState->mPixelAspectRatio);
 
     nsIntSize frameSize(mTheoraState->mInfo.frame_width,
                         mTheoraState->mInfo.frame_height);
-    if (VideoInfo::ValidateVideoRegion(frameSize, picture, displaySize)) {
+    if (IsValidVideoRegion(frameSize, picture, displaySize)) {
       // Video track's frame sizes will not overflow. Activate the video track.
       mInfo.mVideo.mHasVideo = true;
       mInfo.mVideo.mDisplay = displaySize;
       mPicture = picture;
 
       VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
       if (container) {
         container->SetCurrentFrame(gfxIntSize(displaySize.width, displaySize.height),
--- a/content/media/omx/MediaOmxReader.cpp
+++ b/content/media/omx/MediaOmxReader.cpp
@@ -141,17 +141,17 @@ nsresult MediaOmxReader::ReadMetadata(Me
     int32_t width, height;
     mOmxDecoder->GetVideoParameters(&width, &height);
     nsIntRect pictureRect(0, 0, width, height);
 
     // Validate the container-reported frame and pictureRect sizes. This ensures
     // that our video frame creation code doesn't overflow.
     nsIntSize displaySize(width, height);
     nsIntSize frameSize(width, height);
-    if (!VideoInfo::ValidateVideoRegion(frameSize, pictureRect, displaySize)) {
+    if (!IsValidVideoRegion(frameSize, pictureRect, displaySize)) {
       return NS_ERROR_FAILURE;
     }
 
     // Video track's frame sizes will not overflow. Activate the video track.
     mHasVideo = mInfo.mVideo.mHasVideo = true;
     mInfo.mVideo.mDisplay = displaySize;
     mPicture = pictureRect;
     mInitialFrame = frameSize;
--- a/content/media/plugins/MediaPluginReader.cpp
+++ b/content/media/plugins/MediaPluginReader.cpp
@@ -68,17 +68,17 @@ nsresult MediaPluginReader::ReadMetadata
     int32_t width, height;
     mPlugin->GetVideoParameters(mPlugin, &width, &height);
     nsIntRect pictureRect(0, 0, width, height);
 
     // Validate the container-reported frame and pictureRect sizes. This ensures
     // that our video frame creation code doesn't overflow.
     nsIntSize displaySize(width, height);
     nsIntSize frameSize(width, height);
-    if (!VideoInfo::ValidateVideoRegion(frameSize, pictureRect, displaySize)) {
+    if (!IsValidVideoRegion(frameSize, pictureRect, displaySize)) {
       return NS_ERROR_FAILURE;
     }
 
     // Video track's frame sizes will not overflow. Activate the video track.
     mHasVideo = mInfo.mVideo.mHasVideo = true;
     mInfo.mVideo.mDisplay = displaySize;
     mPicture = pictureRect;
     mInitialFrame = frameSize;
--- a/content/media/raw/RawReader.cpp
+++ b/content/media/raw/RawReader.cpp
@@ -65,17 +65,17 @@ nsresult RawReader::ReadMetadata(MediaIn
 
   // Determine and verify frame display size.
   float pixelAspectRatio = static_cast<float>(mMetadata.aspectNumerator) / 
                             mMetadata.aspectDenominator;
   nsIntSize display(mMetadata.frameWidth, mMetadata.frameHeight);
   ScaleDisplayByAspectRatio(display, pixelAspectRatio);
   mPicture = nsIntRect(0, 0, mMetadata.frameWidth, mMetadata.frameHeight);
   nsIntSize frameSize(mMetadata.frameWidth, mMetadata.frameHeight);
-  if (!VideoInfo::ValidateVideoRegion(frameSize, mPicture, display)) {
+  if (!IsValidVideoRegion(frameSize, mPicture, display)) {
     // Video track's frame sizes will overflow. Fail.
     return NS_ERROR_FAILURE;
   }
 
   mInfo.mVideo.mHasVideo = true;
   mInfo.mVideo.mDisplay = display;
 
   mFrameRate = static_cast<float>(mMetadata.framerateNumerator) /
--- a/content/media/webm/WebMReader.cpp
+++ b/content/media/webm/WebMReader.cpp
@@ -324,17 +324,17 @@ nsresult WebMReader::ReadMetadata(MediaI
         pictureRect.width = params.width;
         pictureRect.height = params.height;
       }
 
       // Validate the container-reported frame and pictureRect sizes. This ensures
       // that our video frame creation code doesn't overflow.
       nsIntSize displaySize(params.display_width, params.display_height);
       nsIntSize frameSize(params.width, params.height);
-      if (!VideoInfo::ValidateVideoRegion(frameSize, pictureRect, displaySize)) {
+      if (!IsValidVideoRegion(frameSize, pictureRect, displaySize)) {
         // Video track's frame sizes will overflow. Ignore the video track.
         continue;
       }
 
       mVideoTrack = track;
       mHasVideo = true;
       mInfo.mVideo.mHasVideo = true;
 
--- a/content/media/wmf/WMFReader.cpp
+++ b/content/media/wmf/WMFReader.cpp
@@ -309,17 +309,17 @@ WMFReader::ConfigureVideoFrameGeometry(I
                            &aspectDenom);
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   // Calculate and validate the picture region and frame dimensions after
   // scaling by the pixel aspect ratio.
   nsIntSize frameSize = nsIntSize(width, height);
   nsIntSize displaySize = nsIntSize(pictureRegion.width, pictureRegion.height);
   ScaleDisplayByAspectRatio(displaySize, float(aspectNum) / float(aspectDenom));
-  if (!VideoInfo::ValidateVideoRegion(frameSize, pictureRegion, displaySize)) {
+  if (!IsValidVideoRegion(frameSize, pictureRegion, displaySize)) {
     // Video track's frame sizes will overflow. Ignore the video track.
     return E_FAIL;
   }
 
   // Success! Save state.
   mInfo.mVideo.mDisplay = displaySize;
   GetDefaultStride(aMediaType, &mVideoStride);
   mVideoWidth = width;
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -6776,19 +6776,25 @@ nsDocShell::OnRedirectStateChange(nsICha
     // check if the new load should go through the application cache.
     nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
         do_QueryInterface(aNewChannel);
     if (appCacheChannel) {
         if (GeckoProcessType_Default != XRE_GetProcessType()) {
             // Permission will be checked in the parent process.
             appCacheChannel->SetChooseApplicationCache(true);
         } else {
-            appCacheChannel->SetChooseApplicationCache(
-                                NS_ShouldCheckAppCache(newURI,
-                                                       mInPrivateBrowsing));
+            nsCOMPtr<nsIScriptSecurityManager> secMan =
+                do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+
+            if (secMan) {
+                nsCOMPtr<nsIPrincipal> principal;
+                secMan->GetDocShellCodebasePrincipal(newURI, this, getter_AddRefs(principal));
+                appCacheChannel->SetChooseApplicationCache(NS_ShouldCheckAppCache(principal,
+                    mInPrivateBrowsing));
+            }
         }
     }
 
     if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) && 
         mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) {
         mLoadType = LOAD_NORMAL_REPLACE;
         SetHistoryEntry(&mLSHE, nullptr);
     }
@@ -9708,18 +9714,25 @@ nsDocShell::DoURILoad(nsIURI * aURI,
         appCacheChannel->SetInheritApplicationCache(false);
 
         // Loads with the correct permissions should check for a matching
         // application cache.
         if (GeckoProcessType_Default != XRE_GetProcessType()) {
             // Permission will be checked in the parent process
             appCacheChannel->SetChooseApplicationCache(true);
         } else {
-            appCacheChannel->SetChooseApplicationCache(
-                NS_ShouldCheckAppCache(aURI, mInPrivateBrowsing));
+            nsCOMPtr<nsIScriptSecurityManager> secMan =
+                do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+
+            if (secMan) {
+                nsCOMPtr<nsIPrincipal> principal;
+                secMan->GetDocShellCodebasePrincipal(aURI, this, getter_AddRefs(principal));
+                appCacheChannel->SetChooseApplicationCache(
+                    NS_ShouldCheckAppCache(principal, mInPrivateBrowsing));
+            }
         }
     }
 
     // Make sure to give the caller a channel if we managed to create one
     // This is important for correct error page/session history interaction
     if (aRequest)
         NS_ADDREF(*aRequest = channel);
 
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -1570,19 +1570,18 @@ this.DOMApplicationRegistry = {
       DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
         app: {
           downloading: true,
           installState: aApp.installState,
           progress: 0
         },
         manifestURL: aApp.manifestURL
       });
-      let cacheUpdate = aProfileDir
-        ? updateSvc.scheduleCustomProfileUpdate(appcacheURI, docURI, aProfileDir)
-        : updateSvc.scheduleAppUpdate(appcacheURI, docURI, aApp.localId, false);
+      let cacheUpdate = updateSvc.scheduleAppUpdate(
+        appcacheURI, docURI, aApp.localId, false, aProfileDir);
 
       // We save the download details for potential further usage like
       // cancelling it.
       let download = {
         cacheUpdate: cacheUpdate,
         appId: this._appIdForManifestURL(aApp.manifestURL),
         previousState: aIsUpdate ? "installed" : "pending"
       };
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -200,16 +200,17 @@ static const DOMJSClass Class = {
     %s, /* enumerate */
     %s, /* resolve */
     JS_ConvertStub,
     %s, /* finalize */
     %s, /* call */
     nullptr,               /* hasInstance */
     nullptr,               /* construct */
     %s, /* trace */
+    JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     JS_NULL_OBJECT_OPS
   },
 %s
 };
 """ % (self.descriptor.interface.identifier.name,
        classFlags,
        ADDPROPERTY_HOOK_NAME if wantsAddProperty(self.descriptor) else 'JS_PropertyStub',
--- a/dom/identity/DOMIdentity.jsm
+++ b/dom/identity/DOMIdentity.jsm
@@ -216,16 +216,20 @@ this.DOMIdentity = {
 
   /*
    * Get the RPWatchContext object for a given message manager.
    */
   getContextForMM: function(targetMM) {
     return this._serviceContexts.get(this._mmContexts.get(targetMM));
   },
 
+  hasContextForMM: function(targetMM) {
+    return this._mmContexts.has(targetMM);
+  },
+
   /*
    * Delete the RPWatchContext object for a given message manager.  Removes the
    * mapping both from _serviceContexts and _mmContexts.
    */
   deleteContextForMM: function(targetMM) {
     this._serviceContexts.delete(this._mmContexts.get(targetMM));
     this._mmContexts.delete(targetMM);
   },
@@ -339,16 +343,20 @@ this.DOMIdentity = {
   },
 
   _logout: function DOMIdentity__logout(message) {
     log("logout " + message + "\n");
     this.getService(message).RP.logout(message.id, message.origin, message);
   },
 
   _childProcessShutdown: function DOMIdentity__childProcessShutdown(targetMM) {
+    if (!this.hasContextForMM(targetMM)) {
+      return;
+    }
+
     this.getContextForMM(targetMM).RP.childProcessShutdown(targetMM);
     this.deleteContextForMM(targetMM);
 
     let options = makeMessageObject({messageManager: targetMM, id: null, origin: null});
     Services.obs.notifyObservers({wrappedJSObject: options}, "identity-child-process-shutdown", null);
   },
 
   _beginProvisioning: function DOMIdentity__beginProvisioning(message, targetMM) {
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -139,8 +139,9 @@ UseOfCaptureEventsWarning=Use of capture
 # LOCALIZATION NOTE: Do not translate "releaseEvents()" or "removeEventListener()"
 UseOfReleaseEventsWarning=Use of releaseEvents() is deprecated. To upgrade your code, use the DOM 2 removeEventListener() method. For more help http://developer.mozilla.org/en/docs/DOM:element.removeEventListener
 # LOCALIZATION NOTE: Do not translate "document.load()" or "XMLHttpRequest"
 UseOfDOM3LoadMethodWarning=Use of document.load() is deprecated. To upgrade your code, use the DOM XMLHttpRequest object. For more help https://developer.mozilla.org/en/XMLHttpRequest
 # LOCALIZATION NOTE: Do not translate "window.showModalDialog()" or "window.open()" 
 ShowModalDialogWarning=Use of window.showModalDialog() is deprecated. Use window.open() instead. For more help https://developer.mozilla.org/en-US/docs/Web/API/Window.open
 # LOCALIZATION NOTE: Do not translate "window._content" or "window.content"
 Window_ContentWarning=window._content is deprecated.  Please use window.content instead.
+ImplicitMetaViewportTagFallback=No meta-viewport tag found. Please explicitly specify one to prevent unexpected behavioural changes in future versions. For more help https://developer.mozilla.org/en/docs/Mozilla/Mobile/Viewport_meta_tag
--- a/dom/src/json/nsJSON.cpp
+++ b/dom/src/json/nsJSON.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 sw=2 et tw=79: */
 /* 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 "jsapi.h"
+#include "js/CharacterEncoding.h"
 #include "js/OldDebugAPI.h"
 #include "nsJSON.h"
 #include "nsIXPConnect.h"
 #include "nsIXPCScriptable.h"
 #include "nsStreamUtils.h"
 #include "nsIInputStream.h"
 #include "nsStringStream.h"
 #include "mozilla/dom/EncodingUtils.h"
@@ -519,18 +520,18 @@ nsJSONListener::OnStopRequest(nsIRequest
   if (!mSniffBuffer.IsEmpty()) {
     // Just consume mSniffBuffer
     rv = ProcessBytes(nullptr, 0);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   JS::Rooted<JS::Value> reviver(mCx, JS::NullValue()), value(mCx);
 
-  JS::StableCharPtr chars(reinterpret_cast<const jschar*>(mBufferedChars.Elements()),
-                          mBufferedChars.Length());
+  JS::ConstTwoByteChars chars(reinterpret_cast<const jschar*>(mBufferedChars.Elements()),
+                              mBufferedChars.Length());
   bool ok = JS_ParseJSONWithReviver(mCx, chars.get(),
                                       uint32_t(mBufferedChars.Length()),
                                       reviver, &value);
 
   *mRootVal = value;
   mBufferedChars.TruncateLength(0);
   return ok ? NS_OK : NS_ERROR_FAILURE;
 }
--- a/dom/tests/mochitest/dom-level0/mochitest.ini
+++ b/dom/tests/mochitest/dom-level0/mochitest.ini
@@ -1,9 +1,11 @@
 [DEFAULT]
+# Timeouts on all platforms (bug 932350)
+skip-if = true
 support-files =
   child_ip_address.html
   file_crossdomainprops_inner.html
   file_location.html
   framed_location.html
   idn_child.html
   innerWidthHeight_script.html
 
--- a/dom/tests/mochitest/dom-level1-core/mochitest.ini
+++ b/dom/tests/mochitest/dom-level1-core/mochitest.ini
@@ -1,11 +1,11 @@
 [DEFAULT]
-# Timeouts on OS X (bug 921635)
-skip-if = os == "mac"
+# Timeouts on all platforms (bug 932350)
+skip-if = true
 support-files =
   DOMTestCase.js
   activity-home.css
   exclusions.js
   files/hc_nodtdstaff.html
   files/hc_nodtdstaff.xhtml
   files/hc_staff.html
   files/hc_staff.xhtml
--- a/dom/tests/mochitest/dom-level2-core/mochitest.ini
+++ b/dom/tests/mochitest/dom-level2-core/mochitest.ini
@@ -1,11 +1,11 @@
 [DEFAULT]
-# Timeouts on OS X (bug 921635)
-skip-if = os == "mac"
+# Timeouts on all platforms (bug 932350)
+skip-if = true
 support-files =
   DOMTestCase.js
   exclusions.js
   files/hc_staff.html
   files/hc_staff.svg
   files/hc_staff.xhtml
   files/hc_staff.xml
   files/internalSubset01.js
--- a/dom/tests/mochitest/dom-level2-html/mochitest.ini
+++ b/dom/tests/mochitest/dom-level2-html/mochitest.ini
@@ -1,11 +1,11 @@
 [DEFAULT]
-# Timeouts on OS X (bug 921635)
-skip-if = os == "mac"
+# Timeouts on all platforms (bug 932350)
+skip-if = true
 support-files =
   DOMTestCase.js
   files/anchor.html
   files/anchor.xhtml
   files/anchor.xml
   files/anchor2.html
   files/anchor2.xhtml
   files/anchor2.xml
--- a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp
+++ b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp
@@ -60,21 +60,35 @@ MacIOSurfaceTextureClientOGL::ToSurfaceD
 }
 
 gfx::IntSize
 MacIOSurfaceTextureClientOGL::GetSize() const
 {
   return gfx::IntSize(mSurface->GetDevicePixelWidth(), mSurface->GetDevicePixelHeight());
 }
 
+class MacIOSurfaceTextureClientData : public TextureClientData
+{
+public:
+  MacIOSurfaceTextureClientData(MacIOSurface* aSurface)
+    : mSurface(aSurface)
+  {}
+
+  virtual void DeallocateSharedData(ISurfaceAllocator*) MOZ_OVERRIDE
+  {
+    mSurface = nullptr;
+  }
+
+private:
+  RefPtr<MacIOSurface> mSurface;
+};
+
 TextureClientData*
 MacIOSurfaceTextureClientOGL::DropTextureData()
 {
-  // MacIOSurface has proper cross-process refcounting so we can just drop
-  // our reference now, and the data will stay alive (at least) until the host
-  // has also been torn down.
+  TextureClientData* data = new MacIOSurfaceTextureClientData(mSurface);
   mSurface = nullptr;
   MarkInvalid();
-  return nullptr;
+  return data;
 }
 
 }
 }
--- a/gfx/thebes/gfx3DMatrix.cpp
+++ b/gfx/thebes/gfx3DMatrix.cpp
@@ -783,20 +783,19 @@ gfxPoint gfx3DMatrix::ProjectPoint(const
   return result;
 }
 
 gfxRect gfx3DMatrix::ProjectRectBounds(const gfxRect& aRect) const
 {
   gfxPoint points[4];
 
   points[0] = ProjectPoint(aRect.TopLeft());
-  points[1] = ProjectPoint(gfxPoint(aRect.X() + aRect.Width(), aRect.Y()));
-  points[2] = ProjectPoint(gfxPoint(aRect.X(), aRect.Y() + aRect.Height()));
-  points[3] = ProjectPoint(gfxPoint(aRect.X() + aRect.Width(),
-                                    aRect.Y() + aRect.Height()));
+  points[1] = ProjectPoint(aRect.TopRight());
+  points[2] = ProjectPoint(aRect.BottomLeft());
+  points[3] = ProjectPoint(aRect.BottomRight());
 
   gfxFloat min_x, max_x;
   gfxFloat min_y, max_y;
 
   min_x = max_x = points[0].x;
   min_y = max_y = points[0].y;
 
   for (int i=1; i<4; i++) {
@@ -804,16 +803,44 @@ gfxRect gfx3DMatrix::ProjectRectBounds(c
     max_x = max(points[i].x, max_x);
     min_y = min(points[i].y, min_y);
     max_y = max(points[i].y, max_y);
   }
 
   return gfxRect(min_x, min_y, max_x - min_x, max_y - min_y);
 }
 
+gfxRect gfx3DMatrix::UntransformBounds(const gfxRect& aRect, const gfxRect& aChildBounds) const
+{
+  if (Is2D()) {
+    return Inverse().TransformBounds(aRect);
+  }
+  gfxRect bounds = TransformBounds(aChildBounds);
+
+  gfxRect rect = aRect.Intersect(bounds);
+
+  return Inverse().ProjectRectBounds(rect);
+}
+
+bool gfx3DMatrix::UntransformPoint(const gfxPoint& aPoint, const gfxRect& aChildBounds, gfxPoint* aOut) const
+{
+  if (Is2D()) {
+    *aOut = Inverse().Transform(aPoint);
+    return true;
+  }
+  gfxRect bounds = TransformBounds(aChildBounds);
+
+  if (!bounds.Contains(aPoint)) {
+    return false;
+  }
+
+  *aOut = Inverse().ProjectPoint(aPoint);
+  return true;
+}
+
 gfxPoint3D gfx3DMatrix::GetNormalVector() const
 {
   // Define a plane in transformed space as the transformations
   // of 3 points on the z=0 screen plane.
   gfxPoint3D a = Transform3D(gfxPoint3D(0, 0, 0));
   gfxPoint3D b = Transform3D(gfxPoint3D(0, 1, 0));
   gfxPoint3D c = Transform3D(gfxPoint3D(1, 0, 0));
 
--- a/gfx/thebes/gfx3DMatrix.h
+++ b/gfx/thebes/gfx3DMatrix.h
@@ -245,16 +245,35 @@ public:
    */
   gfxPoint3D Transform3D(const gfxPoint3D& point) const;
   gfxPointH3D Transform4D(const gfxPointH3D& aPoint) const;
   gfxPointH3D TransposeTransform4D(const gfxPointH3D& aPoint) const;
 
   gfxPoint ProjectPoint(const gfxPoint& aPoint) const;
   gfxRect ProjectRectBounds(const gfxRect& aRect) const;
 
+  /**
+   * Transforms a point by the inverse of this matrix. In the case of perspective transforms, some screen
+   * points have no equivalent in the untransformed plane (if they exist past the vanishing point). To
+   * avoid this, we need to specify the bounds of the untransformed plane to restrict the search area.
+   *
+   * @param aPoint Point to untransform.
+   * @param aChildBounds Bounds of the untransformed plane.
+   * @param aOut Untransformed point.
+   * @return Returns true if a point was found within a ChildBounds, false otherwise.
+   */
+  bool UntransformPoint(const gfxPoint& aPoint, const gfxRect& aChildBounds, gfxPoint* aOut) const;
+
+
+  /**
+   * Same as UntransformPoint, but untransforms a rect and returns the bounding rect of the result.
+   * Returns an empty rect if the result doesn't intersect aChildBounds.
+   */
+  gfxRect UntransformBounds(const gfxRect& aRect, const gfxRect& aChildBounds) const;
+
 
   /**
    * Inverts this matrix, if possible. Otherwise, the matrix is left
    * unchanged.
    */
   gfx3DMatrix Inverse() const;
 
   gfx3DMatrix& Invert()
--- a/intl/icu/source/config/mh-darwin
+++ b/intl/icu/source/config/mh-darwin
@@ -23,21 +23,17 @@ ARFLAGS += -c
 COMPILE.c=	$(CC) $(DEFS) $(CPPFLAGS) $(CFLAGS) -fno-common -c
 COMPILE.cc=	$(CXX) $(DEFS) $(CPPFLAGS) $(CXXFLAGS) -fno-common -c
 
 ## Commands to make a shared library
 SHLIB.c=	$(CC) -dynamiclib -dynamic $(CFLAGS) $(LDFLAGS) $(LD_SOOPTIONS)
 SHLIB.cc=	$(CXX) -dynamiclib -dynamic $(CXXFLAGS) $(LDFLAGS) $(LD_SOOPTIONS)
 
 ## Compiler switches to embed a library name and version information
-ifeq ($(ENABLE_RPATH),YES)
-LD_SONAME = -Wl,-compatibility_version -Wl,$(SO_TARGET_VERSION_MAJOR) -Wl,-current_version -Wl,$(SO_TARGET_VERSION) -install_name $(libdir)/$(notdir $(MIDDLE_SO_TARGET))
-else
-LD_SONAME = -Wl,-compatibility_version -Wl,$(SO_TARGET_VERSION_MAJOR) -Wl,-current_version -Wl,$(SO_TARGET_VERSION) -install_name $(notdir $(MIDDLE_SO_TARGET))
-endif
+LD_SONAME = -Wl,-compatibility_version -Wl,$(SO_TARGET_VERSION_MAJOR) -Wl,-current_version -Wl,$(SO_TARGET_VERSION) -install_name @executable_path/$(notdir $(MIDDLE_SO_TARGET))
 
 ## Compiler switch to embed a runtime search path
 LD_RPATH=
 LD_RPATH_PRE= -Wl,-rpath,
 
 ## Environment variable to set a runtime search path
 LDLIBRARYPATH_ENVVAR = DYLD_LIBRARY_PATH
 
--- a/intl/update-icu.sh
+++ b/intl/update-icu.sh
@@ -47,12 +47,13 @@ rm ${icu_dir}/source/data/translit/*
 # repository (not just the path within it we care about) receives a commit.
 # (This ensures that if ICU modifications are performed properly, it's always
 # possible to run the command at the top of this script and make no changes to
 # the tree.)
 svn info $1 | grep -v '^Revision: [[:digit:]]\+$' > ${icu_dir}/SVN-INFO
 
 patch -d ${icu_dir}/../../ -p1 < ${icu_dir}/../icu-patches/bug-724533
 patch -d ${icu_dir}/../../ -p1 < ${icu_dir}/../icu-patches/bug-899722-4
+patch -d ${icu_dir}/../../ -p1 < ${icu_dir}/../icu-patches/bug-915735
 patch -d ${icu_dir}/../../ -p1 < ${icu_dir}/../icu-patches/genrb-omitCollationRules.diff
 patch -d ${icu_dir}/../../ -p1 < ${icu_dir}/../icu-patches/qualify-uinitonce-windows.diff
 
 hg addremove ${icu_dir}
--- a/js/public/CharacterEncoding.h
+++ b/js/public/CharacterEncoding.h
@@ -119,31 +119,16 @@ class TwoByteChars : public mozilla::Ran
 
   public:
     TwoByteChars() : Base() {}
     TwoByteChars(jschar *aChars, size_t aLength) : Base(aChars, aLength) {}
     TwoByteChars(const jschar *aChars, size_t aLength) : Base(const_cast<jschar *>(aChars), aLength) {}
 };
 
 /*
- * A non-convertible variant of TwoByteChars that does not refer to characters
- * inlined inside a JSShortString or a JSInlineString. StableTwoByteChars are
- * thus safe to hold across a GC.
- */
-class StableTwoByteChars : public mozilla::Range<jschar>
-{
-    typedef mozilla::Range<jschar> Base;
-
-  public:
-    StableTwoByteChars() : Base() {}
-    StableTwoByteChars(jschar *aChars, size_t aLength) : Base(aChars, aLength) {}
-    StableTwoByteChars(const jschar *aChars, size_t aLength) : Base(const_cast<jschar *>(aChars), aLength) {}
-};
-
-/*
  * A TwoByteChars, but \0 terminated for compatibility with JSFlatString.
  */
 class TwoByteCharsZ : public mozilla::RangedPtr<jschar>
 {
     typedef mozilla::RangedPtr<jschar> Base;
 
   public:
     TwoByteCharsZ() : Base(nullptr, 0) {}
@@ -152,16 +137,35 @@ class TwoByteCharsZ : public mozilla::Ra
       : Base(chars, length)
     {
         JS_ASSERT(chars[length] == '\0');
     }
 
     using Base::operator=;
 };
 
+typedef mozilla::RangedPtr<const jschar> ConstCharPtr;
+
+/*
+ * Like TwoByteChars, but the chars are const.
+ */
+class ConstTwoByteChars : public mozilla::RangedPtr<const jschar>
+{
+  public:
+    ConstTwoByteChars(const ConstTwoByteChars &s) : ConstCharPtr(s) {}
+    ConstTwoByteChars(const mozilla::RangedPtr<const jschar> &s) : ConstCharPtr(s) {}
+    ConstTwoByteChars(const jschar *s, size_t len) : ConstCharPtr(s, len) {}
+    ConstTwoByteChars(const jschar *pos, const jschar *start, size_t len)
+      : ConstCharPtr(pos, start, len)
+    {}
+
+    using ConstCharPtr::operator=;
+};
+
+
 /*
  * Convert a 2-byte character sequence to "ISO-Latin-1". This works by
  * truncating each 2-byte pair in the sequence to a 1-byte pair. If the source
  * contains any UTF-16 extension characters, then this may give invalid Latin1
  * output. The returned string is zero terminated. The returned string or the
  * returned string's |start()| must be freed with JS_free or js_free,
  * respectively. If allocation fails, an OOM error will be set and the method
  * will return a nullptr chars (which can be tested for with the ! operator).
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -20,16 +20,17 @@
 /*
  * A JSClass acts as a vtable for JS objects that allows JSAPI clients to
  * control various aspects of the behavior of an object like property lookup.
  * js::Class is an engine-private extension that allows more control over
  * object behavior and, e.g., allows custom slow layout.
  */
 
 class JSFreeOp;
+struct JSFunctionSpec;
 
 namespace js {
 
 class Class;
 class FreeOp;
 class PropertyId;
 class PropertyName;
 class Shape;
@@ -411,16 +412,33 @@ typedef void
  * The helper struct to measure the size of JS_CLASS_MEMBERS to know how much
  * we have to pad js::Class to match the size of JSClass.
  */
 struct ClassSizeMeasurement
 {
     JS_CLASS_MEMBERS;
 };
 
+// Callback for the creation of constructor and prototype objects.
+typedef JSObject *(*ClassObjectCreationOp)(JSContext *cx, JSProtoKey key);
+
+// Callback for custom post-processing after class initialization via ClassSpec.
+typedef bool (*FinishClassInitOp)(JSContext *cx, JS::HandleObject ctor,
+                                  JS::HandleObject proto);
+
+struct ClassSpec
+{
+    ClassObjectCreationOp createConstructor;
+    ClassObjectCreationOp createPrototype;
+    const JSFunctionSpec *constructorFunctions;
+    const JSFunctionSpec *prototypeFunctions;
+    FinishClassInitOp finishInit;
+    bool defined() const { return !!createConstructor; }
+};
+
 struct ClassExtension
 {
     JSObjectOp          outerObject;
     JSObjectOp          innerObject;
     JSIteratorOp        iteratorObject;
 
     /*
      * isWrappedNative is true only if the class is an XPCWrappedNative.
@@ -437,16 +455,17 @@ struct ClassExtension
      * that case, the wrapped object is returned by the wrapper's
      * weakmapKeyDelegateOp hook. As long as the wrapper is used as a weakmap
      * key, it will not be collected (and remain in the weakmap) until the
      * wrapped object is collected.
      */
     JSWeakmapKeyDelegateOp weakmapKeyDelegateOp;
 };
 
+#define JS_NULL_CLASS_SPEC  {nullptr,nullptr,nullptr,nullptr,nullptr}
 #define JS_NULL_CLASS_EXT   {nullptr,nullptr,nullptr,false,nullptr}
 
 struct ObjectOps
 {
     LookupGenericOp     lookupGeneric;
     LookupPropOp        lookupProperty;
     LookupElementOp     lookupElement;
     LookupSpecialOp     lookupSpecial;
@@ -587,19 +606,21 @@ struct JSClass {
 #define JSCLASS_NO_INTERNAL_MEMBERS     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
 #define JSCLASS_NO_OPTIONAL_MEMBERS     0,0,0,0,0,JSCLASS_NO_INTERNAL_MEMBERS
 
 namespace js {
 
 struct Class
 {
     JS_CLASS_MEMBERS;
+    ClassSpec          spec;
     ClassExtension      ext;
     ObjectOps           ops;
     uint8_t             pad[sizeof(JSClass) - sizeof(ClassSizeMeasurement) -
+                            sizeof(ClassSpec) -
                             sizeof(ClassExtension) - sizeof(ObjectOps)];
 
     /* Class is not native and its map is not a scope. */
     static const uint32_t NON_NATIVE = JSCLASS_INTERNAL_FLAG2;
 
     bool isNative() const {
         return !(flags & NON_NATIVE);
     }
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -827,22 +827,16 @@ class MOZ_STACK_CLASS Rooted : public js
      */
     T ptr;
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
     Rooted(const Rooted &) MOZ_DELETE;
 };
 
-#if !(defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING))
-// Defined in vm/String.h.
-template <>
-class Rooted<JSStableString *>;
-#endif
-
 } /* namespace JS */
 
 namespace js {
 
 /*
  * Mark a stack location as a root for the rooting analysis, without actually
  * rooting it in release builds. This should only be used for stack locations
  * of GC things that cannot be relocated by a garbage collection, and that
--- a/js/src/NamespaceImports.h
+++ b/js/src/NamespaceImports.h
@@ -16,17 +16,17 @@
 #include "js/TypeDecls.h"
 #include "js/Value.h"
 
 // ... but we do forward declarations of the structs and classes not pulled in
 // by the headers included above.
 namespace JS {
 
 class Latin1CharsZ;
-class StableCharPtr;
+class ConstTwoByteChars;
 class TwoByteChars;
 
 class AutoFunctionVector;
 class AutoIdVector;
 class AutoObjectVector;
 class AutoScriptVector;
 class AutoValueVector;
 
@@ -57,17 +57,17 @@ using JS::ObjectValue;
 using JS::PrivateUint32Value;
 using JS::PrivateValue;
 using JS::StringValue;
 using JS::UndefinedValue;
 
 using JS::IsPoisonedPtr;
 
 using JS::Latin1CharsZ;
-using JS::StableCharPtr;
+using JS::ConstTwoByteChars;
 using JS::TwoByteChars;
 
 using JS::AutoFunctionVector;
 using JS::AutoIdVector;
 using JS::AutoObjectVector;
 using JS::AutoScriptVector;
 using JS::AutoValueVector;
 
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -143,17 +143,17 @@ class EvalScriptGuard
 enum EvalJSONResult {
     EvalJSON_Failure,
     EvalJSON_Success,
     EvalJSON_NotJSON
 };
 
 static EvalJSONResult
 TryEvalJSON(JSContext *cx, JSScript *callerScript,
-            StableCharPtr chars, size_t length, MutableHandleValue rval)
+            ConstTwoByteChars chars, size_t length, MutableHandleValue rval)
 {
     // If the eval string starts with '(' or '[' and ends with ')' or ']', it may be JSON.
     // Try the JSON parser first because it's much faster.  If the eval string
     // isn't JSON, JSON parsing will probably fail quickly, so little time
     // will be lost.
     //
     // Don't use the JSON parser if the caller is strict mode code, because in
     // strict mode object literals must not have repeated properties, and the
@@ -275,34 +275,34 @@ EvalKernel(JSContext *cx, const CallArgs
 
         // Use the global as 'this', modulo outerization.
         JSObject *thisobj = JSObject::thisObject(cx, scopeobj);
         if (!thisobj)
             return false;
         thisv = ObjectValue(*thisobj);
     }
 
-    Rooted<JSStableString*> stableStr(cx, str->ensureStable(cx));
-    if (!stableStr)
+    Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
+    if (!flatStr)
         return false;
 
-    StableCharPtr chars = stableStr->chars();
-    size_t length = stableStr->length();
+    size_t length = flatStr->length();
+    ConstTwoByteChars chars(flatStr->chars(), length);
 
     JSPrincipals *principals = PrincipalsForCompiledCode(args, cx);
 
     RootedScript callerScript(cx, caller ? caller.script() : nullptr);
     EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, args.rval());
     if (ejr != EvalJSON_NotJSON)
         return ejr == EvalJSON_Success;
 
     EvalScriptGuard esg(cx);
 
     if (evalType == DIRECT_EVAL && caller.isNonEvalFunctionFrame())
-        esg.lookupInEvalCache(stableStr, callerScript, pc);
+        esg.lookupInEvalCache(flatStr, callerScript, pc);
 
     if (!esg.foundScript()) {
         unsigned lineno;
         const char *filename;
         JSPrincipals *originPrincipals;
         CurrentScriptFileLineOrigin(cx, &filename, &lineno, &originPrincipals,
                                     evalType == DIRECT_EVAL ? CALLED_FROM_JSOP_EVAL
                                                             : NOT_CALLED_FROM_JSOP_EVAL);
@@ -311,17 +311,17 @@ EvalKernel(JSContext *cx, const CallArgs
         options.setFileAndLine(filename, lineno)
                .setCompileAndGo(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setPrincipals(principals)
                .setOriginPrincipals(originPrincipals);
         JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
                                                      scopeobj, callerScript, options,
-                                                     chars.get(), length, stableStr, staticLevel);
+                                                     chars.get(), length, flatStr, staticLevel);
         if (!compiled)
             return false;
 
         MarkFunctionsWithinEvalScript(compiled);
 
         esg.setNewScript(compiled);
     }
 
@@ -342,33 +342,33 @@ js::DirectEvalStringFromIon(JSContext *c
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CSP_BLOCKED_EVAL);
         return false;
     }
 
     // ES5 15.1.2.1 steps 2-8.
 
     unsigned staticLevel = callerScript->staticLevel() + 1;
 
-    Rooted<JSStableString*> stableStr(cx, str->ensureStable(cx));
-    if (!stableStr)
+    Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
+    if (!flatStr)
         return false;
 
-    StableCharPtr chars = stableStr->chars();
-    size_t length = stableStr->length();
+    size_t length = flatStr->length();
+    ConstTwoByteChars chars(flatStr->chars(), length);
 
     EvalJSONResult ejr = TryEvalJSON(cx, callerScript, chars, length, vp);
     if (ejr != EvalJSON_NotJSON)
         return ejr == EvalJSON_Success;
 
     EvalScriptGuard esg(cx);
 
     // Ion will not perform cross compartment direct eval calls.
     JSPrincipals *principals = cx->compartment()->principals;
 
-    esg.lookupInEvalCache(stableStr, callerScript, pc);
+    esg.lookupInEvalCache(flatStr, callerScript, pc);
 
     if (!esg.foundScript()) {
         unsigned lineno;
         const char *filename;
         JSPrincipals *originPrincipals;
         CurrentScriptFileLineOrigin(cx, &filename, &lineno, &originPrincipals,
                                     CALLED_FROM_JSOP_EVAL);
 
@@ -376,17 +376,17 @@ js::DirectEvalStringFromIon(JSContext *c
         options.setFileAndLine(filename, lineno)
                .setCompileAndGo(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setPrincipals(principals)
                .setOriginPrincipals(originPrincipals);
         JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
                                                      scopeobj, callerScript, options,
-                                                     chars.get(), length, stableStr, staticLevel);
+                                                     chars.get(), length, flatStr, staticLevel);
         if (!compiled)
             return false;
 
         MarkFunctionsWithinEvalScript(compiled);
 
         esg.setNewScript(compiled);
     }
 
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1308,16 +1308,17 @@ const Class CloneBufferObject::class_ = 
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     Finalize,
     nullptr,                  /* call */
     nullptr,                  /* hasInstance */
     nullptr,                  /* construct */
     nullptr,                  /* trace */
+    JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     JS_NULL_OBJECT_OPS
 };
 
 const JSPropertySpec CloneBufferObject::props_[] = {
     JS_PSGS("clonebuffer", getCloneBuffer, setCloneBuffer, 0),
     JS_PS_END
 };
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -2266,16 +2266,17 @@ const Class TypedObject::class_ = {
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     TypedDatum::obj_finalize,
     nullptr,        /* call        */
     nullptr,        /* construct   */
     nullptr,        /* hasInstance */
     TypedDatum::obj_trace,
+    JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     {
         TypedDatum::obj_lookupGeneric,
         TypedDatum::obj_lookupProperty,
         TypedDatum::obj_lookupElement,
         TypedDatum::obj_lookupSpecial,
         TypedDatum::obj_defineGeneric,
         TypedDatum::obj_defineProperty,
@@ -2433,16 +2434,17 @@ const Class TypedHandle::class_ = {
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     TypedDatum::obj_finalize,
     nullptr,        /* call        */
     nullptr,        /* construct   */
     nullptr,        /* hasInstance */
     TypedDatum::obj_trace,
+    JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     {
         TypedDatum::obj_lookupGeneric,
         TypedDatum::obj_lookupProperty,
         TypedDatum::obj_lookupElement,
         TypedDatum::obj_lookupSpecial,
         TypedDatum::obj_defineGeneric,
         TypedDatum::obj_defineProperty,
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -4815,34 +4815,31 @@ StructType::DefineInternal(JSContext* cx
     structAlign = 0;
 
     for (uint32_t i = 0; i < len; ++i) {
       RootedValue item(cx);
       if (!JS_GetElement(cx, fieldsObj, i, &item))
         return false;
 
       RootedObject fieldType(cx, nullptr);
-      JSFlatString* flat = ExtractStructField(cx, item, fieldType.address());
-      if (!flat)
-        return false;
-      Rooted<JSStableString*> name(cx, flat->ensureStable(cx));
+      Rooted<JSFlatString*> name(cx, ExtractStructField(cx, item, fieldType.address()));
       if (!name)
         return false;
       fieldRoots[i] = JS::ObjectValue(*fieldType);
 
       // Make sure each field name is unique
       FieldInfoHash::AddPtr entryPtr = fields->lookupForAdd(name);
       if (entryPtr) {
         JS_ReportError(cx, "struct fields must have unique names");
         return false;
       }
 
       // Add the field to the StructType's 'prototype' property.
       if (!JS_DefineUCProperty(cx, prototype,
-             name->chars().get(), name->length(), JSVAL_VOID,
+             name->chars(), name->length(), JSVAL_VOID,
              StructType::FieldGetter, StructType::FieldSetter,
              JSPROP_SHARED | JSPROP_ENUMERATE | JSPROP_PERMANENT))
         return false;
 
       size_t fieldSize = CType::GetSize(fieldType);
       size_t fieldAlign = CType::GetAlignment(fieldType);
       size_t fieldOffset = Align(structSize, fieldAlign);
       // Check for overflow. Since we hold invariant that fieldSize % fieldAlign
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -91,17 +91,17 @@ BytecodeEmitter::BytecodeEmitter(Bytecod
     script(sc->context, script),
     prolog(sc->context, lineNum),
     main(sc->context, lineNum),
     current(&main),
     parser(parser),
     evalCaller(evalCaller),
     topStmt(nullptr),
     topScopeStmt(nullptr),
-    blockChain(sc->context),
+    staticScope(sc->context),
     atomIndices(sc->context),
     firstLine(lineNum),
     stackDepth(0), maxStackDepth(0),
     arrayCompDepth(0),
     emitLevel(0),
     constList(sc->context),
     tryNoteList(sc->context),
     blockScopeList(sc->context),
@@ -488,17 +488,17 @@ class NonLocalExitScope {
 
   public:
     explicit NonLocalExitScope(ExclusiveContext *cx_, BytecodeEmitter *bce_)
       : cx(cx_),
         bce(bce_),
         savedScopeIndex(bce->blockScopeList.length()),
         savedDepth(bce->stackDepth),
         openScopeIndex(UINT32_MAX) {
-        if (bce->blockChain) {
+        if (bce->staticScope) {
             StmtInfoBCE *stmt = bce->topStmt;
             while (1) {
                 JS_ASSERT(stmt);
                 if (stmt->isBlockScope) {
                     openScopeIndex = stmt->blockScopeIndex;
                     break;
                 }
                 stmt = stmt->down;
@@ -573,18 +573,17 @@ NonLocalExitScope::prepareForNonLocalJum
              */
             npops += 2;
             break;
 
           default:;
         }
 
         if (stmt->isBlockScope) {
-            JS_ASSERT(stmt->blockObj);
-            StaticBlockObject &blockObj = *stmt->blockObj;
+            StaticBlockObject &blockObj = stmt->staticBlock();
             if (!popScopeForNonLocalExit(blockObj, stmt->blockScopeIndex))
                 return false;
             npops += blockObj.slotCount();
         }
     }
 
     FLUSH_POPS();
     return true;
@@ -641,18 +640,18 @@ PushStatementBCE(BytecodeEmitter *bce, S
 
 /*
  * Return the enclosing lexical scope, which is the innermost enclosing static
  * block object or compiler created function.
  */
 static JSObject *
 EnclosingStaticScope(BytecodeEmitter *bce)
 {
-    if (bce->blockChain)
-        return bce->blockChain;
+    if (bce->staticScope)
+        return bce->staticScope;
 
     if (!bce->sc->isFunctionBox()) {
         JS_ASSERT(!bce->parent);
         return nullptr;
     }
 
     return bce->sc->asFunctionBox()->function();
 }
@@ -739,17 +738,17 @@ EmitInternedObjectOp(ExclusiveContext *c
 // If no variable declared in the scope is "aliased", then no scope chain node
 // is allocated.
 //
 // To help debuggers, the bytecode emitter arranges to record the PC ranges
 // comprehended by a block scope, and ultimately attach them to the JSScript.
 // An element in the "block scope array" specifies the PC range, and links to a
 // StaticBlockObject in the object list of the script.  That block is linked to
 // the previous block in the scope, if any.  The static block chain at any
-// pre-retire PC can be retrieved using JSScript::getBlockScope(jsbytecode *pc).
+// pre-retire PC can be retrieved using JSScript::getStaticScope(jsbytecode *pc).
 //
 // When PUSHBLOCKSCOPE is executed, it assumes that the block's locals are
 // already on the stack.  Initial values of "aliased" locals are copied from the
 // stack to the ClonedBlockObject, and no further access is made to the stack
 // slot.
 //
 // Likewise after leaving a POPBLOCKSCOPE, we will need to emit code to pop the
 // stack values.
@@ -778,19 +777,19 @@ EmitInternedObjectOp(ExclusiveContext *c
 // LeaveBlockScope.  Push locals before entering a scope, and pop them
 // afterwards.  Brush your teeth, and clean behind your ears!
 //
 static bool
 EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox,
                 unsigned extraSlots)
 {
     uint32_t parent = BlockScopeNote::NoBlockScopeIndex;
-    if (bce->blockChain) {
+    if (bce->staticScope) {
         StmtInfoBCE *stmt = bce->topScopeStmt;
-        for (; stmt->blockObj != bce->blockChain; stmt = stmt->down) {}
+        for (; stmt->staticScope != bce->staticScope; stmt = stmt->down) {}
         parent = stmt->blockScopeIndex;
     }
 
     Rooted<StaticBlockObject *> blockObj(cx, &objbox->object->as<StaticBlockObject>());
 
     uint32_t scopeObjectIndex = bce->objectList.add(objbox);
 
     int depth = bce->stackDepth - (blockObj->slotCount() + extraSlots);
@@ -805,17 +804,17 @@ EnterBlockScope(ExclusiveContext *cx, By
             return false;
     }
 
     stmt->blockScopeIndex = bce->blockScopeList.length();
     if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent))
         return false;
 
     PushStatementBCE(bce, stmt, STMT_BLOCK, bce->offset());
-    blockObj->initEnclosingStaticScope(EnclosingStaticScope(bce));
+    blockObj->initEnclosingNestedScope(EnclosingStaticScope(bce));
     FinishPushBlockScope(bce, stmt, *blockObj);
 
     JS_ASSERT(stmt->isBlockScope);
 
     return true;
 }
 
 // Patches |breaks| and |continues| unless the top statement info record
@@ -841,22 +840,23 @@ LeaveBlockScope(ExclusiveContext *cx, By
     StmtInfoBCE *stmt = bce->topStmt;
     JS_ASSERT(stmt->isBlockScope);
     uint32_t blockScopeIndex = stmt->blockScopeIndex;
 
 #ifdef DEBUG
     JS_ASSERT(bce->blockScopeList.list[blockScopeIndex].length == 0);
     uint32_t blockObjIndex = bce->blockScopeList.list[blockScopeIndex].index;
     ObjectBox *blockObjBox = bce->objectList.find(blockObjIndex);
-    StaticBlockObject *blockObj = &blockObjBox->object->as<StaticBlockObject>();
-    JS_ASSERT(stmt->blockObj == blockObj);
-    JS_ASSERT(blockObj == bce->blockChain);
+    NestedScopeObject *staticScope = &blockObjBox->object->as<NestedScopeObject>();
+    JS_ASSERT(stmt->staticScope == staticScope);
+    JS_ASSERT(staticScope == bce->staticScope);
 #endif
 
-    bool blockOnChain = bce->blockChain->needsClone();
+    JS_ASSERT(bce->staticScope->is<StaticBlockObject>());
+    bool blockOnChain = bce->staticScope->as<StaticBlockObject>().needsClone();
 
     if (!PopStatementBCE(cx, bce))
         return false;
 
     if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0)
         return false;
 
     bce->blockScopeList.recordEnd(blockScopeIndex, bce->offset());
@@ -993,26 +993,28 @@ EmitAliasedVarOp(ExclusiveContext *cx, J
     SET_SCOPECOORD_HOPS(pc, sc.hops());
     pc += SCOPECOORD_HOPS_LEN;
     SET_SCOPECOORD_SLOT(pc, sc.slot());
     pc += SCOPECOORD_SLOT_LEN;
     CheckTypeSet(cx, bce, op);
     return true;
 }
 
+// Compute the number of nested scope objects that will actually be on the scope
+// chain at runtime, given the BCE's current staticScope.
 static unsigned
-ClonedBlockDepth(BytecodeEmitter *bce)
-{
-    unsigned clonedBlockDepth = 0;
-    for (StaticBlockObject *b = bce->blockChain; b; b = b->enclosingBlock()) {
-        if (b->needsClone())
-            ++clonedBlockDepth;
-    }
-
-    return clonedBlockDepth;
+DynamicNestedScopeDepth(BytecodeEmitter *bce)
+{
+    unsigned depth = 0;
+    for (NestedScopeObject *b = bce->staticScope; b; b = b->enclosingNestedScope()) {
+        if (!b->is<StaticBlockObject>() || b->as<StaticBlockObject>().needsClone())
+            ++depth;
+    }
+
+    return depth;
 }
 
 static bool
 LookupAliasedName(HandleScript script, PropertyName *name, uint32_t *pslot)
 {
     /*
      * Beware: BindingIter may contain more than one Binding for a given name
      * (in the case of |function f(x,x) {}|) but only one will be aliased.
@@ -1073,55 +1075,58 @@ EmitAliasedVarOp(ExclusiveContext *cx, J
     BytecodeEmitter *bceOfDef = bce;
     if (pn->isUsed()) {
         /*
          * As explained in BindNameToSlot, the 'level' of a use indicates how
          * many function scopes (i.e., BytecodeEmitters) to skip to find the
          * enclosing function scope of the definition being accessed.
          */
         for (unsigned i = pn->pn_cookie.level(); i; i--) {
-            skippedScopes += ClonedBlockDepth(bceOfDef);
+            skippedScopes += DynamicNestedScopeDepth(bceOfDef);
             FunctionBox *funbox = bceOfDef->sc->asFunctionBox();
             if (funbox->isHeavyweight()) {
                 skippedScopes++;
                 if (funbox->function()->isNamedLambda())
                     skippedScopes++;
             }
             bceOfDef = bceOfDef->parent;
         }
     } else {
         JS_ASSERT(pn->isDefn());
         JS_ASSERT(pn->pn_cookie.level() == bce->script->staticLevel());
     }
 
     /*
-     * The final part of the skippedScopes computation depends on the type of variable. An arg or
-     * local variable is at the outer scope of a function and so includes the full
-     * ClonedBlockDepth. A let/catch-binding requires a search of the block chain to see how many
-     * (dynamic) block objects to skip.
+     * The final part of the skippedScopes computation depends on the type of
+     * variable. An arg or local variable is at the outer scope of a function
+     * and so includes the full DynamicNestedScopeDepth. A let/catch-binding
+     * requires a search of the block chain to see how many (dynamic) block
+     * objects to skip.
      */
     ScopeCoordinate sc;
     if (IsArgOp(pn->getOp())) {
-        if (!AssignHops(bce, pn, skippedScopes + ClonedBlockDepth(bceOfDef), &sc))
+        if (!AssignHops(bce, pn, skippedScopes + DynamicNestedScopeDepth(bceOfDef), &sc))
             return false;
         JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc));
     } else {
         JS_ASSERT(IsLocalOp(pn->getOp()) || pn->isKind(PNK_FUNCTION));
         uint32_t local = pn->pn_cookie.slot();
         if (local < bceOfDef->script->bindings.numVars()) {
-            if (!AssignHops(bce, pn, skippedScopes + ClonedBlockDepth(bceOfDef), &sc))
+            if (!AssignHops(bce, pn, skippedScopes + DynamicNestedScopeDepth(bceOfDef), &sc))
                 return false;
             JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc));
         } else {
             uint32_t depth = local - bceOfDef->script->bindings.numVars();
-            Rooted<StaticBlockObject*> b(cx, bceOfDef->blockChain);
+            JS_ASSERT(bceOfDef->staticScope->is<StaticBlockObject>());
+            Rooted<StaticBlockObject*> b(cx, &bceOfDef->staticScope->as<StaticBlockObject>());
             while (!b->containsVarAtDepth(depth)) {
                 if (b->needsClone())
                     skippedScopes++;
                 b = b->enclosingBlock();
+                JS_ASSERT(b);
             }
             if (!AssignHops(bce, pn, skippedScopes, &sc))
                 return false;
             sc.setSlot(b->localIndexToSlot(bceOfDef->script->bindings, local));
         }
     }
 
     return EmitAliasedVarOp(cx, op, sc, bce);
@@ -2886,18 +2891,16 @@ MaybeEmitVarDecl(ExclusiveContext *cx, B
  */
 enum VarEmitOption
 {
     DefineVars        = 0,
     PushInitialValues = 1,
     InitializeVars    = 2
 };
 
-#if JS_HAS_DESTRUCTURING
-
 typedef bool
 (*DestructuringDeclEmitter)(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn);
 
 static bool
 EmitDestructuringDecl(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn)
 {
     JS_ASSERT(pn->isKind(PNK_NAME));
     if (!BindNameToSlot(cx, bce, pn))
@@ -3335,76 +3338,66 @@ MaybeEmitLetGroupDecl(ExclusiveContext *
                 return false;
         }
 
         *pop = JSOP_NOP;
     }
     return true;
 }
 
-#endif /* JS_HAS_DESTRUCTURING */
-
 static bool
 EmitVariables(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmitOption emitOption,
               bool isLet = false)
 {
     JS_ASSERT(pn->isArity(PN_LIST));
     JS_ASSERT(isLet == (emitOption == PushInitialValues));
 
     ParseNode *next;
     for (ParseNode *pn2 = pn->pn_head; ; pn2 = next) {
         next = pn2->pn_next;
 
         ParseNode *pn3;
         if (!pn2->isKind(PNK_NAME)) {
-#if JS_HAS_DESTRUCTURING
             if (pn2->isKind(PNK_ARRAY) || pn2->isKind(PNK_OBJECT)) {
                 /*
                  * Emit variable binding ops, but not destructuring ops.  The
                  * parser (see Parser::variables) has ensured that our caller
                  * will be the PNK_FOR/PNK_FORIN/PNK_FOROF case in EmitTree, and
                  * that case will emit the destructuring code only after
                  * emitting an enumerating opcode and a branch that tests
                  * whether the enumeration ended.
                  */
                 JS_ASSERT(emitOption == DefineVars);
                 JS_ASSERT(pn->pn_count == 1);
                 if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn2))
                     return false;
                 break;
             }
-#endif
 
             /*
              * A destructuring initialiser assignment preceded by var will
              * never occur to the left of 'in' in a for-in loop.  As with 'for
              * (var x = i in o)...', this will cause the entire 'var [a, b] =
              * i' to be hoisted out of the loop.
              */
             JS_ASSERT(pn2->isKind(PNK_ASSIGN));
             JS_ASSERT(pn2->isOp(JSOP_NOP));
             JS_ASSERT(emitOption != DefineVars);
 
             /*
              * To allow the front end to rewrite var f = x; as f = x; when a
              * function f(){} precedes the var, detect simple name assignment
              * here and initialize the name.
              */
-#if !JS_HAS_DESTRUCTURING
-            JS_ASSERT(pn2->pn_left->isKind(PNK_NAME));
-#else
-            if (pn2->pn_left->isKind(PNK_NAME))
-#endif
-            {
+            if (pn2->pn_left->isKind(PNK_NAME)) {
                 pn3 = pn2->pn_right;
                 pn2 = pn2->pn_left;
                 goto do_name;
             }
 
-#if JS_HAS_DESTRUCTURING
             ptrdiff_t stackDepthBefore = bce->stackDepth;
             JSOp op = JSOP_POP;
             if (pn->pn_count == 1) {
                 /*
                  * If this is the only destructuring assignment in the list,
                  * try to optimize to a group assignment.  If we're in a let
                  * head, pass JSOP_POP rather than the pseudo-prolog JSOP_NOP
                  * in pn->pn_op, to suppress a second (and misplaced) 'let'.
@@ -3442,17 +3435,16 @@ EmitVariables(ExclusiveContext *cx, Byte
 
             /* If we are not initializing, nothing to pop. */
             if (emitOption != InitializeVars) {
                 if (next)
                     continue;
                 break;
             }
             goto emit_note_pop;
-#endif
         }
 
         /*
          * Load initializer early to share code above that jumps to do_name.
          * NB: if this var redeclares an existing binding, then pn2 is linked
          * on its definition's use-chain and pn_expr has been overlayed with
          * pn_lexdef.
          */
@@ -3509,19 +3501,17 @@ EmitVariables(ExclusiveContext *cx, Byte
         if (!pn2->pn_cookie.isFree()) {
             if (!EmitVarOp(cx, pn2, op, bce))
                 return false;
         } else {
             if (!EmitIndexOp(cx, op, atomIndex, bce))
                 return false;
         }
 
-#if JS_HAS_DESTRUCTURING
     emit_note_pop:
-#endif
         if (!next)
             break;
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
     }
 
     if (pn->pn_xflags & PNX_POPVAR) {
         if (Emit1(cx, bce, JSOP_POP) < 0)
@@ -3573,21 +3563,19 @@ EmitAssignment(ExclusiveContext *cx, Byt
       case PNK_ELEM:
         JS_ASSERT(lhs->isArity(PN_BINARY));
         if (!EmitTree(cx, bce, lhs->pn_left))
             return false;
         if (!EmitTree(cx, bce, lhs->pn_right))
             return false;
         offset += 2;
         break;
-#if JS_HAS_DESTRUCTURING
       case PNK_ARRAY:
       case PNK_OBJECT:
         break;
-#endif
       case PNK_CALL:
         JS_ASSERT(lhs->pn_xflags & PNX_SETCALL);
         if (!EmitTree(cx, bce, lhs))
             return false;
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
         break;
       default:
@@ -3719,23 +3707,21 @@ EmitAssignment(ExclusiveContext *cx, Byt
       case PNK_CALL:
         /* Do nothing. The JSOP_SETCALL we emitted will always throw. */
         JS_ASSERT(lhs->pn_xflags & PNX_SETCALL);
         break;
       case PNK_ELEM:
         if (Emit1(cx, bce, JSOP_SETELEM) < 0)
             return false;
         break;
-#if JS_HAS_DESTRUCTURING
       case PNK_ARRAY:
       case PNK_OBJECT:
         if (!EmitDestructuringOps(cx, bce, lhs))
             return false;
         break;
-#endif
       default:
         JS_ASSERT(0);
     }
     return true;
 }
 
 bool
 ParseNode::getConstantValue(ExclusiveContext *cx, bool strictChecks, MutableHandleValue vp)
@@ -3897,25 +3883,23 @@ EmitCatch(ExclusiveContext *cx, Bytecode
      * Dup the exception object if there is a guard for rethrowing to use
      * it later when rethrowing or in other catches.
      */
     if (pn->pn_kid2 && Emit1(cx, bce, JSOP_DUP) < 0)
         return false;
 
     ParseNode *pn2 = pn->pn_kid1;
     switch (pn2->getKind()) {
-#if JS_HAS_DESTRUCTURING
       case PNK_ARRAY:
       case PNK_OBJECT:
         if (!EmitDestructuringOps(cx, bce, pn2))
             return false;
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
         break;
-#endif
 
       case PNK_NAME:
         /* Inline and specialize BindNameToSlot for pn2. */
         JS_ASSERT(!pn2->pn_cookie.isFree());
         if (!EmitVarOp(cx, pn2, JSOP_SETLOCAL, bce))
             return false;
         if (Emit1(cx, bce, JSOP_POP) < 0)
             return false;
@@ -4674,23 +4658,21 @@ EmitNormalFor(ExclusiveContext *cx, Byte
     JSOp op = JSOP_POP;
     ParseNode *pn3 = forHead->pn_kid1;
     if (!pn3) {
         // No initializer, but emit a nop so that there's somewhere to put the
         // SRC_FOR annotation that IonBuilder will look for.
         op = JSOP_NOP;
     } else {
         bce->emittingForInit = true;
-#if JS_HAS_DESTRUCTURING
         if (pn3->isKind(PNK_ASSIGN)) {
             JS_ASSERT(pn3->isOp(JSOP_NOP));
             if (!MaybeEmitGroupAssignment(cx, bce, op, pn3, GroupIsNotDecl, &op))
                 return false;
         }
-#endif
         if (op == JSOP_POP) {
             if (!UpdateSourceCoordNotes(cx, bce, pn3->pn_pos.begin))
                 return false;
             if (!EmitTree(cx, bce, pn3))
                 return false;
             if (pn3->isKind(PNK_VAR) || pn3->isKind(PNK_CONST) || pn3->isKind(PNK_LET)) {
                 /*
                  * Check whether a destructuring-initialized var decl
@@ -4750,23 +4732,21 @@ EmitNormalFor(ExclusiveContext *cx, Byte
     } while ((stmt = stmt->down) != nullptr && stmt->type == STMT_LABEL);
 
     /* Check for update code to do before the condition (if any). */
     pn3 = forHead->pn_kid3;
     if (pn3) {
         if (!UpdateSourceCoordNotes(cx, bce, pn3->pn_pos.begin))
             return false;
         op = JSOP_POP;
-#if JS_HAS_DESTRUCTURING
         if (pn3->isKind(PNK_ASSIGN)) {
             JS_ASSERT(pn3->isOp(JSOP_NOP));
             if (!MaybeEmitGroupAssignment(cx, bce, op, pn3, GroupIsNotDecl, &op))
                 return false;
         }
-#endif
         if (op == JSOP_POP && !EmitTree(cx, bce, pn3))
             return false;
 
         /* Always emit the POP or NOP to help IonBuilder. */
         if (Emit1(cx, bce, op) < 0)
             return false;
 
         /* Restore the absolute line number for source note readers. */
@@ -4860,17 +4840,17 @@ EmitFunc(ExclusiveContext *cx, BytecodeE
             fun->isInterpreted() &&
             (bce->checkSingletonContext() ||
              (!bce->isInLoop() && bce->isRunOnceLambda()));
         if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton))
             return false;
 
         if (fun->isInterpretedLazy()) {
             if (!fun->lazyScript()->sourceObject()) {
-                JSObject *scope = bce->blockChain;
+                JSObject *scope = bce->staticScope;
                 if (!scope && bce->sc->isFunctionBox())
                     scope = bce->sc->asFunctionBox()->function();
                 JSObject *source = bce->script->sourceObject();
                 fun->lazyScript()->setParent(scope, &source->as<ScriptSourceObject>());
             }
             if (bce->emittingRunOnceLambda)
                 fun->lazyScript()->setTreatAsRunOnce();
         } else {
@@ -5396,24 +5376,22 @@ EmitStatement(ExclusiveContext *cx, Byte
         {
             useful = true;
         }
     }
 
     if (useful) {
         JSOp op = wantval ? JSOP_SETRVAL : JSOP_POP;
         JS_ASSERT_IF(pn2->isKind(PNK_ASSIGN), pn2->isOp(JSOP_NOP));
-#if JS_HAS_DESTRUCTURING
         if (!wantval &&
             pn2->isKind(PNK_ASSIGN) &&
             !MaybeEmitGroupAssignment(cx, bce, op, pn2, GroupIsNotDecl, &op))
         {
             return false;
         }
-#endif
         if (op != JSOP_NOP) {
             if (!EmitTree(cx, bce, pn2))
                 return false;
             if (Emit1(cx, bce, op) < 0)
                 return false;
         }
     } else if (!pn->isDirectivePrologueMember()) {
         /* Don't complain about directive prologue members; just don't emit their code. */
@@ -5880,22 +5858,20 @@ EmitConditionalExpression(ExclusiveConte
 
 /*
  * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
  * the comment on EmitSwitch.
  */
 MOZ_NEVER_INLINE static bool
 EmitObject(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
-#if JS_HAS_DESTRUCTURING_SHORTHAND
     if (pn->pn_xflags & PNX_DESTRUCT) {
         bce->reportError(pn, JSMSG_BAD_OBJECT_INIT);
         return false;
     }
-#endif
 
     if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext())
         return EmitSingletonInitialiser(cx, bce, pn);
 
     /*
      * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing
      * a new object and defining (in source order) each property on the object
      * (or mutating the object's [[Prototype]], in the case of __proto__).
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -102,18 +102,18 @@ struct BytecodeEmitter
 
     /* the parser */
     Parser<FullParseHandler> *const parser;
 
     HandleScript    evalCaller;     /* scripted caller info for eval and dbgapi */
 
     StmtInfoBCE     *topStmt;       /* top of statement info stack */
     StmtInfoBCE     *topScopeStmt;  /* top lexical scope statement */
-    Rooted<StaticBlockObject *> blockChain;
-                                    /* compile time block scope chain */
+    Rooted<NestedScopeObject *> staticScope;
+                                    /* compile time scope chain */
 
     OwnedAtomIndexMapPtr atomIndices; /* literals indexed for mapping */
     unsigned        firstLine;      /* first line, for JSScript::initFromEmitter */
 
     int32_t         stackDepth;     /* current stack depth in script frame */
     uint32_t        maxStackDepth;  /* maximum stack depth so far */
 
     uint32_t        arrayCompDepth; /* stack depth of array in comprehension */
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -321,18 +321,16 @@ Definition::kindString(Kind kind)
 
     JS_ASSERT(unsigned(kind) <= unsigned(ARG));
     return table[kind];
 }
 
 namespace js {
 namespace frontend {
 
-#if JS_HAS_DESTRUCTURING
-
 /*
  * This function assumes the cloned tree is for use in the same statement and
  * binding context as the original tree.
  */
 template <>
 ParseNode *
 Parser<FullParseHandler>::cloneParseTree(ParseNode *opn)
 {
@@ -418,18 +416,16 @@ Parser<FullParseHandler>::cloneParseTree
         pn->pn_u = opn->pn_u;
         break;
 
 #undef NULLCHECK
     }
     return pn;
 }
 
-#endif /* JS_HAS_DESTRUCTURING */
-
 /*
  * Used by Parser::forStatement and comprehensionTail to clone the TARGET in
  *   for (var/const/let TARGET in EXPR)
  *
  * opn must be the pn_head of a node produced by Parser::variables, so its form
  * is known to be LHS = NAME | [LHS] | {id:LHS}.
  *
  * The cloned tree is for use only in the same statement and binding context as
@@ -442,17 +438,16 @@ Parser<FullParseHandler>::cloneLeftHandS
     ParseNode *pn = handler.new_<ParseNode>(opn->getKind(), opn->getOp(), opn->getArity(),
                                             opn->pn_pos);
     if (!pn)
         return nullptr;
     pn->setInParens(opn->isInParens());
     pn->setDefn(opn->isDefn());
     pn->setUsed(opn->isUsed());
 
-#if JS_HAS_DESTRUCTURING
     if (opn->isArity(PN_LIST)) {
         JS_ASSERT(opn->isKind(PNK_ARRAY) || opn->isKind(PNK_OBJECT));
         pn->makeEmpty();
         for (ParseNode *opn2 = opn->pn_head; opn2; opn2 = opn2->pn_next) {
             ParseNode *pn2;
             if (opn->isKind(PNK_OBJECT)) {
                 JS_ASSERT(opn2->isArity(PN_BINARY));
                 JS_ASSERT(opn2->isKind(PNK_COLON));
@@ -474,17 +469,16 @@ Parser<FullParseHandler>::cloneLeftHandS
 
             if (!pn2)
                 return nullptr;
             pn->append(pn2);
         }
         pn->pn_xflags = opn->pn_xflags;
         return pn;
     }
-#endif
 
     JS_ASSERT(opn->isArity(PN_NAME));
     JS_ASSERT(opn->isKind(PNK_NAME));
 
     /* If opn is a definition or use, make pn a use. */
     pn->pn_u.name = opn->pn_u.name;
     pn->setOp(JSOP_SETNAME);
     if (opn->isUsed()) {
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -44,16 +44,18 @@ using namespace js;
 using namespace js::gc;
 using mozilla::Maybe;
 
 namespace js {
 namespace frontend {
 
 typedef Rooted<StaticBlockObject*> RootedStaticBlockObject;
 typedef Handle<StaticBlockObject*> HandleStaticBlockObject;
+typedef Rooted<NestedScopeObject*> RootedNestedScopeObject;
+typedef Handle<NestedScopeObject*> HandleNestedScopeObject;
 
 
 /*
  * Insist that the next token be of type tt, or report errno and return null.
  * NB: this macro uses cx and ts from its lexical environment.
  */
 #define MUST_MATCH_TOKEN(tt, errno)                                                         \
     JS_BEGIN_MACRO                                                                          \
@@ -1500,17 +1502,16 @@ Parser<ParseHandler>::defineArg(Node fun
 
     if (!checkStrictBinding(name, argpn))
         return false;
 
     handler.addFunctionArgument(funcpn, argpn);
     return pc->define(tokenStream, name, argpn, Definition::ARG);
 }
 
-#if JS_HAS_DESTRUCTURING
 template <typename ParseHandler>
 /* static */ bool
 Parser<ParseHandler>::bindDestructuringArg(BindData<ParseHandler> *data,
                                            HandlePropertyName name, Parser<ParseHandler> *parser)
 {
     ParseContext<ParseHandler> *pc = parser->pc;
     JS_ASSERT(pc->sc->isFunctionBox());
 
@@ -1519,17 +1520,16 @@ Parser<ParseHandler>::bindDestructuringA
         return false;
     }
 
     if (!parser->checkStrictBinding(name, data->pn))
         return false;
 
     return pc->define(parser->tokenStream, name, data->pn, Definition::VAR);
 }
-#endif /* JS_HAS_DESTRUCTURING */
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::functionArguments(FunctionSyntaxKind kind, Node *listp, Node funcpn,
                                         bool *hasRest)
 {
     FunctionBox *funbox = pc->sc->asFunctionBox();
 
@@ -1553,30 +1553,27 @@ Parser<ParseHandler>::functionArguments(
     Node argsbody = handler.newList(PNK_ARGSBODY);
     if (!argsbody)
         return false;
     handler.setFunctionBody(funcpn, argsbody);
 
     if (parenFreeArrow || !tokenStream.matchToken(TOK_RP)) {
         bool hasDefaults = false;
         Node duplicatedArg = null();
-#if JS_HAS_DESTRUCTURING
         Node list = null();
-#endif
 
         do {
             if (*hasRest) {
                 report(ParseError, false, null(), JSMSG_PARAMETER_AFTER_REST);
                 return false;
             }
 
             TokenKind tt = tokenStream.getToken();
             JS_ASSERT_IF(parenFreeArrow, tt == TOK_NAME);
             switch (tt) {
-#if JS_HAS_DESTRUCTURING
               case TOK_LB:
               case TOK_LC:
               {
                 /* See comment below in the TOK_NAME case. */
                 if (duplicatedArg) {
                     report(ParseError, false, duplicatedArg, JSMSG_BAD_DUP_ARGS);
                     return false;
                 }
@@ -1623,17 +1620,16 @@ Parser<ParseHandler>::functionArguments(
                 } else {
                     list = handler.newList(PNK_VAR, item);
                     if (!list)
                         return false;
                     *listp = list;
                 }
                 break;
               }
-#endif /* JS_HAS_DESTRUCTURING */
 
               case TOK_YIELD:
                 if (!checkYieldNameValidity())
                     return false;
                 goto TOK_NAME;
 
               case TOK_TRIPLEDOT:
               {
@@ -2012,17 +2008,16 @@ Parser<ParseHandler>::functionDef(Handle
 
 template <>
 bool
 Parser<FullParseHandler>::finishFunctionDefinition(ParseNode *pn, FunctionBox *funbox,
                                                    ParseNode *prelude, ParseNode *body)
 {
     pn->pn_pos.end = pos().end;
 
-#if JS_HAS_DESTRUCTURING
     /*
      * If there were destructuring formal parameters, prepend the initializing
      * comma expression that we synthesized to body. If the body is a return
      * node, we must make a special PNK_SEQ node, to prepend the destructuring
      * code without bracing the decompilation of the function body.
      */
     if (prelude) {
         if (!body->isArity(PN_LIST)) {
@@ -2045,17 +2040,16 @@ Parser<FullParseHandler>::finishFunction
         item->pn_kid = prelude;
         item->pn_next = body->pn_head;
         body->pn_head = item;
         if (body->pn_tail == &body->pn_head)
             body->pn_tail = &item->pn_next;
         ++body->pn_count;
         body->pn_xflags |= PNX_DESTRUCT;
     }
-#endif
 
     JS_ASSERT(pn->pn_funbox == funbox);
     pn->pn_body->append(body);
     pn->pn_body->pn_pos = body->pn_pos;
 
     return true;
 }
 
@@ -2671,17 +2665,17 @@ Parser<ParseHandler>::reportRedeclaratio
     return false;
 }
 
 /*
  * Define a let-variable in a block, let-expression, or comprehension scope. pc
  * must already be in such a scope.
  *
  * Throw a SyntaxError if 'atom' is an invalid name. Otherwise create a
- * property for the new variable on the block object, pc->blockChain;
+ * property for the new variable on the block object, pc->staticScope;
  * populate data->pn->pn_{op,cookie,defn,dflags}; and stash a pointer to
  * data->pn in a slot of the block object.
  */
 template <>
 /* static */ bool
 Parser<FullParseHandler>::bindLet(BindData<FullParseHandler> *data,
                                   HandlePropertyName name, Parser<FullParseHandler> *parser)
 {
@@ -2774,25 +2768,28 @@ struct PopLetDecl {
         return true;
     }
 };
 
 template <typename ParseHandler>
 static void
 PopStatementPC(TokenStream &ts, ParseContext<ParseHandler> *pc)
 {
-    RootedStaticBlockObject blockObj(ts.context(), pc->topStmt->blockObj);
-    JS_ASSERT(!!blockObj == (pc->topStmt->isBlockScope));
+    RootedNestedScopeObject scopeObj(ts.context(), pc->topStmt->staticScope);
+    JS_ASSERT(!!scopeObj == (pc->topStmt->isBlockScope));
 
     FinishPopStatement(pc);
 
-    if (blockObj) {
-        JS_ASSERT(!blockObj->inDictionaryMode());
-        ForEachLetDef(ts, pc, blockObj, PopLetDecl<ParseHandler>());
-        blockObj->resetPrevBlockChainFromParser();
+    if (scopeObj) {
+        if (scopeObj->is<StaticBlockObject>()) {
+            RootedStaticBlockObject blockObj(ts.context(), &scopeObj->as<StaticBlockObject>());
+            JS_ASSERT(!blockObj->inDictionaryMode());
+            ForEachLetDef(ts, pc, blockObj, PopLetDecl<ParseHandler>());
+        }
+        scopeObj->resetEnclosingNestedScopeFromParser();
     }
 }
 
 /*
  * The function LexicalLookup searches a static binding for the given name in
  * the stack of statements enclosing the statement currently being parsed. Each
  * statement that introduces a new scope has a corresponding scope object, on
  * which the bindings for that scope are stored. LexicalLookup either returns
@@ -2815,17 +2812,17 @@ LexicalLookup(ContextT *ct, HandleAtom a
          */
         if (stmt->type == STMT_WITH)
             break;
 
         // Skip statements that do not introduce a new scope
         if (!stmt->isBlockScope)
             continue;
 
-        StaticBlockObject &blockObj = *stmt->blockObj;
+        StaticBlockObject &blockObj = stmt->staticBlock();
         Shape *shape = blockObj.nativeLookup(ct->sc->context, id);
         if (shape) {
             JS_ASSERT(shape->hasShortID());
 
             if (slotp)
                 *slotp = blockObj.stackDepth() + shape->shortid();
             return stmt;
         }
@@ -2985,18 +2982,16 @@ Parser<ParseHandler>::noteNameUse(Handle
     handler.linkUseToDef(pn, dn);
 
     if (stmt && stmt->type == STMT_WITH)
         handler.setFlag(pn, PND_DEOPTIMIZED);
 
     return true;
 }
 
-#if JS_HAS_DESTRUCTURING
-
 template <>
 bool
 Parser<FullParseHandler>::bindDestructuringVar(BindData<FullParseHandler> *data, ParseNode *pn)
 {
     JS_ASSERT(pn->isKind(PNK_NAME));
 
     RootedPropertyName name(context, pn->pn_atom->asPropertyName());
 
@@ -3184,30 +3179,28 @@ Parser<ParseHandler>::destructuringExpr(
     pc->inDeclDestructuring = false;
     if (!pn)
         return null();
     if (!checkDestructuring(data, pn))
         return null();
     return pn;
 }
 
-#endif /* JS_HAS_DESTRUCTURING */
-
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::pushLexicalScope(HandleStaticBlockObject blockObj, StmtInfoPC *stmt)
 {
     JS_ASSERT(blockObj);
 
     ObjectBox *blockbox = newObjectBox(blockObj);
     if (!blockbox)
         return null();
 
     PushStatementPC(pc, stmt, STMT_BLOCK);
-    blockObj->initPrevBlockChainFromParser(pc->blockChain);
+    blockObj->initEnclosingNestedScopeFromParser(pc->staticScope);
     FinishPushBlockScope(pc, stmt, *blockObj.get());
 
     Node pn = handler.newLexicalScope(blockbox);
     if (!pn)
         return null();
 
     if (!GenerateBlockId(tokenStream, pc, stmt->blockid))
         return null();
@@ -3456,17 +3449,16 @@ Parser<ParseHandler>::variables(ParseNod
     bool first = true;
     Node pn2;
     do {
         if (psimple && !first)
             *psimple = false;
         first = false;
 
         TokenKind tt = tokenStream.getToken();
-#if JS_HAS_DESTRUCTURING
         if (tt == TOK_LB || tt == TOK_LC) {
             if (psimple)
                 *psimple = false;
 
             pc->inDeclDestructuring = true;
             pn2 = primaryExpr(tt);
             pc->inDeclDestructuring = false;
             if (!pn2)
@@ -3488,17 +3480,16 @@ Parser<ParseHandler>::variables(ParseNod
                 return null();
 
             pn2 = handler.newBinaryOrAppend(PNK_ASSIGN, pn2, init, pc);
             if (!pn2)
                 return null();
             handler.addList(pn, pn2);
             continue;
         }
-#endif /* JS_HAS_DESTRUCTURING */
 
         if (tt != TOK_NAME) {
             if (tt == TOK_YIELD) {
                 if (!checkYieldNameValidity())
                     return null();
             } else {
                 if (tt != TOK_ERROR)
                     report(ParseError, false, null(), JSMSG_NO_VARIABLE_NAME);
@@ -3555,17 +3546,17 @@ Parser<FullParseHandler>::letDeclaration
          */
         StmtInfoPC *stmt = pc->topStmt;
         if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) {
             report(ParseError, false, null(), JSMSG_LET_DECL_NOT_IN_BLOCK);
             return null();
         }
 
         if (stmt && stmt->isBlockScope) {
-            JS_ASSERT(pc->blockChain == stmt->blockObj);
+            JS_ASSERT(pc->staticScope == stmt->staticScope);
         } else {
             if (pc->atBodyLevel()) {
                 /*
                  * ES4 specifies that let at top level and at body-block scope
                  * does not shadow var, so convert back to var.
                  */
                 pn = variables(PNK_VAR);
                 if (!pn)
@@ -3601,19 +3592,19 @@ Parser<FullParseHandler>::letDeclaration
              * list stack, if it isn't already there.  If it is there, but it
              * lacks the SIF_SCOPE flag, it must be a try, catch, or finally
              * block.
              */
             stmt->isBlockScope = true;
             stmt->downScope = pc->topScopeStmt;
             pc->topScopeStmt = stmt;
 
-            blockObj->initPrevBlockChainFromParser(pc->blockChain);
-            pc->blockChain = blockObj;
-            stmt->blockObj = blockObj;
+            blockObj->initEnclosingNestedScopeFromParser(pc->staticScope);
+            pc->staticScope = blockObj;
+            stmt->staticScope = blockObj;
 
 #ifdef DEBUG
             ParseNode *tmp = pc->blockNode;
             JS_ASSERT(!tmp || !tmp->isKind(PNK_LEXICALSCOPE));
 #endif
 
             /* Create a new lexical scope node for these statements. */
             ParseNode *pn1 = LexicalScopeNode::create(PNK_LEXICALSCOPE, &handler);
@@ -3623,17 +3614,17 @@ Parser<FullParseHandler>::letDeclaration
             pn1->setOp(JSOP_POPN);
             pn1->pn_pos = pc->blockNode->pn_pos;
             pn1->pn_objbox = blockbox;
             pn1->pn_expr = pc->blockNode;
             pn1->pn_blockid = pc->blockNode->pn_blockid;
             pc->blockNode = pn1;
         }
 
-        pn = variables(PNK_LET, nullptr, pc->blockChain, HoistVars);
+        pn = variables(PNK_LET, nullptr, &pc->staticScope->as<StaticBlockObject>(), HoistVars);
         if (!pn)
             return null();
         pn->pn_xflags = PNX_POPVAR;
     } while (0);
 
     return MatchOrInsertSemicolon(tokenStream) ? pn : nullptr;
 }
 
@@ -4058,49 +4049,45 @@ Parser<FullParseHandler>::isValidForStat
                                                  ParseNodeKind headKind)
 {
     if (isForDecl) {
         if (pn1->pn_count > 1)
             return false;
         if (pn1->isOp(JSOP_DEFCONST))
             return false;
 
-#if JS_HAS_DESTRUCTURING
         // In JS 1.7 only, for (var [K, V] in EXPR) has a special meaning.
         // Hence all other destructuring decls are banned there.
         if (version == JSVERSION_1_7 && !isForEach && headKind == PNK_FORIN) {
             ParseNode *lhs = pn1->pn_head;
             if (lhs->isKind(PNK_ASSIGN))
                 lhs = lhs->pn_left;
 
             if (lhs->isKind(PNK_OBJECT))
                 return false;
             if (lhs->isKind(PNK_ARRAY) && lhs->pn_count != 2)
                 return false;
         }
-#endif
         return true;
     }
 
     switch (pn1->getKind()) {
       case PNK_NAME:
       case PNK_DOT:
       case PNK_CALL:
       case PNK_ELEM:
         return true;
 
-#if JS_HAS_DESTRUCTURING
       case PNK_ARRAY:
       case PNK_OBJECT:
         // In JS 1.7 only, for ([K, V] in EXPR) has a special meaning.
         // Hence all other destructuring left-hand sides are banned there.
         if (version == JSVERSION_1_7 && !isForEach && headKind == PNK_FORIN)
             return pn1->isKind(PNK_ARRAY) && pn1->pn_count == 2;
         return true;
-#endif
 
       default:
         return false;
     }
 }
 
 template <>
 ParseNode *
@@ -4241,22 +4228,17 @@ Parser<FullParseHandler>::forStatement()
         /*
          * After the following if-else, pn2 will point to the name or
          * destructuring pattern on in's left. pn1 will point to the decl, if
          * any, else nullptr. Note that the "declaration with initializer" case
          * rewrites the loop-head, moving the decl and setting pn1 to nullptr.
          */
         if (isForDecl) {
             pn2 = pn1->pn_head;
-            if ((pn2->isKind(PNK_NAME) && pn2->maybeExpr())
-#if JS_HAS_DESTRUCTURING
-                || pn2->isKind(PNK_ASSIGN)
-#endif
-                )
-            {
+            if ((pn2->isKind(PNK_NAME) && pn2->maybeExpr()) || pn2->isKind(PNK_ASSIGN)) {
                 /*
                  * Declaration with initializer.
                  *
                  * Rewrite 'for (<decl> x = i in o)' where <decl> is 'var' or
                  * 'const' to hoist the initializer or the entire decl out of
                  * the loop head.
                  */
                 if (headKind == PNK_FOROF) {
@@ -4275,23 +4257,21 @@ Parser<FullParseHandler>::forStatement()
                  *
                  * Request JSOP_POP here since the var is for a simple
                  * name (it is not a destructuring binding's left-hand
                  * side) and it has an initializer.
                  */
                 pn1->pn_xflags |= PNX_POPVAR;
                 pn1 = nullptr;
 
-#if JS_HAS_DESTRUCTURING
                 if (pn2->isKind(PNK_ASSIGN)) {
                     pn2 = pn2->pn_left;
                     JS_ASSERT(pn2->isKind(PNK_ARRAY) || pn2->isKind(PNK_OBJECT) ||
                               pn2->isKind(PNK_NAME));
                 }
-#endif
             }
         } else {
             /* Not a declaration. */
             JS_ASSERT(!blockObj);
             pn2 = pn1;
             pn1 = nullptr;
 
             if (!checkAndMarkAsAssignmentLhs(pn2, PlainAssignment))
@@ -4329,32 +4309,30 @@ Parser<FullParseHandler>::forStatement()
         }
 
         switch (pn2->getKind()) {
           case PNK_NAME:
             /* Beware 'for (arguments in ...)' with or without a 'var'. */
             pn2->markAsAssigned();
             break;
 
-#if JS_HAS_DESTRUCTURING
           case PNK_ASSIGN:
             MOZ_ASSUME_UNREACHABLE("forStatement TOK_ASSIGN");
 
           case PNK_ARRAY:
           case PNK_OBJECT:
             if (versionNumber() == JSVERSION_1_7) {
                 /*
                  * Destructuring for-in requires [key, value] enumeration
                  * in JS1.7.
                  */
                 if (!isForEach && headKind == PNK_FORIN)
                     iflags |= JSITER_FOREACH | JSITER_KEYVALUE;
             }
             break;
-#endif
 
           default:;
         }
     } else {
         if (isForEach) {
             reportWithOffset(ParseError, false, begin, JSMSG_BAD_FOR_EACH_LOOP);
             return null();
         }
@@ -5073,30 +5051,29 @@ Parser<ParseHandler>::tryStatement()
              */
             MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_BEFORE_CATCH);
 
             /*
              * Contrary to ECMA Ed. 3, the catch variable is lexically
              * scoped, not a property of a new Object instance.  This is
              * an intentional change that anticipates ECMA Ed. 4.
              */
-            data.initLet(HoistVars, *pc->blockChain, JSMSG_TOO_MANY_CATCH_VARS);
+            data.initLet(HoistVars, pc->staticScope->template as<StaticBlockObject>(),
+                         JSMSG_TOO_MANY_CATCH_VARS);
             JS_ASSERT(data.let.blockObj);
 
             tt = tokenStream.getToken();
             Node catchName;
             switch (tt) {
-#if JS_HAS_DESTRUCTURING
               case TOK_LB:
               case TOK_LC:
                 catchName = destructuringExpr(&data, tt);
                 if (!catchName)
                     return null();
                 break;
-#endif
 
               case TOK_YIELD:
                 if (!checkYieldNameValidity())
                     return null();
                 // Fall through.
               case TOK_NAME:
               {
                 RootedPropertyName label(context, tokenStream.currentName());
@@ -5517,27 +5494,25 @@ Parser<FullParseHandler>::checkAndMarkAs
         }
         pn->markAsAssigned();
         break;
 
       case PNK_DOT:
       case PNK_ELEM:
         break;
 
-#if JS_HAS_DESTRUCTURING
       case PNK_ARRAY:
       case PNK_OBJECT:
         if (flavor == CompoundAssignment) {
             report(ParseError, false, null(), JSMSG_BAD_DESTRUCT_ASS);
             return false;
         }
         if (!checkDestructuring(nullptr, pn))
             return false;
         break;
-#endif
 
       case PNK_CALL:
         if (!makeSetCall(pn, JSMSG_BAD_LEFTSIDE_OF_ASS))
             return false;
         break;
 
       default:
         report(ParseError, false, pn, JSMSG_BAD_LEFTSIDE_OF_ASS);
@@ -6092,18 +6067,18 @@ Parser<FullParseHandler>::comprehensionT
 
     CompExprTransplanter transplanter(kid, this, outerpc, kind == PNK_SEMI, adjust);
     if (!transplanter.init())
         return null();
 
     if (!transplanter.transplant(kid))
         return null();
 
-    JS_ASSERT(pc->blockChain && pc->blockChain == pn->pn_objbox->object);
-    data.initLet(HoistVars, *pc->blockChain, JSMSG_ARRAY_INIT_TOO_BIG);
+    JS_ASSERT(pc->staticScope && pc->staticScope == pn->pn_objbox->object);
+    data.initLet(HoistVars, pc->staticScope->as<StaticBlockObject>(), JSMSG_ARRAY_INIT_TOO_BIG);
 
     do {
         /*
          * FOR node is binary, left is loop control and right is body.  Use
          * index to count each block-local let-variable on the left-hand side
          * of the in/of.
          */
         pn2 = BinaryNode::create(PNK_FOR, &handler);
@@ -6116,26 +6091,24 @@ Parser<FullParseHandler>::comprehensionT
             pn2->pn_iflags |= JSITER_FOREACH;
         MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR);
 
         uint32_t startYieldOffset = pc->lastYieldOffset;
 
         RootedPropertyName name(context);
         tt = tokenStream.getToken();
         switch (tt) {
-#if JS_HAS_DESTRUCTURING
           case TOK_LB:
           case TOK_LC:
             pc->inDeclDestructuring = true;
             pn3 = primaryExpr(tt);
             pc->inDeclDestructuring = false;
             if (!pn3)
                 return null();
             break;
-#endif
 
           case TOK_NAME:
             name = tokenStream.currentName();
 
             /*
              * Create a name node with pn_op JSOP_NAME.  We can't set pn_op to
              * JSOP_GETLOCAL here, because we don't yet know the block's depth
              * in the operand stack frame.  The code generator computes that,
@@ -6177,17 +6150,16 @@ Parser<FullParseHandler>::comprehensionT
 
         if (isGenexp && pc->lastYieldOffset != startYieldOffset) {
             reportWithOffset(ParseError, false, pc->lastYieldOffset,
                              JSMSG_BAD_GENEXP_BODY, js_yield_str);
             return null();
         }
 
         switch (tt) {
-#if JS_HAS_DESTRUCTURING
           case TOK_LB:
           case TOK_LC:
             if (!checkDestructuring(&data, pn3))
                 return null();
 
             if (versionNumber() == JSVERSION_1_7 &&
                 !(pn2->pn_iflags & JSITER_FOREACH) &&
                 !isForOf)
@@ -6199,17 +6171,16 @@ Parser<FullParseHandler>::comprehensionT
                 }
 
                 JS_ASSERT(pn2->isOp(JSOP_ITER));
                 JS_ASSERT(pn2->pn_iflags & JSITER_ENUMERATE);
                 JS_ASSERT(headKind == PNK_FORIN);
                 pn2->pn_iflags |= JSITER_FOREACH | JSITER_KEYVALUE;
             }
             break;
-#endif
 
           case TOK_NAME:
             data.pn = pn3;
             if (!data.binder(&data, name, this))
                 return null();
             break;
 
           default:;
@@ -6630,25 +6601,25 @@ Parser<ParseHandler>::stringLiteral()
     return handler.newStringLiteral(atom, pos());
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::newRegExp()
 {
     // Create the regexp even when doing a syntax parse, to check the regexp's syntax.
+    const jschar *chars = tokenStream.getTokenbuf().begin();
     size_t length = tokenStream.getTokenbuf().length();
-    const StableCharPtr chars(tokenStream.getTokenbuf().begin(), length);
     RegExpFlag flags = tokenStream.currentToken().regExpFlags();
 
     Rooted<RegExpObject*> reobj(context);
     if (RegExpStatics *res = context->global()->getRegExpStatics())
-        reobj = RegExpObject::create(context, res, chars.get(), length, flags, &tokenStream);
+        reobj = RegExpObject::create(context, res, chars, length, flags, &tokenStream);
     else
-        reobj = RegExpObject::createNoStatics(context, chars.get(), length, flags, &tokenStream);
+        reobj = RegExpObject::createNoStatics(context, chars, length, flags, &tokenStream);
 
     if (!reobj)
         return null();
 
     return handler.newRegExp(reobj, pos(), *this);
 }
 
 template <typename ParseHandler>
@@ -6910,17 +6881,16 @@ Parser<ParseHandler>::objectLiteral()
                  * the default Object.prototype.
                  */
                 if (!handler.isConstant(propexpr) || atom == context->names().proto)
                     handler.setListFlag(literal, PNX_NONCONST);
 
                 if (!handler.addPropertyDefinition(literal, propname, propexpr))
                     return null();
             }
-#if JS_HAS_DESTRUCTURING_SHORTHAND
             else if (ltok == TOK_NAME && (tt == TOK_COMMA || tt == TOK_RC)) {
                 /*
                  * Support, e.g., |var {x, y} = o| as destructuring shorthand
                  * for |var {x: x, y: y} = o|, per proposed JS2/ES4 for JS1.8.
                  */
                 if (!abortIfSyntaxParser())
                     return null();
                 tokenStream.ungetToken();
@@ -6929,17 +6899,16 @@ Parser<ParseHandler>::objectLiteral()
                 PropertyName *name = handler.isName(propname);
                 JS_ASSERT(atom);
                 propname = newName(name);
                 if (!propname)
                     return null();
                 if (!handler.addShorthandPropertyDefinition(literal, propname))
                     return null();
             }
-#endif
             else {
                 report(ParseError, false, null(), JSMSG_COLON_AFTER_ID);
                 return null();
             }
         } else {
             /* NB: Getter function in { get x(){} } is unnamed. */
             Rooted<PropertyName*> funName(context, nullptr);
             TokenStream::Position start(keepAtoms);
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -93,18 +93,17 @@ struct ParseContext : public GenericPars
     typedef typename ParseHandler::Node Node;
     typedef typename ParseHandler::DefinitionNode DefinitionNode;
 
     uint32_t        bodyid;         /* block number of program/function body */
     uint32_t        blockidGen;     /* preincremented block number generator */
 
     StmtInfoPC      *topStmt;       /* top of statement info stack */
     StmtInfoPC      *topScopeStmt;  /* top lexical scope statement */
-    Rooted<StaticBlockObject *> blockChain;
-                                    /* compile time block scope chain */
+    Rooted<NestedScopeObject *> staticScope;  /* compile time scope chain */
     Node            maybeFunction;  /* sc->isFunctionBox, the pn where pn->pn_funbox == sc */
 
     const unsigned  staticLevel;    /* static compilation unit nesting level */
 
     // lastYieldOffset stores the offset of the last yield that was parsed.
     // NoYieldOffset is its initial value.
     static const uint32_t NoYieldOffset = UINT32_MAX;
     uint32_t         lastYieldOffset;
@@ -245,17 +244,17 @@ struct ParseContext : public GenericPars
                  Node maybeFunction, SharedContext *sc,
                  Directives *newDirectives,
                  unsigned staticLevel, uint32_t bodyid)
       : GenericParseContext(parent, sc),
         bodyid(0),           // initialized in init()
         blockidGen(bodyid),  // used to set |bodyid| and subsequently incremented in init()
         topStmt(nullptr),
         topScopeStmt(nullptr),
-        blockChain(prs->context),
+        staticScope(prs->context),
         maybeFunction(maybeFunction),
         staticLevel(staticLevel),
         lastYieldOffset(NoYieldOffset),
         blockNode(ParseHandler::null()),
         decls_(prs->context, prs->alloc),
         args_(prs->context),
         vars_(prs->context),
         parserPC(&prs->pc),
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -396,30 +396,36 @@ struct StmtInfoBase {
      * STMT_FINALLY and the block contains at least one let-declaration.
      */
     bool isBlockScope:1;
 
     /* for (let ...) induced block scope */
     bool isForLetBlock:1;
 
     RootedAtom      label;          /* name of LABEL */
-    Rooted<StaticBlockObject *> blockObj; /* block scope object */
+    Rooted<NestedScopeObject *> staticScope; /* scope object */
 
     StmtInfoBase(ExclusiveContext *cx)
-        : isBlockScope(false), isForLetBlock(false), label(cx), blockObj(cx)
+        : isBlockScope(false), isForLetBlock(false), label(cx), staticScope(cx)
     {}
 
     bool maybeScope() const {
         return STMT_BLOCK <= type && type <= STMT_SUBROUTINE && type != STMT_WITH;
     }
 
     bool linksScope() const {
         return (STMT_WITH <= type && type <= STMT_CATCH) || isBlockScope;
     }
 
+    StaticBlockObject& staticBlock() const {
+        JS_ASSERT(isBlockScope);
+        JS_ASSERT(staticScope);
+        return staticScope->as<StaticBlockObject>();
+    }
+
     bool isLoop() const {
         return type >= STMT_DO_LOOP;
     }
 
     bool isTrying() const {
         return STMT_TRY <= type && type <= STMT_SUBROUTINE;
     }
 };
@@ -428,51 +434,51 @@ struct StmtInfoBase {
 template <class ContextT>
 void
 PushStatement(ContextT *ct, typename ContextT::StmtInfo *stmt, StmtType type)
 {
     stmt->type = type;
     stmt->isBlockScope = false;
     stmt->isForLetBlock = false;
     stmt->label = nullptr;
-    stmt->blockObj = nullptr;
+    stmt->staticScope = nullptr;
     stmt->down = ct->topStmt;
     ct->topStmt = stmt;
     if (stmt->linksScope()) {
         stmt->downScope = ct->topScopeStmt;
         ct->topScopeStmt = stmt;
     } else {
         stmt->downScope = nullptr;
     }
 }
 
 template <class ContextT>
 void
-FinishPushBlockScope(ContextT *ct, typename ContextT::StmtInfo *stmt, StaticBlockObject &blockObj)
+FinishPushBlockScope(ContextT *ct, typename ContextT::StmtInfo *stmt, NestedScopeObject &staticScope)
 {
     stmt->isBlockScope = true;
     stmt->downScope = ct->topScopeStmt;
     ct->topScopeStmt = stmt;
-    ct->blockChain = &blockObj;
-    stmt->blockObj = &blockObj;
+    ct->staticScope = &staticScope;
+    stmt->staticScope = &staticScope;
 }
 
 // Pop pc->topStmt. If the top StmtInfoPC struct is not stack-allocated, it
 // is up to the caller to free it.  The dummy argument is just to make the
 // template matching work.
 template <class ContextT>
 void
 FinishPopStatement(ContextT *ct)
 {
     typename ContextT::StmtInfo *stmt = ct->topStmt;
     ct->topStmt = stmt->down;
     if (stmt->linksScope()) {
         ct->topScopeStmt = stmt->downScope;
         if (stmt->isBlockScope)
-            ct->blockChain = stmt->blockObj->enclosingBlock();
+            ct->staticScope = stmt->staticBlock().enclosingBlock();
     }
 }
 
 /*
  * Find a lexically scoped variable (one declared by let, catch, or an array
  * comprehension) named by atom, looking in sc's compile-time scopes.
  *
  * If a WITH statement is reached along the scope stack, return its statement
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -423,22 +423,16 @@ AutoGCRooter::trace(JSTracer *trc)
         return;
 
       case VALVECTOR: {
         AutoValueVector::VectorImpl &vector = static_cast<AutoValueVector *>(this)->vector;
         MarkValueRootRange(trc, vector.length(), vector.begin(), "js::AutoValueVector.vector");
         return;
       }
 
-      case STRING:
-        if (static_cast<AutoStringRooter *>(this)->str_)
-            MarkStringRoot(trc, &static_cast<AutoStringRooter *>(this)->str_,
-                           "JS::AutoStringRooter.str_");
-        return;
-
       case IDVECTOR: {
         AutoIdVector::VectorImpl &vector = static_cast<AutoIdVector *>(this)->vector;
         MarkIdRootRange(trc, vector.length(), vector.begin(), "js::AutoIdVector.vector");
         return;
       }
 
       case SHAPEVECTOR: {
         AutoShapeVector::VectorImpl &vector = static_cast<js::AutoShapeVector *>(this)->vector;
--- a/js/src/jit-test/tests/asm.js/testZOOB.js
+++ b/js/src/jit-test/tests/asm.js/testZOOB.js
@@ -1,13 +1,15 @@
 load(libdir + "asm.js");
 
+setIonCheckGraphCoherency(false);
+setCachingEnabled(false);
+
 // constants
 var buf = new ArrayBuffer(4096);
-setIonCheckGraphCoherency(false);
 
 // An unshifted literal constant byte index in the range 0 to 2^31-1 inclusive should give a link failure.
 assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b);  function f() {return arr[0x7fffffff]|0 } return f'), this, null, buf);
 assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x1fffffff]|0 } return f'), this, null, buf);
 
 
 // An unshifted literal constant byte index outside the range 0 to 2^31-1 inclusive should cause an error compiling.
 assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x20000000]|0 } return f');
--- a/js/src/jit-test/tests/basic/bug698584.js
+++ b/js/src/jit-test/tests/basic/bug698584.js
@@ -10,12 +10,13 @@ try
   var str = "";
   for (var i = 0; i < MAX; ++i) {
       /x/.test(str);
       str += str + 'xxxxxxxxxxxxxx';
   }
 }
 catch (e)
 {
-  assertEq(""+e, "InternalError: allocation size overflow");
+  assertEq(""+e === "InternalError: allocation size overflow" ||
+           ""+e === "out of memory", true);
 }
 
 /* Don't crash */
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug967039.js
@@ -0,0 +1,6 @@
+var g1 = newGlobal();
+var dbg = Debugger(g1);
+g1.dbg = dbg;
+g1.eval("function foo() { dbg.removeDebuggee(this); }");
+g1.eval("function f() { try { throw 3; } catch(e) { foo(); } }\n");
+g1.f();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug966926.js
@@ -0,0 +1,12 @@
+var f32 = new Float32Array(32);
+function f(n) {
+    var x;
+    if (n > 10000) {
+        x = (0);
+    } else {
+        x = f32[0];
+    }
+    g('' + (x));
+}
+function g(y){}
+f(0);
--- a/js/src/jit/AsmJSLink.cpp
+++ b/js/src/jit/AsmJSLink.cpp
@@ -437,17 +437,17 @@ NewExportedFunction(JSContext *cx, const
 static bool
 HandleDynamicLinkFailure(JSContext *cx, CallArgs args, AsmJSModule &module, HandlePropertyName name)
 {
     if (cx->isExceptionPending())
         return false;
 
     uint32_t begin = module.charsBegin();
     uint32_t end = module.charsEnd();
-    Rooted<JSStableString*> src(cx, module.scriptSource()->substring(cx, begin, end));
+    Rooted<JSFlatString*> src(cx, module.scriptSource()->substring(cx, begin, end));
     if (!src)
         return false;
 
     RootedFunction fun(cx, NewFunction(cx, NullPtr(), nullptr, 0, JSFunction::INTERPRETED,
                                        cx->global(), name, JSFunction::FinalizeKind,
                                        TenuredObject));
     if (!fun)
         return false;
@@ -462,17 +462,17 @@ HandleDynamicLinkFailure(JSContext *cx, 
         formals.infallibleAppend(module.bufferArgumentName());
 
     CompileOptions options(cx);
     options.setPrincipals(cx->compartment()->principals)
            .setOriginPrincipals(module.scriptSource()->originPrincipals())
            .setCompileAndGo(false)
            .setNoScriptRval(false);
 
-    if (!frontend::CompileFunctionBody(cx, &fun, options, formals, src->chars().get(), end - begin))
+    if (!frontend::CompileFunctionBody(cx, &fun, options, formals, src->chars(), end - begin))
         return false;
 
     // Call the function we just recompiled.
 
     unsigned argc = args.length();
 
     InvokeArgs args2(cx);
     if (!args2.init(argc))
--- a/js/src/jit/TypePolicy.cpp
+++ b/js/src/jit/TypePolicy.cpp
@@ -10,16 +10,27 @@
 #include "jit/MIR.h"
 #include "jit/MIRGraph.h"
 
 using namespace js;
 using namespace js::jit;
 
 using JS::DoubleNaNValue;
 
+static void
+EnsureOperandNotFloat32(TempAllocator &alloc, MInstruction *def, unsigned op)
+{
+    MDefinition *in = def->getOperand(op);
+    if (in->type() == MIRType_Float32) {
+        MToDouble *replace = MToDouble::New(alloc, in);
+        def->block()->insertBefore(def, replace);
+        def->replaceOperand(op, replace);
+    }
+}
+
 MDefinition *
 BoxInputsPolicy::boxAt(TempAllocator &alloc, MInstruction *at, MDefinition *operand)
 {
     if (operand->isUnbox())
         return operand->toUnbox()->input();
     return alwaysBoxAt(alloc, at, operand);
 }
 
@@ -404,16 +415,19 @@ ConvertToStringPolicy<Op>::staticAdjustI
 
     MInstruction *replace;
     if (in->mightBeType(MIRType_Object)) {
         if (in->type() != MIRType_Value)
             in = boxAt(alloc, ins, in);
 
         replace = MUnbox::New(alloc, in, MIRType_String, MUnbox::Fallible);
     } else {
+        // TODO remove these two lines once 966957 has landed
+        EnsureOperandNotFloat32(alloc, ins, Op);
+        in = ins->getOperand(Op);
         replace = MToString::New(alloc, in);
     }
 
     ins->block()->insertBefore(ins, replace);
     ins->replaceOperand(Op, replace);
     return true;
 }
 
@@ -517,27 +531,16 @@ Float32Policy<Op>::staticAdjustInputs(Te
     def->replaceOperand(Op, replace);
     return true;
 }
 
 template bool Float32Policy<0>::staticAdjustInputs(TempAllocator &alloc, MInstruction *def);
 template bool Float32Policy<1>::staticAdjustInputs(TempAllocator &alloc, MInstruction *def);
 template bool Float32Policy<2>::staticAdjustInputs(TempAllocator &alloc, MInstruction *def);
 
-static void
-EnsureOperandNotFloat32(TempAllocator &alloc, MInstruction *def, unsigned op)
-{
-    MDefinition *in = def->getOperand(op);
-    if (in->type() == MIRType_Float32) {
-        MToDouble *replace = MToDouble::New(alloc, in);
-        def->block()->insertBefore(def, replace);
-        def->replaceOperand(op, replace);
-    }
-}
-
 template <unsigned Op>
 bool
 NoFloatPolicy<Op>::staticAdjustInputs(TempAllocator &alloc, MInstruction *def)
 {
     EnsureOperandNotFloat32(alloc, def, Op);
     return true;
 }
 
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -919,17 +919,17 @@ PopBlockScope(JSContext *cx, BaselineFra
 {
     frame->popBlock(cx);
     return true;
 }
 
 bool
 DebugLeaveBlock(JSContext *cx, BaselineFrame *frame, jsbytecode *pc)
 {
-    JS_ASSERT(cx->compartment()->debugMode());
+    JS_ASSERT(frame->script()->baselineScript()->debugMode());
 
     DebugScopes::onPopBlock(cx, frame, pc);
 
     return true;
 }
 
 bool
 InitBaselineFrameForOsr(BaselineFrame *frame, StackFrame *interpFrame, uint32_t numStackValues)
--- a/js/src/jsapi-tests/testCustomIterator.cpp
+++ b/js/src/jsapi-tests/testCustomIterator.cpp
@@ -37,16 +37,17 @@ const js::Class HasCustomIterClass = {
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     nullptr,
     nullptr, /* call */
     nullptr, /* hasInstance */
     nullptr, /* construct */
     nullptr, /* mark */
+    JS_NULL_CLASS_SPEC,
     {
         nullptr,     /* outerObject */
         nullptr,     /* innerObject */
         IterHook,
         false        /* isWrappedNative */
     }
 };
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -274,20 +274,20 @@ JS_ConvertArgumentsVA(JSContext *cx, uns
           case 'S':
           case 'W':
             val = *sp;
             str = ToString<CanGC>(cx, val);
             if (!str)
                 return false;
             *sp = STRING_TO_JSVAL(str);
             if (c == 'W') {
-                JSStableString *stable = str->ensureStable(cx);
-                if (!stable)
+                JSFlatString *flat = str->ensureFlat(cx);
+                if (!flat)
                     return false;
-                *va_arg(ap, const jschar **) = stable->chars().get();
+                *va_arg(ap, const jschar **) = flat->chars();
             } else {
                 *va_arg(ap, JSString **) = str;
             }
             break;
           case 'o':
             if (sp->isNullOrUndefined()) {
                 obj = nullptr;
             } else {
@@ -5480,25 +5480,25 @@ JS_Stringify(JSContext *cx, MutableHandl
 
 JS_PUBLIC_API(bool)
 JS_ParseJSON(JSContext *cx, const jschar *chars, uint32_t len, JS::MutableHandleValue vp)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
     RootedValue reviver(cx, NullValue());
-    return ParseJSONWithReviver(cx, JS::StableCharPtr(chars, len), len, reviver, vp);
+    return ParseJSONWithReviver(cx, ConstTwoByteChars(chars, len), len, reviver, vp);
 }
 
 JS_PUBLIC_API(bool)
 JS_ParseJSONWithReviver(JSContext *cx, const jschar *chars, uint32_t len, HandleValue reviver, MutableHandleValue vp)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    return ParseJSONWithReviver(cx, StableCharPtr(chars, len), len, reviver, vp);
+    return ParseJSONWithReviver(cx, ConstTwoByteChars(chars, len), len, reviver, vp);
 }
 
 /************************************************************************/
 
 JS_PUBLIC_API(void)
 JS_ReportError(JSContext *cx, const char *format, ...)
 {
     va_list ap;
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -35,30 +35,16 @@
 
 struct JSTracer;
 
 namespace JS {
 
 class Latin1CharsZ;
 class TwoByteChars;
 
-typedef mozilla::RangedPtr<const jschar> CharPtr;
-
-class StableCharPtr : public CharPtr {
-  public:
-    StableCharPtr(const StableCharPtr &s) : CharPtr(s) {}
-    StableCharPtr(const mozilla::RangedPtr<const jschar> &s) : CharPtr(s) {}
-    StableCharPtr(const jschar *s, size_t len) : CharPtr(s, len) {}
-    StableCharPtr(const jschar *pos, const jschar *start, size_t len)
-      : CharPtr(pos, start, len)
-    {}
-
-    using CharPtr::operator=;
-};
-
 #if defined JS_THREADSAFE && defined JS_DEBUG
 
 class JS_PUBLIC_API(AutoCheckRequestDepth)
 {
     JSContext *cx;
   public:
     AutoCheckRequestDepth(JSContext *cx);
     AutoCheckRequestDepth(js::ContextFriendFields *cx);
@@ -119,17 +105,16 @@ class JS_PUBLIC_API(AutoGCRooter) {
     enum {
         VALARRAY =     -2, /* js::AutoValueArray */
         PARSER =       -3, /* js::frontend::Parser */
         SHAPEVECTOR =  -4, /* js::AutoShapeVector */
         IDARRAY =      -6, /* js::AutoIdArray */
         DESCRIPTORS =  -7, /* js::AutoPropDescArrayRooter */
         ID =           -9, /* js::AutoIdRooter */
         VALVECTOR =   -10, /* js::AutoValueVector */
-        STRING =      -12, /* js::AutoStringRooter */
         IDVECTOR =    -13, /* js::AutoIdVector */
         OBJVECTOR =   -14, /* js::AutoObjectVector */
         STRINGVECTOR =-15, /* js::AutoStringVector */
         SCRIPTVECTOR =-16, /* js::AutoScriptVector */
         NAMEVECTOR =  -17, /* js::AutoNameVector */
         HASHABLEVALUE=-18, /* js::HashableValue */
         IONMASM =     -19, /* js::jit::MacroAssembler */
         IONALLOC =    -20, /* js::jit::AutoTempAllocatorRooter */
@@ -146,48 +131,16 @@ class JS_PUBLIC_API(AutoGCRooter) {
   private:
     AutoGCRooter ** const stackTop;
 
     /* No copy or assignment semantics. */
     AutoGCRooter(AutoGCRooter &ida) MOZ_DELETE;
     void operator=(AutoGCRooter &ida) MOZ_DELETE;
 };
 
-class AutoStringRooter : private AutoGCRooter {
-  public:
-    AutoStringRooter(JSContext *cx, JSString *str = nullptr
-                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-      : AutoGCRooter(cx, STRING), str_(str)
-    {
-        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    }
-
-    void setString(JSString *str) {
-        str_ = str;
-    }
-
-    JSString * string() const {
-        return str_;
-    }
-
-    JSString ** addr() {
-        return &str_;
-    }
-
-    JSString * const * addr() const {
-        return &str_;
-    }
-
-    friend void AutoGCRooter::trace(JSTracer *trc);
-
-  private:
-    JSString *str_;
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-};
-
 class AutoArrayRooter : private AutoGCRooter
 {
   public:
     AutoArrayRooter(JSContext *cx, size_t len, Value *vec
                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : AutoGCRooter(cx, len), array(vec), skip(cx, array, len)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -838,23 +838,17 @@ const Class ArrayObject::class_ = {
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     nullptr,
     nullptr,        /* call        */
     nullptr,        /* hasInstance */
     nullptr,        /* construct   */
-    nullptr,        /* trace       */
-    {
-        nullptr,    /* outerObject */
-        nullptr,    /* innerObject */
-        nullptr,    /* iteratorObject  */
-        false,      /* isWrappedNative */
-    }
+    nullptr         /* trace       */
 };
 
 static bool
 AddLengthProperty(ExclusiveContext *cx, HandleObject obj)
 {
     /*
      * Add the 'length' property for a newly created array,
      * and update the elements to be an empty array owned by the object.
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -516,33 +516,16 @@ static bool
 date_convert(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue vp)
 {
     JS_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID);
     JS_ASSERT(obj->is<DateObject>());
 
     return DefaultValue(cx, obj, (hint == JSTYPE_VOID) ? JSTYPE_STRING : hint, vp);
 }
 
-/*
- * Other Support routines and definitions
- */
-
-const Class DateObject::class_ = {
-    js_Date_str,
-    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
-    JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
-    JS_PropertyStub,         /* addProperty */
-    JS_DeletePropertyStub,   /* delProperty */
-    JS_PropertyStub,         /* getProperty */
-    JS_StrictPropertyStub,   /* setProperty */
-    JS_EnumerateStub,
-    JS_ResolveStub,
-    date_convert
-};
-
 /* for use by date_parse */
 
 static const char* const wtb[] = {
     "am", "pm",
     "monday", "tuesday", "wednesday", "thursday", "friday",
     "saturday", "sunday",
     "january", "february", "march", "april", "may", "june",
     "july", "august", "september", "october", "november", "december",
@@ -3022,61 +3005,57 @@ js_Date(JSContext *cx, unsigned argc, Va
     JSObject *obj = js_NewDateObjectMsec(cx, d);
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
-JSObject *
-js_InitDateClass(JSContext *cx, HandleObject obj)
+static bool
+FinishDateClassInit(JSContext *cx, HandleObject ctor, HandleObject proto)
 {
-    JS_ASSERT(obj->isNative());
-
-    Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
-
-    RootedObject dateProto(cx, global->createBlankPrototype(cx, &DateObject::class_));
-    if (!dateProto)
-        return nullptr;
-    dateProto->as<DateObject>().setUTCTime(GenericNaN());
-
-    RootedFunction ctor(cx);
-    ctor = global->createConstructor(cx, js_Date, cx->names().Date, MAXARGS);
-    if (!ctor)
-        return nullptr;
-
-    if (!LinkConstructorAndPrototype(cx, ctor, dateProto))
-        return nullptr;
-
-    if (!DefinePropertiesAndBrand(cx, ctor, nullptr, date_static_methods))
-        return nullptr;
+    proto->as<DateObject>().setUTCTime(GenericNaN());
 
     /*
-     * Define all Date.prototype.* functions, then brand for trace-jitted code.
      * Date.prototype.toGMTString has the same initial value as
      * Date.prototype.toUTCString.
      */
-    if (!JS_DefineFunctions(cx, dateProto, date_methods))
-        return nullptr;
     RootedValue toUTCStringFun(cx);
     RootedId toUTCStringId(cx, NameToId(cx->names().toUTCString));
     RootedId toGMTStringId(cx, NameToId(cx->names().toGMTString));
-    if (!baseops::GetProperty(cx, dateProto, toUTCStringId, &toUTCStringFun) ||
-        !baseops::DefineGeneric(cx, dateProto, toGMTStringId, toUTCStringFun,
-                                JS_PropertyStub, JS_StrictPropertyStub, 0))
+    return baseops::GetProperty(cx, proto, toUTCStringId, &toUTCStringFun) &&
+           baseops::DefineGeneric(cx, proto, toGMTStringId, toUTCStringFun,
+                                  JS_PropertyStub, JS_StrictPropertyStub, 0);
+}
+
+const Class DateObject::class_ = {
+    js_Date_str,
+    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
+    JS_PropertyStub,         /* addProperty */
+    JS_DeletePropertyStub,   /* delProperty */
+    JS_PropertyStub,         /* getProperty */
+    JS_StrictPropertyStub,   /* setProperty */
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    date_convert,
+    nullptr,                 /* finalize */
+    nullptr,                 /* call */
+    nullptr,                 /* hasInstance */
+    nullptr,                 /* construct */
+    nullptr,                 /* trace */
     {
-        return nullptr;
+        GenericCreateConstructor<js_Date, NAME_OFFSET(Date), MAXARGS>,
+        GenericCreatePrototype<&DateObject::class_>,
+        date_static_methods,
+        date_methods,
+        FinishDateClassInit
     }
-
-    if (!DefineConstructorAndPrototype(cx, global, JSProto_Date, ctor, dateProto))
-        return nullptr;
-
-    return dateProto;
-}
+};
 
 JS_FRIEND_API(JSObject *)
 js_NewDateObjectMsec(JSContext *cx, double msec_time)
 {
     JSObject *obj = NewBuiltinClassInstance(cx, &DateObject::class_);
     if (!obj)
         return nullptr;
     obj->as<DateObject>().setUTCTime(msec_time);
--- a/js/src/jsdate.h
+++ b/js/src/jsdate.h
@@ -11,19 +11,16 @@
 #ifndef jsdate_h
 #define jsdate_h
 
 #include "jstypes.h"
 
 #include "js/RootingAPI.h"
 #include "js/TypeDecls.h"
 
-extern JSObject *
-js_InitDateClass(JSContext *cx, JS::HandleObject obj);
-
 /*
  * These functions provide a C interface to the date/time object
  */
 
 /*
  * Construct a new Date Object from a time value given in milliseconds UTC
  * since the epoch.
  */
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -842,18 +842,18 @@ js_ReportUncaughtException(JSContext *cx
         if (str) {
             // Note that using |str| for |ucmessage| here is kind of wrong,
             // because |str| is supposed to be of the format
             // |ErrorName: ErrorMessage|, and |ucmessage| is supposed to
             // correspond to |ErrorMessage|. But this is what we've historically
             // done for duck-typed error objects.
             //
             // If only this stuff could get specced one day...
-            if (JSStableString *stable = str->ensureStable(cx))
-                report.ucmessage = stable->chars().get();
+            if (JSFlatString *flat = str->ensureFlat(cx))
+                report.ucmessage = flat->chars();
         }
     }
 
     JSAutoByteString bytesStorage;
     const char *bytes = nullptr;
     if (str)
         bytes = bytesStorage.encodeLatin1(cx, str);
     if (!bytes)
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -257,16 +257,17 @@ namespace js {
         JS_EnumerateStub,                                                               \
         JS_ResolveStub,                                                                 \
         js::proxy_Convert,                                                              \
         js::proxy_Finalize,      /* finalize    */                                      \
         callOp,                  /* call        */                                      \
         js::proxy_HasInstance,   /* hasInstance */                                      \
         constructOp,             /* construct   */                                      \
         js::proxy_Trace,         /* trace       */                                      \
+        JS_NULL_CLASS_SPEC,                                                             \
         ext,                                                                            \
         {                                                                               \
             js::proxy_LookupGeneric,                                                    \
             js::proxy_LookupProperty,                                                   \
             js::proxy_LookupElement,                                                    \
             js::proxy_LookupSpecial,                                                    \
             js::proxy_DefineGeneric,                                                    \
             js::proxy_DefineProperty,                                                   \
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -577,17 +577,17 @@ const Class JSFunction::class_ = {
     nullptr,                 /* construct   */
     fun_trace
 };
 
 const Class* const js::FunctionClassPtr = &JSFunction::class_;
 
 /* Find the body of a function (not including braces). */
 static bool
-FindBody(JSContext *cx, HandleFunction fun, StableCharPtr chars, size_t length,
+FindBody(JSContext *cx, HandleFunction fun, ConstTwoByteChars chars, size_t length,
          size_t *bodyStart, size_t *bodyEnd)
 {
     // We don't need principals, since those are only used for error reporting.
     CompileOptions options(cx);
     options.setFileAndLine("internal-findBody", 0)
            .setVersion(fun->nonLazyScript()->getVersion());
     AutoKeepAtoms keepAtoms(cx->perThreadData);
     TokenStream ts(cx, options, chars.get(), length, nullptr);
@@ -620,17 +620,17 @@ FindBody(JSContext *cx, HandleFunction f
         tt = ts.getToken();
     if (tt == TOK_ERROR)
         return false;
     bool braced = tt == TOK_LC;
     JS_ASSERT_IF(fun->isExprClosure(), !braced);
     *bodyStart = ts.currentToken().pos.begin;
     if (braced)
         *bodyStart += 1;
-    StableCharPtr end(chars.get() + length, chars.get(), length);
+    ConstTwoByteChars end(chars.get() + length, chars.get(), length);
     if (end[-1] == '}') {
         end--;
     } else {
         JS_ASSERT(!braced);
         for (; unicode::IsSpaceOrBOM2(end[-1]); end--)
             ;
     }
     *bodyEnd = end - chars;
@@ -688,21 +688,21 @@ js::FunctionToString(JSContext *cx, Hand
         !JSScript::loadSource(cx, script->scriptSource(), &haveSource))
     {
         return nullptr;
     }
     if (haveSource) {
         RootedString srcStr(cx, script->sourceData(cx));
         if (!srcStr)
             return nullptr;
-        Rooted<JSStableString *> src(cx, srcStr->ensureStable(cx));
+        Rooted<JSFlatString *> src(cx, srcStr->ensureFlat(cx));
         if (!src)
             return nullptr;
 
-        StableCharPtr chars = src->chars();
+        ConstTwoByteChars chars(src->chars(), src->length());
         bool exprBody = fun->isExprClosure();
 
         // The source data for functions created by calling the Function
         // constructor is only the function's body.  This depends on the fact,
         // asserted below, that in Function("function f() {}"), the inner
         // function's sourceStart points to the '(', not the 'f'.
         bool funCon = !fun->isArrow() &&
                       script->sourceStart() == 0 &&
@@ -1538,17 +1538,17 @@ FunctionConstructor(JSContext *cx, unsig
          * free collected_args and its tokenstream in one swoop.
          */
         LifoAllocScope las(&cx->tempLifoAlloc());
         jschar *cp = cx->tempLifoAlloc().newArray<jschar>(args_length + 1);
         if (!cp) {
             js_ReportOutOfMemory(cx);
             return false;
         }
-        StableCharPtr collected_args(cp, args_length + 1);
+        ConstTwoByteChars collected_args(cp, args_length + 1);
 
         /*
          * Concatenate the arguments into the new string, separated by commas.
          */
         for (unsigned i = 0; i < n; i++) {
             arg = args[i].toString();
             size_t arg_length = arg->length();
             const jschar *arg_chars = arg->getChars(cx);
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -834,16 +834,17 @@ const Class PropertyIteratorObject::clas
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     finalize,
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
     trace,
+    JS_NULL_CLASS_SPEC,
     {
         nullptr,             /* outerObject    */
         nullptr,             /* innerObject    */
         iterator_iteratorObject,
     }
 };
 
 enum {
@@ -1490,16 +1491,17 @@ const Class LegacyGeneratorObject::class
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     FinalizeGenerator<LegacyGeneratorObject>,
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
     TraceGenerator<LegacyGeneratorObject>,
+    JS_NULL_CLASS_SPEC,
     {
         nullptr,             /* outerObject    */
         nullptr,             /* innerObject    */
         iterator_iteratorObject,
     }
 };
 
 const Class StarGeneratorObject::class_ = {
@@ -1512,16 +1514,17 @@ const Class StarGeneratorObject::class_ 
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     FinalizeGenerator<StarGeneratorObject>,
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
     TraceGenerator<StarGeneratorObject>,
+    JS_NULL_CLASS_SPEC,
     {
         nullptr,             /* outerObject    */
         nullptr,             /* innerObject    */
         iterator_iteratorObject,
     }
 };
 
 /*
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -5843,17 +5843,17 @@ js_DumpStackFrame(JSContext *cx, StackFr
         fputc('\n', stderr);
 
         fprintf(stderr, "file %s line %u\n",
                 i.script()->filename(), (unsigned) i.script()->lineno());
 
         if (jsbytecode *pc = i.pc()) {
             fprintf(stderr, "  pc = %p\n", pc);
             fprintf(stderr, "  current op: %s\n", js_CodeName[*pc]);
-            MaybeDumpObject("blockChain", i.script()->getBlockScope(pc));
+            MaybeDumpObject("staticScope", i.script()->getStaticScope(pc));
         }
         MaybeDumpValue("this", i.thisv());
         if (!i.isJit()) {
             fprintf(stderr, "  rval: ");
             dumpValue(i.interpFrame()->returnValue());
             fputc('\n', stderr);
         }
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1605,16 +1605,19 @@ extern bool
 js_ReportGetterOnlyAssignment(JSContext *cx, bool strict);
 
 extern unsigned
 js_InferFlags(JSContext *cx, unsigned defaultFlags);
 
 
 namespace js {
 
+const Class *
+ProtoKeyToClass(JSProtoKey key);
+
 JSObject *
 GetClassPrototypePure(GlobalObject *global, JSProtoKey protoKey);
 
 extern bool
 SetClassAndProto(JSContext *cx, HandleObject obj,
                  const Class *clasp, Handle<TaggedProto> proto, bool *succeeded);
 
 extern JSObject *
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -769,18 +769,18 @@ Revive(JSContext *cx, HandleValue revive
     if (!JSObject::defineProperty(cx, obj, cx->names().empty, vp))
         return false;
 
     Rooted<jsid> id(cx, NameToId(cx->names().empty));
     return Walk(cx, obj, id, reviver, vp);
 }
 
 bool
-js::ParseJSONWithReviver(JSContext *cx, StableCharPtr chars, size_t length, HandleValue reviver,
-                         MutableHandleValue vp)
+js::ParseJSONWithReviver(JSContext *cx, ConstTwoByteChars chars, size_t length,
+                         HandleValue reviver, MutableHandleValue vp)
 {
     /* 15.12.2 steps 2-3. */
     JSONParser parser(cx, chars, length);
     if (!parser.parse(vp))
         return false;
 
     /* 15.12.2 steps 4-5. */
     if (js_IsCallable(reviver))
@@ -805,26 +805,27 @@ json_parse(JSContext *cx, unsigned argc,
 
     /* Step 1. */
     JSString *str = (args.length() >= 1)
                     ? ToString<CanGC>(cx, args[0])
                     : cx->names().undefined;
     if (!str)
         return false;
 
-    JSStableString *stable = str->ensureStable(cx);
-    if (!stable)
+    Rooted<JSFlatString*> flat(cx, str->ensureFlat(cx));
+    if (!flat)
         return false;
 
-    JS::Anchor<JSString *> anchor(stable);
+    JS::Anchor<JSString *> anchor(flat);
 
     RootedValue reviver(cx, (argc >= 2) ? args[1] : UndefinedValue());
 
     /* Steps 2-5. */
-    return ParseJSONWithReviver(cx, stable->chars(), stable->length(), reviver, args.rval());
+    return ParseJSONWithReviver(cx, ConstTwoByteChars(flat->chars(), flat->length()),
+                                flat->length(), reviver, args.rval());
 }
 
 /* ES5 15.12.3. */
 bool
 json_stringify(JSContext *cx, unsigned argc, Value *vp)
 {
     RootedObject replacer(cx, (argc >= 2 && vp[3].isObject())
                               ? &vp[3].toObject()
--- a/js/src/json.h
+++ b/js/src/json.h
@@ -20,14 +20,14 @@ js_InitJSONClass(JSContext *cx, js::Hand
 
 extern bool
 js_Stringify(JSContext *cx, js::MutableHandleValue vp, JSObject *replacer,
              js::Value space, js::StringBuffer &sb);
 
 namespace js {
 
 extern bool
-ParseJSONWithReviver(JSContext *cx, JS::StableCharPtr chars, size_t length, HandleValue reviver,
-                     MutableHandleValue vp);
+ParseJSONWithReviver(JSContext *cx, JS::ConstTwoByteChars chars, size_t length,
+                     HandleValue reviver, MutableHandleValue vp);
 
 } // namespace js
 
 #endif /* json_h */
--- a/js/src/jsonparser.cpp
+++ b/js/src/jsonparser.cpp
@@ -55,17 +55,17 @@ JSONParser::trace(JSTracer *trc)
             }
         }
     }
 }
 
 void
 JSONParser::getTextPosition(uint32_t *column, uint32_t *line)
 {
-    StableCharPtr ptr = begin;
+    ConstTwoByteChars ptr = begin;
     uint32_t col = 1;
     uint32_t row = 1;
     for (; ptr < current; ptr++) {
         if (*ptr == '\n' || *ptr == '\r') {
             ++row;
             col = 1;
             // \r\n is treated as a single newline.
             if (ptr + 1 < current && *ptr == '\r' && *(ptr + 1) == '\n')
--- a/js/src/jsonparser.h
+++ b/js/src/jsonparser.h
@@ -18,18 +18,18 @@ class MOZ_STACK_CLASS JSONParser : priva
 {
   public:
     enum ErrorHandling { RaiseError, NoError };
 
   private:
     /* Data members */
 
     JSContext * const cx;
-    StableCharPtr current;
-    const StableCharPtr begin, end;
+    JS::ConstTwoByteChars current;
+    const JS::ConstTwoByteChars begin, end;
 
     Value v;
 
     const ErrorHandling errorHandling;
 
     enum Token { String, Number, True, False, Null,
                  ArrayOpen, ArrayClose,
                  ObjectOpen, ObjectClose,
@@ -102,17 +102,17 @@ class MOZ_STACK_CLASS JSONParser : priva
 #ifdef DEBUG
     Token lastToken;
 #endif
 
   public:
     /* Public API */
 
     /* Create a parser for the provided JSON data. */
-    JSONParser(JSContext *cx, JS::StableCharPtr data, size_t length,
+    JSONParser(JSContext *cx, JS::ConstTwoByteChars data, size_t length,
                ErrorHandling errorHandling = RaiseError)
       : AutoGCRooter(cx, JSONPARSER),
         cx(cx),
         current(data),
         begin(data),
         end((data + length).get(), data.get(), length),
         errorHandling(errorHandling),
         stack(cx),
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -1638,17 +1638,19 @@ JSAtom *
 ExpressionDecompiler::loadAtom(jsbytecode *pc)
 {
     return script->getAtom(GET_UINT32_INDEX(pc));
 }
 
 JSAtom *
 ExpressionDecompiler::findLetVar(jsbytecode *pc, uint32_t depth)
 {
-    for (JSObject *chain = script->getBlockScope(pc); chain; chain = chain->getParent()) {
+    for (JSObject *chain = script->getStaticScope(pc); chain; chain = chain->getParent()) {
+        if (!chain->is<StaticBlockObject>())
+            continue;
         StaticBlockObject &block = chain->as<StaticBlockObject>();
         uint32_t blockDepth = block.stackDepth();
         uint32_t blockCount = block.slotCount();
         if (uint32_t(depth - blockDepth) < uint32_t(blockCount)) {
             for (Shape::Range<NoGC> r(block.lastProperty()); !r.empty(); r.popFront()) {
                 const Shape &shape = r.front();
                 if (shape.shortid() == int(depth - blockDepth))
                     return JSID_TO_ATOM(shape.propid());
--- a/js/src/jsprototypes.h
+++ b/js/src/jsprototypes.h
@@ -56,17 +56,17 @@
 
 #define JS_FOR_PROTOTYPES(real,imaginary) \
     imaginary(Null,              0,     js_InitNullClass,          dummy) \
     real(Object,                 1,     js_InitObjectClass,        &JSObject::class_) \
     real(Function,               2,     js_InitFunctionClass,      &JSFunction::class_) \
     real(Array,                  3,     js_InitArrayClass,         OCLASP(Array)) \
     real(Boolean,                4,     js_InitBooleanClass,       OCLASP(Boolean)) \
     real(JSON,                   5,     js_InitJSONClass,          CLASP(JSON)) \
-    real(Date,                   6,     js_InitDateClass,          OCLASP(Date)) \
+    real(Date,                   6,     js_InitViaClassSpec,       OCLASP(Date)) \
     real(Math,                   7,     js_InitMathClass,          CLASP(Math)) \
     real(Number,                 8,     js_InitNumberClass,        OCLASP(Number)) \
     real(String,                 9,     js_InitStringClass,        OCLASP(String)) \
     real(RegExp,                10,     js_InitRegExpClass,        OCLASP(RegExp)) \
     real(Error,                 11,     js_InitExceptionClasses,   OCLASP(Error)) \
     real(InternalError,         12,     js_InitExceptionClasses,   OCLASP(Error)) \
     real(EvalError,             13,     js_InitExceptionClasses,   OCLASP(Error)) \
     real(RangeError,            14,     js_InitExceptionClasses,   OCLASP(Error)) \
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -133,17 +133,16 @@ typedef struct JSPropertySpec           
 typedef struct JSRuntime                    JSRuntime;
 typedef struct JSSecurityCallbacks          JSSecurityCallbacks;
 typedef struct JSStructuredCloneCallbacks   JSStructuredCloneCallbacks;
 typedef struct JSStructuredCloneReader      JSStructuredCloneReader;
 typedef struct JSStructuredCloneWriter      JSStructuredCloneWriter;
 typedef struct JSTracer                     JSTracer;
 
 class                                       JSFlatString;
-class                                       JSStableString;  // long story
 
 #ifdef JS_THREADSAFE
 typedef struct PRCallOnceType   JSCallOnceType;
 #else
 typedef bool                    JSCallOnceType;
 #endif
 typedef bool                    (*JSInitCallback)(void);
 
--- a/js/src/jsreflect.cpp
+++ b/js/src/jsreflect.cpp
@@ -3266,27 +3266,25 @@ reflect_parse(JSContext *cx, uint32_t ar
         }
     }
 
     /* Extract the builder methods first to report errors before parsing. */
     ASTSerializer serialize(cx, loc, filename, lineno);
     if (!serialize.init(builder))
         return false;
 
-    JSStableString *stable = src->ensureStable(cx);
-    if (!stable)
+    JSFlatString *flat = src->ensureFlat(cx);
+    if (!flat)
         return false;
 
-    const StableCharPtr chars = stable->chars();
-    size_t length = stable->length();
     CompileOptions options(cx);
     options.setFileAndLine(filename, lineno);
     options.setCanLazilyParse(false);
-    Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, chars.get(), length,
-                                    /* foldConstants = */ false, nullptr, nullptr);
+    Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(), options, flat->chars(),
+                                    flat->length(), /* foldConstants = */ false, nullptr, nullptr);
 
     serialize.setParser(&parser);
 
     ParseNode *pn = parser.parse(nullptr);
     if (!pn)
         return false;
 
     RootedValue val(cx);
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -408,27 +408,27 @@ js::XDRScriptConst(XDRState<mode> *xdr, 
 
 template bool
 js::XDRScriptConst(XDRState<XDR_ENCODE> *, MutableHandleValue);
 
 template bool
 js::XDRScriptConst(XDRState<XDR_DECODE> *, MutableHandleValue);
 
 static inline uint32_t
-FindBlockIndex(JSScript *script, StaticBlockObject &block)
+FindScopeObjectIndex(JSScript *script, NestedScopeObject &scope)
 {
     ObjectArray *objects = script->objects();
     HeapPtrObject *vector = objects->vector;
     unsigned length = objects->length;
     for (unsigned i = 0; i < length; ++i) {
-        if (vector[i] == &block)
+        if (vector[i] == &scope)
             return i;
     }
 
-    MOZ_ASSUME_UNREACHABLE("Block not found");
+    MOZ_ASSUME_UNREACHABLE("Scope not found");
 }
 
 static bool
 SaveSharedScriptData(ExclusiveContext *, Handle<JSScript *>, SharedScriptData *, uint32_t);
 
 enum XDRClassKind {
     CK_BlockObject = 0,
     CK_JSFunction  = 1,
@@ -748,17 +748,17 @@ js::XDRScript(XDRState<mode> *xdr, Handl
                 return false;
             if (mode == XDR_DECODE)
                 vector[i].init(val);
         }
     }
 
     /*
      * Here looping from 0-to-length to xdr objects is essential to ensure that
-     * all references to enclosing blocks (via FindBlockIndex below) happen
+     * all references to enclosing blocks (via FindScopeObjectIndex below) happen
      * after the enclosing block has been XDR'd.
      */
     for (i = 0; i != nobjects; ++i) {
         HeapPtr<JSObject> *objp = &script->objects()->vector[i];
         XDRClassKind classk;
 
         if (mode == XDR_ENCODE) {
             JSObject *obj = *objp;
@@ -775,18 +775,19 @@ js::XDRScript(XDRState<mode> *xdr, Handl
         if (!xdr->codeEnum32(&classk))
             return false;
 
         switch (classk) {
           case CK_BlockObject: {
             /* Code the nested block's enclosing scope. */
             uint32_t blockEnclosingScopeIndex = 0;
             if (mode == XDR_ENCODE) {
-                if (StaticBlockObject *block = (*objp)->as<StaticBlockObject>().enclosingBlock())
-                    blockEnclosingScopeIndex = FindBlockIndex(script, *block);
+                NestedScopeObject &scope = (*objp)->as<NestedScopeObject>();
+                if (NestedScopeObject *enclosing = scope.enclosingNestedScope())
+                    blockEnclosingScopeIndex = FindScopeObjectIndex(script, *enclosing);
                 else
                     blockEnclosingScopeIndex = UINT32_MAX;
             }
             if (!xdr->codeUint32(&blockEnclosingScopeIndex))
                 return false;
             Rooted<JSObject*> blockEnclosingScope(cx);
             if (mode == XDR_DECODE) {
                 if (blockEnclosingScopeIndex != UINT32_MAX) {
@@ -812,17 +813,17 @@ js::XDRScript(XDRState<mode> *xdr, Handl
                 if (!innerScript)
                     return false;
                 RootedObject staticScope(cx, innerScript->enclosingStaticScope());
                 StaticScopeIter<NoGC> ssi(staticScope);
                 if (ssi.done() || ssi.type() == StaticScopeIter<NoGC>::FUNCTION) {
                     JS_ASSERT(ssi.done() == !fun);
                     funEnclosingScopeIndex = UINT32_MAX;
                 } else {
-                    funEnclosingScopeIndex = FindBlockIndex(script, ssi.block());
+                    funEnclosingScopeIndex = FindScopeObjectIndex(script, ssi.block());
                     JS_ASSERT(funEnclosingScopeIndex < i);
                 }
             }
             if (!xdr->codeUint32(&funEnclosingScopeIndex))
                 return false;
             Rooted<JSObject*> funEnclosingScope(cx);
             if (mode == XDR_DECODE) {
                 if (funEnclosingScopeIndex == UINT32_MAX) {
@@ -1255,28 +1256,25 @@ ScriptSource::chars(JSContext *cx, const
         }
 
         return decompressed;
     }
 #endif
     return data.source;
 }
 
-JSStableString *
+JSFlatString *
 ScriptSource::substring(JSContext *cx, uint32_t start, uint32_t stop)
 {
     JS_ASSERT(start <= stop);
     SourceDataCache::AutoSuppressPurge asp(cx);
     const jschar *chars = this->chars(cx, asp);
     if (!chars)
         return nullptr;
-    JSFlatString *flatStr = js_NewStringCopyN<CanGC>(cx, chars + start, stop - start);
-    if (!flatStr)
-        return nullptr;
-    return flatStr->ensureStable(cx);
+    return js_NewStringCopyN<CanGC>(cx, chars + start, stop - start);
 }
 
 bool
 ScriptSource::setSourceCopy(ExclusiveContext *cx, const jschar *src, uint32_t length,
                             bool argumentsNotIncluded, SourceCompressionTask *task)
 {
     JS_ASSERT(!hasSourceData());
     length_ = length;
@@ -2491,42 +2489,42 @@ js::CloneScript(JSContext *cx, HandleObj
     /* Objects */
 
     AutoObjectVector objects(cx);
     if (nobjects != 0) {
         HeapPtrObject *vector = src->objects()->vector;
         for (unsigned i = 0; i < nobjects; i++) {
             RootedObject obj(cx, vector[i]);
             RootedObject clone(cx);
-            if (obj->is<StaticBlockObject>()) {
-                Rooted<StaticBlockObject*> innerBlock(cx, &obj->as<StaticBlockObject>());
+            if (obj->is<NestedScopeObject>()) {
+                Rooted<NestedScopeObject*> innerBlock(cx, &obj->as<NestedScopeObject>());
 
                 RootedObject enclosingScope(cx);
-                if (StaticBlockObject *enclosingBlock = innerBlock->enclosingBlock())
-                    enclosingScope = objects[FindBlockIndex(src, *enclosingBlock)];
+                if (NestedScopeObject *enclosingBlock = innerBlock->enclosingNestedScope())
+                    enclosingScope = objects[FindScopeObjectIndex(src, *enclosingBlock)];
                 else
                     enclosingScope = fun;
 
-                clone = CloneStaticBlockObject(cx, enclosingScope, innerBlock);
+                clone = CloneNestedScopeObject(cx, enclosingScope, innerBlock);
             } else if (obj->is<JSFunction>()) {
                 RootedFunction innerFun(cx, &obj->as<JSFunction>());
                 if (innerFun->isNative()) {
                     assertSameCompartment(cx, innerFun);
                     clone = innerFun;
                 } else {
                     if (innerFun->isInterpretedLazy()) {
                         AutoCompartment ac(cx, innerFun);
                         if (!innerFun->getOrCreateScript(cx))
                             return nullptr;
                     }
                     RootedObject staticScope(cx, innerFun->nonLazyScript()->enclosingStaticScope());
                     StaticScopeIter<CanGC> ssi(cx, staticScope);
                     RootedObject enclosingScope(cx);
                     if (!ssi.done() && ssi.type() == StaticScopeIter<CanGC>::BLOCK)
-                        enclosingScope = objects[FindBlockIndex(src, ssi.block())];
+                        enclosingScope = objects[FindScopeObjectIndex(src, ssi.block())];
                     else
                         enclosingScope = fun;
 
                     clone = CloneFunctionAndScript(cx, enclosingScope, innerFun);
                 }
             } else {
                 /*
                  * Clone object literals emitted for the JSOP_NEWOBJECT opcode. We only emit that
@@ -2981,31 +2979,31 @@ LazyScript::markChildren(JSTracer *trc)
 
 void
 LazyScript::finalize(FreeOp *fop)
 {
     if (table_)
         fop->free_(table_);
 }
 
-StaticBlockObject *
-JSScript::getBlockScope(jsbytecode *pc)
+NestedScopeObject *
+JSScript::getStaticScope(jsbytecode *pc)
 {
     JS_ASSERT(containsPC(pc));
 
     if (!hasBlockScopes())
         return nullptr;
 
     ptrdiff_t offset = pc - main();
 
     if (offset < 0)
         return nullptr;
 
     BlockScopeArray *scopes = blockScopes();
-    StaticBlockObject *blockChain = nullptr;
+    NestedScopeObject *blockChain = nullptr;
 
     // Find the innermost block chain using a binary search.
     size_t bottom = 0;
     size_t top = scopes->length;
 
     while (bottom < top) {
         size_t mid = bottom + (top - bottom) / 2;
         const BlockScopeNote *note = &scopes->vector[mid];
@@ -3020,17 +3018,17 @@ JSScript::getBlockScope(jsbytecode *pc)
                 const BlockScopeNote *checkNote = &scopes->vector[check];
                 JS_ASSERT(checkNote->start <= offset);
                 if (offset < checkNote->start + checkNote->length) {
                     // We found a matching block chain but there may be inner ones
                     // at a higher block chain index than mid. Continue the binary search.
                     if (checkNote->index == BlockScopeNote::NoBlockScopeIndex)
                         blockChain = nullptr;
                     else
-                        blockChain = &getObject(checkNote->index)->as<StaticBlockObject>();
+                        blockChain = &getObject(checkNote->index)->as<NestedScopeObject>();
                     break;
                 }
                 if (checkNote->parent == UINT32_MAX)
                     break;
                 check = checkNote->parent;
             }
             bottom = mid + 1;
         } else {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -39,17 +39,17 @@ namespace jit {
 
 class BreakpointSite;
 class BindingIter;
 class LazyScript;
 class RegExpObject;
 struct SourceCompressionTask;
 class Shape;
 class WatchpointMap;
-class StaticBlockObject;
+class NestedScopeObject;
 
 namespace analyze {
     class ScriptAnalysis;
 }
 
 namespace frontend {
     class BytecodeEmitter;
 }
@@ -93,17 +93,17 @@ namespace js {
 // are popping the block chain in preparation for a goto.  These exits are also
 // nested with respect to outer scopes.  The scopes in these exits are indicated
 // by the "index" field, just like any other block.  If a nonlocal exit pops the
 // last block scope, the index will be NoBlockScopeIndex.
 //
 struct BlockScopeNote {
     static const uint32_t NoBlockScopeIndex = UINT32_MAX;
 
-    uint32_t        index;      // Index of StaticScopeObject in the object
+    uint32_t        index;      // Index of NestedScopeObject in the object
                                 // array, or NoBlockScopeIndex if there is no
                                 // block scope in this range.
     uint32_t        start;      // Bytecode offset at which this scope starts,
                                 // from script->main().
     uint32_t        length;     // Bytecode length of scope.
     uint32_t        parent;     // Index of parent block scope in notes, or UINT32_MAX.
 };
 
@@ -425,17 +425,17 @@ class ScriptSource
         JS_ASSERT(hasSourceData());
         return length_;
     }
     bool argumentsNotIncluded() const {
         JS_ASSERT(hasSourceData());
         return argumentsNotIncluded_;
     }
     const jschar *chars(JSContext *cx, const SourceDataCache::AutoSuppressPurge &asp);
-    JSStableString *substring(JSContext *cx, uint32_t start, uint32_t stop);
+    JSFlatString *substring(JSContext *cx, uint32_t start, uint32_t stop);
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
     // XDR handling
     template <XDRMode mode>
     bool performXDR(XDRState<mode> *xdr);
 
     bool setFilename(ExclusiveContext *cx, const char *filename);
     const char *filename() const {
@@ -1417,17 +1417,17 @@ class JSScript : public js::gc::Barriere
     inline js::RegExpObject *getRegExp(jsbytecode *pc);
 
     const js::Value &getConst(size_t index) {
         js::ConstArray *arr = consts();
         JS_ASSERT(index < arr->length);
         return arr->vector[index];
     }
 
-    js::StaticBlockObject *getBlockScope(jsbytecode *pc);
+    js::NestedScopeObject *getStaticScope(jsbytecode *pc);
 
     /*
      * The isEmpty method tells whether this script has code that computes any
      * result (not return value, result AKA normal completion value) other than
      * JSVAL_VOID, or any other effects.
      */
     bool isEmpty() const {
         if (length() > 3)
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -2644,26 +2644,26 @@ FlattenSubstrings(JSContext *cx, const j
     }
     JS_ASSERT(pos == outputLen);
 
     buf[outputLen] = 0;
     return str;
 }
 
 static JSString *
-AppendSubstrings(JSContext *cx, Handle<JSStableString*> stableStr,
+AppendSubstrings(JSContext *cx, Handle<JSFlatString*> flatStr,
                  const StringRange *ranges, size_t rangesLen)
 {
     JS_ASSERT(rangesLen);
 
     /* For single substrings, construct a dependent string. */
     if (rangesLen == 1)
-        return js_NewDependentString(cx, stableStr, ranges[0].start, ranges[0].length);
-
-    const jschar *chars = stableStr->getChars(cx);
+        return js_NewDependentString(cx, flatStr, ranges[0].start, ranges[0].length);
+
+    const jschar *chars = flatStr->getChars(cx);
     if (!chars)
         return nullptr;
 
     /* Collect substrings into a rope */
     size_t i = 0;
     RopeBuilder rope(cx);
     RootedString part(cx, nullptr);
     while (i < rangesLen) {
@@ -2675,17 +2675,17 @@ AppendSubstrings(JSContext *cx, Handle<J
             if (substrLen + ranges[end].length > JSShortString::MAX_SHORT_LENGTH)
                 break;
             substrLen += ranges[end].length;
         }
 
         if (i == end) {
             /* Not even one range fits JSShortString, use DependentString */
             const StringRange &sr = ranges[i++];
-            part = js_NewDependentString(cx, stableStr, sr.start, sr.length);
+            part = js_NewDependentString(cx, flatStr, sr.start, sr.length);
         } else {
             /* Copy the ranges (linearly) into a JSShortString */
             part = FlattenSubstrings(cx, chars, ranges + i, end - i, substrLen);
             i = end;
         }
 
         if (!part)
             return nullptr;
@@ -2696,36 +2696,36 @@ AppendSubstrings(JSContext *cx, Handle<J
     }
 
     return rope.result();
 }
 
 static bool
 StrReplaceRegexpRemove(JSContext *cx, HandleString str, RegExpShared &re, MutableHandleValue rval)
 {
-    Rooted<JSStableString*> stableStr(cx, str->ensureStable(cx));
-    if (!stableStr)
+    Rooted<JSFlatString*> flatStr(cx, str->ensureFlat(cx));
+    if (!flatStr)
         return false;
 
     Vector<StringRange, 16, SystemAllocPolicy> ranges;
 
-    StableCharPtr chars = stableStr->chars();
-    size_t charsLen = stableStr->length();
+    size_t charsLen = flatStr->length();
 
     MatchPair match;
     size_t startIndex = 0; /* Index used for iterating through the string. */
     size_t lastIndex = 0;  /* Index after last successful match. */
     size_t lazyIndex = 0;  /* Index before last successful match. */
 
     /* Accumulate StringRanges for unmatched substrings. */
     while (startIndex <= charsLen) {
         if (!JS_CHECK_OPERATION_LIMIT(cx))
             return false;
 
-        RegExpRunStatus status = re.executeMatchOnly(cx, chars.get(), charsLen, &startIndex, match);
+        RegExpRunStatus status =
+            re.executeMatchOnly(cx, flatStr->chars(), charsLen, &startIndex, match);
         if (status == RegExpRunStatus_Error)
             return false;
         if (status == RegExpRunStatus_Success_NotFound)
             break;
 
         /* Include the latest unmatched substring. */
         if (size_t(match.start) > lastIndex) {
             if (!ranges.append(StringRange(lastIndex, match.start - lastIndex)))
@@ -2741,37 +2741,37 @@ StrReplaceRegexpRemove(JSContext *cx, Ha
         /* Non-global removal executes at most once. */
         if (!re.global())
             break;
     }
 
     /* If unmatched, return the input string. */
     if (!lastIndex) {
         if (startIndex > 0)
-            cx->global()->getRegExpStatics()->updateLazily(cx, stableStr, &re, lazyIndex);
+            cx->global()->getRegExpStatics()->updateLazily(cx, flatStr, &re, lazyIndex);
         rval.setString(str);
         return true;
     }
 
     /* The last successful match updates the RegExpStatics. */
-    cx->global()->getRegExpStatics()->updateLazily(cx, stableStr, &re, lazyIndex);
+    cx->global()->getRegExpStatics()->updateLazily(cx, flatStr, &re, lazyIndex);
 
     /* Include any remaining part of the string. */
     if (lastIndex < charsLen) {
         if (!ranges.append(StringRange(lastIndex, charsLen - lastIndex)))
             return false;
     }
 
     /* Handle the empty string before calling .begin(). */
     if (ranges.empty()) {
         rval.setString(cx->runtime()->emptyString);
         return true;
     }
 
-    JSString *result = AppendSubstrings(cx, stableStr, ranges.begin(), ranges.length());
+    JSString *result = AppendSubstrings(cx, flatStr, ranges.begin(), ranges.length());
     if (!result)
         return false;
 
     rval.setString(result);
     return true;
 }
 
 static inline bool
@@ -3948,26 +3948,26 @@ js_InitStringClass(JSContext *cx, Handle
      */
     if (!JS_DefineFunctions(cx, global, string_functions))
         return nullptr;
 
     return proto;
 }
 
 template <AllowGC allowGC>
-JSStableString *
+JSFlatString *
 js_NewString(ThreadSafeContext *cx, jschar *chars, size_t length)
 {
-    return JSStableString::new_<allowGC>(cx, chars, length);
+    return JSFlatString::new_<allowGC>(cx, chars, length);
 }
 
-template JSStableString *
+template JSFlatString *
 js_NewString<CanGC>(ThreadSafeContext *cx, jschar *chars, size_t length);
 
-template JSStableString *
+template JSFlatString *
 js_NewString<NoGC>(ThreadSafeContext *cx, jschar *chars, size_t length);
 
 JSLinearString *
 js_NewDependentString(JSContext *cx, JSString *baseArg, size_t start, size_t length)
 {
     if (length == 0)
         return cx->emptyString();
 
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -15,17 +15,16 @@
 
 #include "gc/Rooting.h"
 #include "js/RootingAPI.h"
 #include "vm/Unicode.h"
 
 class JSAutoByteString;
 class JSFlatString;
 class JSLinearString;
-class JSStableString;
 
 namespace js {
 
 class StringBuffer;
 
 class MutatingRopeSegmentRange;
 
 template <AllowGC allowGC>
@@ -95,17 +94,17 @@ extern const char js_unescape_str[];
 extern const char js_uneval_str[];
 extern const char js_decodeURI_str[];
 extern const char js_encodeURI_str[];
 extern const char js_decodeURIComponent_str[];
 extern const char js_encodeURIComponent_str[];
 
 /* GC-allocate a string descriptor for the given malloc-allocated chars. */
 template <js::AllowGC allowGC>
-extern JSStableString *
+extern JSFlatString *
 js_NewString(js::ThreadSafeContext *cx, jschar *chars, size_t length);
 
 extern JSLinearString *
 js_NewDependentString(JSContext *cx, JSString *base, size_t start, size_t length);
 
 /* Copy a counted string and GC-allocate a descriptor for it. */
 template <js::AllowGC allowGC>
 extern JSFlatString *
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -122,16 +122,17 @@ static bool enableTypeInference = true;
 static bool enableDisassemblyDumps = false;
 static bool enableIon = true;
 static bool enableBaseline = true;
 static bool enableAsmJS = true;
 
 static bool printTiming = false;
 static const char *jsCacheDir = nullptr;
 static const char *jsCacheAsmJSPath = nullptr;
+static bool jsCachingEnabled = true;
 mozilla::Atomic<int32_t> jsCacheOpened(false);
 
 static bool
 SetTimeoutValue(JSContext *cx, double t);
 
 static bool
 InitWatchdog(JSRuntime *rt);
 
@@ -3994,17 +3995,26 @@ WithSourceHook(JSContext *cx, unsigned a
     js::SetSourceHook(cx->runtime(), savedHook);
     return result;
 }
 
 static bool
 IsCachingEnabled(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    args.rval().setBoolean(jsCacheAsmJSPath != nullptr);
+    args.rval().setBoolean(jsCachingEnabled && jsCacheAsmJSPath != nullptr);
+    return true;
+}
+
+static bool
+SetCachingEnabled(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    jsCachingEnabled = ToBoolean(args.get(0));
+    args.rval().setUndefined();
     return true;
 }
 
 static const JSFunctionSpecWithHelp shell_functions[] = {
     JS_FN_HELP("version", Version, 0, 0,
 "version([number])",
 "  Get or force a script compilation version number."),
 
@@ -4309,17 +4319,21 @@ static const JSFunctionSpecWithHelp shel
 
     JS_FN_HELP("objectEmulatingUndefined", ObjectEmulatingUndefined, 0, 0,
 "objectEmulatingUndefined()",
 "  Return a new object obj for which typeof obj === \"undefined\", obj == null\n"
 "  and obj == undefined (and vice versa for !=), and ToBoolean(obj) === false.\n"),
 
     JS_FN_HELP("isCachingEnabled", IsCachingEnabled, 0, 0,
 "isCachingEnabled()",
-"  Return whether --js-cache was set."),
+"  Return whether JS caching is enabled."),
+
+    JS_FN_HELP("setCachingEnabled", SetCachingEnabled, 1, 0,
+"setCachingEnabled(b)",
+"  Enable or disable JS caching."),
 
     JS_FS_HELP_END
 };
 
 static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = {
     JS_FN_HELP("clone", Clone, 1, 0,
 "clone(fun[, scope])",
 "  Clone function object."),
@@ -5068,17 +5082,17 @@ class ScopedFileDesc
 // fully serialized and flushed to disk.
 static const uint32_t asmJSCacheCookie = 0xabbadaba;
 
 static bool
 ShellOpenAsmJSCacheEntryForRead(HandleObject global, const jschar *begin, const jschar *limit,
                                 size_t *serializedSizeOut, const uint8_t **memoryOut,
                                 intptr_t *handleOut)
 {
-    if (!jsCacheAsmJSPath)
+    if (!jsCachingEnabled || !jsCacheAsmJSPath)
         return false;
 
     ScopedFileDesc fd(open(jsCacheAsmJSPath, O_RDWR), ScopedFileDesc::READ_LOCK);
     if (fd == -1)
         return false;
 
     // Get the size and make sure we can dereference at least one uint32_t.
     off_t off = lseek(fd, 0, SEEK_END);
@@ -5140,17 +5154,17 @@ ShellCloseAsmJSCacheEntryForRead(HandleO
     jsCacheOpened = false;
     close(handle);
 }
 
 static bool
 ShellOpenAsmJSCacheEntryForWrite(HandleObject global, const jschar *begin, const jschar *end,
                                  size_t serializedSize, uint8_t **memoryOut, intptr_t *handleOut)
 {
-    if (!jsCacheAsmJSPath)
+    if (!jsCachingEnabled || !jsCacheAsmJSPath)
         return false;
 
     // Create the cache directory if it doesn't already exist.
     struct stat dirStat;
     if (stat(jsCacheDir, &dirStat) == 0) {
         if (!(dirStat.st_mode & S_IFDIR))
             return false;
     } else {
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -589,23 +589,17 @@ const Class NormalArgumentsObject::class
     JS_StrictPropertyStub,   /* setProperty */
     args_enumerate,
     reinterpret_cast<JSResolveOp>(args_resolve),
     JS_ConvertStub,
     ArgumentsObject::finalize,
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
-    ArgumentsObject::trace,
-    {
-        nullptr,    /* outerObject */
-        nullptr,    /* innerObject */
-        nullptr,    /* iteratorObject  */
-        false,      /* isWrappedNative */
-    }
+    ArgumentsObject::trace
 };
 
 /*
  * Strict mode arguments is significantly less magical than non-strict mode
  * arguments, so it is represented by a different class while sharing some
  * functionality.
  */
 const Class StrictArgumentsObject::class_ = {
@@ -619,16 +613,10 @@ const Class StrictArgumentsObject::class
     JS_StrictPropertyStub,   /* setProperty */
     strictargs_enumerate,
     reinterpret_cast<JSResolveOp>(strictargs_resolve),
     JS_ConvertStub,
     ArgumentsObject::finalize,
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
-    ArgumentsObject::trace,
-    {
-        nullptr,    /* outerObject */
-        nullptr,    /* innerObject */
-        nullptr,    /* iteratorObject  */
-        false,      /* isWrappedNative */
-    }
+    ArgumentsObject::trace
 };
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4381,17 +4381,17 @@ DebuggerFrame_setOnPop(JSContext *cx, un
  *
  * If |frame| is non-nullptr, evaluate as for a direct eval in that frame; |env|
  * must be either |frame|'s DebugScopeObject, or some extension of that
  * environment; either way, |frame|'s scope is where newly declared variables
  * go. In this case, |frame| must have a computed 'this' value, equal to |thisv|.
  */
 bool
 js::EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame,
-                  StableCharPtr chars, unsigned length, const char *filename, unsigned lineno,
+                  ConstTwoByteChars chars, unsigned length, const char *filename, unsigned lineno,
                   MutableHandleValue rval)
 {
     assertSameCompartment(cx, env, frame);
     JS_ASSERT_IF(frame, thisv.get() == frame.thisValue());
 
     JS_ASSERT(!IsPoisonedPtr(chars.get()));
 
     /*
@@ -4432,18 +4432,18 @@ DebuggerGenericEval(JSContext *cx, const
     JS_ASSERT_IF(!iter, scope && scope->is<GlobalObject>());
 
     /* Check the first argument, the eval code string. */
     if (!code.isString()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE,
                              fullMethodName, "string", InformalValueTypeName(code));
         return false;
     }
-    Rooted<JSStableString *> stable(cx, code.toString()->ensureStable(cx));
-    if (!stable)
+    Rooted<JSFlatString *> flat(cx, code.toString()->ensureFlat(cx));
+    if (!flat)
         return false;
 
     /*
      * Gather keys and values of bindings, if any. This must be done in the
      * debugger compartment, since that is where any exceptions must be
      * thrown.
      */
     AutoIdVector keys(cx);
@@ -4537,20 +4537,21 @@ DebuggerGenericEval(JSContext *cx, const
             {
                 return false;
             }
         }
     }
 
     /* Run the code and produce the completion value. */
     RootedValue rval(cx);
-    JS::Anchor<JSString *> anchor(stable);
+    JS::Anchor<JSString *> anchor(flat);
     AbstractFramePtr frame = iter ? iter->abstractFramePtr() : NullFramePtr();
-    bool ok = EvaluateInEnv(cx, env, thisv, frame, stable->chars(), stable->length(),
-                            url ? url : "debugger eval code", lineNumber, &rval);
+    bool ok = EvaluateInEnv(cx, env, thisv, frame,
+                            ConstTwoByteChars(flat->chars(), flat->length()),
+                            flat->length(), url ? url : "debugger eval code", lineNumber, &rval);
     if (url)
         JS_free(cx, url);
     return dbg->receiveCompletionValue(ac, ok, rval, vp);
 }
 
 static bool
 DebuggerFrame_eval(JSContext *cx, unsigned argc, Value *vp)
 {
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -726,14 +726,14 @@ Debugger::onNewGlobalObject(JSContext *c
     global->compartment()->firedOnNewGlobalObject = true;
 #endif
     if (!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers))
         Debugger::slowPathOnNewGlobalObject(cx, global);
 }
 
 extern bool
 EvaluateInEnv(JSContext *cx, Handle<Env*> env, HandleValue thisv, AbstractFramePtr frame,
-              StableCharPtr chars, unsigned length, const char *filename, unsigned lineno,
+              ConstTwoByteChars chars, unsigned length, const char *filename, unsigned lineno,
               MutableHandleValue rval);
 
 }
 
 #endif /* vm_Debugger_h */
--- a/js/src/vm/ErrorObject.cpp
+++ b/js/src/vm/ErrorObject.cpp
@@ -133,19 +133,19 @@ js::ErrorObject::getOrCreateErrorReport(
     report.lineno = lineNumber();
     report.column = columnNumber();
 
     // Message. Note that |new Error()| will result in an undefined |message|
     // slot, so we need to explicitly substitute the empty string in that case.
     RootedString message(cx, getMessage());
     if (!message)
         message = cx->runtime()->emptyString;
-    if (!message->ensureStable(cx))
+    if (!message->ensureFlat(cx))
         return nullptr;
-    report.ucmessage = message->asStable().chars().get();
+    report.ucmessage = message->asFlat().chars();
 
     // Cache and return.
     JSErrorReport *copy = CopyErrorReport(cx, &report);
     if (!copy)
         return nullptr;
     setReservedSlot(ERROR_REPORT_SLOT, PrivateValue(copy));
     return copy;
 }
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -17,40 +17,61 @@
 
 #include "builtin/Eval.h"
 #if EXPOSE_INTL_API
 # include "builtin/Intl.h"
 #endif
 #include "builtin/MapObject.h"
 #include "builtin/Object.h"
 #include "builtin/RegExp.h"
+#include "builtin/SIMD.h"
 #include "builtin/TypedObject.h"
 #include "vm/RegExpStatics.h"
+#include "vm/StopIterationObject.h"
+#include "vm/WeakMapObject.h"
 
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/ObjectImpl-inl.h"
 
 using namespace js;
 
+struct ProtoTableEntry {
+    const Class *clasp;
+    ClassInitializerOp init;
+};
+
 #define DECLARE_PROTOTYPE_CLASS_INIT(name,code,init,clasp) \
     extern JSObject *init(JSContext *cx, Handle<JSObject*> obj);
 JS_FOR_EACH_PROTOTYPE(DECLARE_PROTOTYPE_CLASS_INIT)
 #undef DECLARE_PROTOTYPE_CLASS_INIT
 
-static const ClassInitializerOp class_init_functions[JSProto_LIMIT] = {
-#define INIT_FUNC(name,code,init,clasp) init,
-#define INIT_FUNC_DUMMY(name,code,init,clasp) nullptr,
+JSObject *
+js_InitViaClassSpec(JSContext *cx, Handle<JSObject*> obj)
+{
+    MOZ_ASSUME_UNREACHABLE();
+}
+
+static const ProtoTableEntry protoTable[JSProto_LIMIT] = {
+#define INIT_FUNC(name,code,init,clasp) { clasp, init },
+#define INIT_FUNC_DUMMY(name,code,init,clasp) { nullptr, nullptr },
     JS_FOR_PROTOTYPES(INIT_FUNC, INIT_FUNC_DUMMY)
 #undef INIT_FUNC_DUMMY
 #undef INIT_FUNC
 };
 
+const js::Class *
+js::ProtoKeyToClass(JSProtoKey key)
+{
+    MOZ_ASSERT(key < JSProto_LIMIT);
+    return protoTable[key].clasp;
+}
+
 // This method is not in the header file to avoid having to include
 // TypedObject.h from GlobalObject.h. It is not generally perf
 // sensitive.
 TypedObjectModuleObject&
 js::GlobalObject::getTypedObjectModule() const {
     Value v = getConstructor(JSProto_TypedObject);
     // only gets called from contexts where TypedObject must be initialized
     JS_ASSERT(v.isObject());
@@ -421,20 +442,89 @@ GlobalObject::initFunctionAndObjectClass
     return functionProto;
 }
 
 bool
 GlobalObject::ensureConstructor(JSContext *cx, JSProtoKey key)
 {
     if (getConstructor(key).isObject())
         return true;
+    return initConstructor(cx, key);
+}
+
+bool
+GlobalObject::initConstructor(JSContext *cx, JSProtoKey key)
+{
     MOZ_ASSERT(getConstructor(key).isUndefined());
-    RootedObject self(cx, this);
-    ClassInitializerOp init = class_init_functions[key];
-    return !init || init(cx, self);
+    Rooted<GlobalObject*> self(cx, this);
+
+    // There are two different kinds of initialization hooks. One of them is
+    // the class js_InitFoo hook, defined in a JSProtoKey-keyed table at the
+    // top of this file. The other lives in the ClassSpec for classes that
+    // define it. Classes may use one or the other, but not both.
+    ClassInitializerOp init = protoTable[key].init;
+    if (init == js_InitViaClassSpec)
+        init = nullptr;
+
+    const Class *clasp = ProtoKeyToClass(key);
+
+    // Some classes have no init routine, which means that they're disabled at
+    // compile-time. We could try to enforce that callers never pass such keys
+    // to initConstructor, but that would cramp the style of consumers like
+    // GlobalObject::initStandardClasses that want to just carpet-bomb-call
+    // ensureConstructor with every JSProtoKey. So it's easier to just handle
+    // it here.
+    bool haveSpec = clasp && clasp->spec.defined();
+    if (!init && !haveSpec)
+        return true;
+
+    // See if there's an old-style initialization hook.
+    if (init) {
+        MOZ_ASSERT(!haveSpec);
+        return init(cx, self);
+    }
+
+    //
+    // Ok, we're doing it with a class spec.
+    //
+
+    // Create the constructor.
+    RootedObject ctor(cx, clasp->spec.createConstructor(cx, key));
+    if (!ctor)
+        return false;
+
+    // Define any specified functions.
+    if (const JSFunctionSpec *funs = clasp->spec.constructorFunctions) {
+        if (!JS_DefineFunctions(cx, ctor, funs))
+            return false;
+    }
+
+    // We don't always have a prototype (i.e. Math and JSON). If we don't,
+    // |createPrototype| and |prototypeFunctions| should both be null.
+    RootedObject proto(cx);
+    if (clasp->spec.createPrototype) {
+        proto = clasp->spec.createPrototype(cx, key);
+        if (!proto)
+            return false;
+    }
+    if (const JSFunctionSpec *funs = clasp->spec.prototypeFunctions) {
+        if (!JS_DefineFunctions(cx, proto, funs))
+            return false;
+    }
+
+    // If the prototype exists, link it with the constructor.
+    if (proto && !LinkConstructorAndPrototype(cx, ctor, proto))
+        return false;
+
+    // Call the post-initialization hook, if provided.
+    if (clasp->spec.finishInit && !clasp->spec.finishInit(cx, ctor, proto))
+        return false;
+
+    // Stash things in the right slots and define the constructor on the global.
+    return DefineConstructorAndPrototype(cx, self, key, ctor, proto);
 }
 
 GlobalObject *
 GlobalObject::create(JSContext *cx, const Class *clasp)
 {
     JS_ASSERT(clasp->flags & JSCLASS_IS_GLOBAL);
 
     JSObject *obj = NewObjectWithGivenProto(cx, clasp, nullptr, nullptr, SingletonObject);
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -155,16 +155,17 @@ class GlobalObject : public JSObject
     warnOnceAbout(JSContext *cx, HandleObject obj, uint32_t slot, unsigned errorNumber);
 
   public:
     Value getConstructor(JSProtoKey key) const {
         JS_ASSERT(key <= JSProto_LIMIT);
         return getSlotForCompilation(APPLICATION_SLOTS + key);
     }
     bool ensureConstructor(JSContext *cx, JSProtoKey key);
+    bool initConstructor(JSContext *cx, JSProtoKey key);
 
     void setConstructor(JSProtoKey key, const Value &v) {
         JS_ASSERT(key <= JSProto_LIMIT);
         setSlot(APPLICATION_SLOTS + key, v);
     }
 
     Value getPrototype(JSProtoKey key) const {
         JS_ASSERT(key <= JSProto_LIMIT);
@@ -816,16 +817,36 @@ LinkConstructorAndPrototype(JSContext *c
  * benefits.
  */
 extern bool
 DefinePropertiesAndBrand(JSContext *cx, JSObject *obj,
                          const JSPropertySpec *ps, const JSFunctionSpec *fs);
 
 typedef HashSet<GlobalObject *, DefaultHasher<GlobalObject *>, SystemAllocPolicy> GlobalObjectSet;
 
+/*
+ * Convenience templates to generic constructor and prototype creation functions
+ * for ClassSpecs.
+ */
+
+template<JSNative ctor, size_t atomOffset, unsigned length>
+JSObject *
+GenericCreateConstructor(JSContext *cx, JSProtoKey key)
+{
+    JSAtom *atom = AtomStateOffsetToName(cx->runtime()->atomState, atomOffset);
+    return cx->global()->createConstructor(cx, ctor, atom, length);
+}
+
+template<const Class *clasp>
+JSObject *
+GenericCreatePrototype(JSContext *cx, JSProtoKey key)
+{
+    return cx->global()->createBlankPrototype(cx, clasp);
+}
+
 } // namespace js
 
 template<>
 inline bool
 JSObject::is<js::GlobalObject>() const
 {
     return !!(getClass()->flags & JSCLASS_IS_GLOBAL);
 }
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1723,30 +1723,36 @@ END_CASE(JSOP_UNDEFINED)
 CASE(JSOP_POP)
     REGS.sp--;
 END_CASE(JSOP_POP)
 
 CASE(JSOP_POPN)
     JS_ASSERT(GET_UINT16(REGS.pc) <= REGS.stackDepth());
     REGS.sp -= GET_UINT16(REGS.pc);
 #ifdef DEBUG
-    if (StaticBlockObject *block = script->getBlockScope(REGS.pc + JSOP_POPN_LENGTH))
-        JS_ASSERT(REGS.stackDepth() >= block->stackDepth() + block->slotCount());
+    if (NestedScopeObject *scope = script->getStaticScope(REGS.pc + JSOP_POPN_LENGTH)) {
+        JS_ASSERT(scope->is<StaticBlockObject>());
+        StaticBlockObject &blockObj = scope->as<StaticBlockObject>();
+        JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount());
+    }
 #endif
 END_CASE(JSOP_POPN)
 
 CASE(JSOP_POPNV)
 {
     JS_ASSERT(GET_UINT16(REGS.pc) < REGS.stackDepth());
     Value val = REGS.sp[-1];
     REGS.sp -= GET_UINT16(REGS.pc);
     REGS.sp[-1] = val;
 #ifdef DEBUG
-    if (StaticBlockObject *block = script->getBlockScope(REGS.pc + JSOP_POPNV_LENGTH))
-        JS_ASSERT(REGS.stackDepth() >= block->stackDepth() + block->slotCount());
+    if (NestedScopeObject *scope = script->getStaticScope(REGS.pc + JSOP_POPNV_LENGTH)) {
+        JS_ASSERT(scope->is<StaticBlockObject>());
+        StaticBlockObject &blockObj = scope->as<StaticBlockObject>();
+        JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount());
+    }
 #endif
 }
 END_CASE(JSOP_POPNV)
 
 CASE(JSOP_SETRVAL)
     POP_RETURN_VALUE();
 END_CASE(JSOP_SETRVAL)
 
@@ -3364,31 +3370,34 @@ CASE(JSOP_PUSHBLOCKSCOPE)
 }
 END_CASE(JSOP_PUSHBLOCKSCOPE)
 
 CASE(JSOP_POPBLOCKSCOPE)
 {
 #ifdef DEBUG
     // Pop block from scope chain.
     JS_ASSERT(*(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH) == JSOP_DEBUGLEAVEBLOCK);
-    StaticBlockObject *blockObj = script->getBlockScope(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH);
-    JS_ASSERT(blockObj && blockObj->needsClone());
+    NestedScopeObject *scope = script->getStaticScope(REGS.pc - JSOP_DEBUGLEAVEBLOCK_LENGTH);
+    JS_ASSERT(scope && scope->is<StaticBlockObject>());
+    StaticBlockObject &blockObj = scope->as<StaticBlockObject>();
+    JS_ASSERT(blockObj.needsClone());
 
     // FIXME: "Aliased" slots don't need to be on the stack.
-    JS_ASSERT(REGS.stackDepth() >= blockObj->stackDepth() + blockObj->slotCount());
+    JS_ASSERT(REGS.stackDepth() >= blockObj.stackDepth() + blockObj.slotCount());
 #endif
 
     // Pop block from scope chain.
     REGS.fp()->popBlock(cx);
 }
 END_CASE(JSOP_POPBLOCKSCOPE)
 
 CASE(JSOP_DEBUGLEAVEBLOCK)
 {
-    JS_ASSERT(script->getBlockScope(REGS.pc));
+    JS_ASSERT(script->getStaticScope(REGS.pc));
+    JS_ASSERT(script->getStaticScope(REGS.pc)->is<StaticBlockObject>());
 
     // FIXME: This opcode should not be necessary.  The debugger shouldn't need
     // help from bytecode to do its job.  See bug 927782.
 
     if (MOZ_UNLIKELY(cx->compartment()->debugMode()))
         DebugScopes::onPopBlock(cx, REGS.fp(), REGS.pc);
 }
 END_CASE(JSOP_DEBUGLEAVEBLOCK)
--- a/js/src/vm/OldDebugAPI.cpp
+++ b/js/src/vm/OldDebugAPI.cpp
@@ -1296,17 +1296,17 @@ JSAbstractFramePtr::evaluateUCInStackFra
         return false;
 
     AbstractFramePtr frame(*this);
     if (!ComputeThis(cx, frame))
         return false;
     RootedValue thisv(cx, frame.thisValue());
 
     js::AutoCompartment ac(cx, env);
-    return EvaluateInEnv(cx, env, thisv, frame, StableCharPtr(chars, length), length,
+    return EvaluateInEnv(cx, env, thisv, frame, ConstTwoByteChars(chars, length), length,
                          filename, lineno, rval);
 }
 
 JSBrokenFrameIterator::JSBrokenFrameIterator(JSContext *cx)
 {
     // Show all frames on the stack whose principal is subsumed by the current principal.
     NonBuiltinScriptFrameIter iter(cx,
                                    ScriptFrameIter::ALL_CONTEXTS,
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -43,18 +43,18 @@ StaticScopeIter<allowGC>::done() const
 {
     return !obj;
 }
 
 template <AllowGC allowGC>
 inline void
 StaticScopeIter<allowGC>::operator++(int)
 {
-    if (obj->template is<StaticBlockObject>()) {
-        obj = obj->template as<StaticBlockObject>().enclosingStaticScope();
+    if (obj->template is<NestedScopeObject>()) {
+        obj = obj->template as<NestedScopeObject>().enclosingScopeForStaticScopeIter();
     } else if (onNamedLambda || !obj->template as<JSFunction>().isNamedLambda()) {
         onNamedLambda = false;
         obj = obj->template as<JSFunction>().nonLazyScript()->enclosingStaticScope();
     } else {
         onNamedLambda = true;
     }
     JS_ASSERT_IF(obj, obj->template is<StaticBlockObject>() || obj->template is<JSFunction>());
     JS_ASSERT_IF(onNamedLambda, obj->template is<JSFunction>());
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -33,19 +33,19 @@ typedef Rooted<ArgumentsObject *> Rooted
 /*****************************************************************************/
 
 static JSObject *
 InnermostStaticScope(JSScript *script, jsbytecode *pc)
 {
     JS_ASSERT(script->containsPC(pc));
     JS_ASSERT(JOF_OPTYPE(*pc) == JOF_SCOPECOORD);
 
-    StaticBlockObject *block = script->getBlockScope(pc);
-    if (block)
-        return block;
+    NestedScopeObject *scope = script->getStaticScope(pc);
+    if (scope)
+        return scope;
     return script->functionNonDelazifying();
 }
 
 Shape *
 js::ScopeCoordinateToStaticScopeShape(JSScript *script, jsbytecode *pc)
 {
     StaticScopeIter<NoGC> ssi(InnermostStaticScope(script, pc));
     uint32_t hops = ScopeCoordinate(pc).hops();
@@ -538,16 +538,17 @@ const Class WithObject::class_ = {
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     nullptr,                 /* finalize */
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
     nullptr,                 /* trace       */
+    JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     {
         with_LookupGeneric,
         with_LookupProperty,
         with_LookupElement,
         with_LookupSpecial,
         nullptr,             /* defineGeneric */
         nullptr,             /* defineProperty */
@@ -716,17 +717,17 @@ js::XDRStaticBlockObject(XDRState<mode> 
         JS_ASSERT(count <= UINT16_MAX);
         depthAndCount = (depth << 16) | uint16_t(count);
     }
 
     if (mode == XDR_DECODE) {
         obj = StaticBlockObject::create(cx);
         if (!obj)
             return false;
-        obj->initEnclosingStaticScope(enclosingScope);
+        obj->initEnclosingNestedScope(enclosingScope);
         *objp = obj;
     }
 
     if (!xdr->codeUint32(&depthAndCount))
         return false;
 
     if (mode == XDR_DECODE) {
         uint32_t depth = uint16_t(depthAndCount >> 16);
@@ -801,26 +802,26 @@ js::XDRStaticBlockObject(XDRState<mode> 
 }
 
 template bool
 js::XDRStaticBlockObject(XDRState<XDR_ENCODE> *, HandleObject, StaticBlockObject **);
 
 template bool
 js::XDRStaticBlockObject(XDRState<XDR_DECODE> *, HandleObject, StaticBlockObject **);
 
-JSObject *
-js::CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> srcBlock)
+static JSObject *
+CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> srcBlock)
 {
     /* NB: Keep this in sync with XDRStaticBlockObject. */
 
     Rooted<StaticBlockObject*> clone(cx, StaticBlockObject::create(cx));
     if (!clone)
         return nullptr;
 
-    clone->initEnclosingStaticScope(enclosingScope);
+    clone->initEnclosingNestedScope(enclosingScope);
     clone->setStackDepth(srcBlock->stackDepth());
 
     /* Shape::Range is reverse order, so build a list in forward order. */
     AutoShapeVector shapes(cx);
     if (!shapes.growBy(srcBlock->slotCount()))
         return nullptr;
     for (Shape::Range<NoGC> r(srcBlock->lastProperty()); !r.empty(); r.popFront())
         shapes[r.front().shortid()] = &r.front();
@@ -836,67 +837,75 @@ js::CloneStaticBlockObject(JSContext *cx
         }
 
         clone->setAliased(i, srcBlock->isAliased(i));
     }
 
     return clone;
 }
 
+JSObject *
+js::CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle<NestedScopeObject*> srcBlock)
+{
+    JS_ASSERT(srcBlock->is<StaticBlockObject>());
+    Rooted<StaticBlockObject *> blockObj(cx, &srcBlock->as<StaticBlockObject>());
+    return CloneStaticBlockObject(cx, enclosingScope, blockObj);
+}
+
 /*****************************************************************************/
 
 // Any name atom for a function which will be added as a DeclEnv object to the
 // scope chain above call objects for fun.
 static inline JSAtom *
 CallObjectLambdaName(JSFunction &fun)
 {
     return fun.isNamedLambda() ? fun.atom() : nullptr;
 }
 
 ScopeIter::ScopeIter(const ScopeIter &si, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx(cx),
     frame_(si.frame_),
     cur_(cx, si.cur_),
-    block_(cx, si.block_),
+    staticScope_(cx, si.staticScope_),
     type_(si.type_),
     hasScopeObject_(si.hasScopeObject_)
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
 ScopeIter::ScopeIter(JSObject &enclosingScope, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx(cx),
     frame_(NullFramePtr()),
     cur_(cx, &enclosingScope),
-    block_(cx, nullptr),
+    staticScope_(cx, nullptr),
     type_(Type(-1))
 {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
 ScopeIter::ScopeIter(AbstractFramePtr frame, jsbytecode *pc, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx(cx),
     frame_(frame),
     cur_(cx, frame.scopeChain()),
-    block_(cx, frame.script()->getBlockScope(pc))
+    staticScope_(cx, frame.script()->getStaticScope(pc))
 {
     assertSameCompartment(cx, frame);
     settle();
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
 ScopeIter::ScopeIter(const ScopeIterVal &val, JSContext *cx
                      MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : cx(cx),
     frame_(val.frame_),
     cur_(cx, val.cur_),
-    block_(cx, val.block_),
+    staticScope_(cx, val.staticScope_),
     type_(val.type_),
     hasScopeObject_(val.hasScopeObject_)
 {
     assertSameCompartment(cx, val.frame_);
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 }
 
 ScopeObject &
@@ -915,17 +924,17 @@ ScopeIter::operator++()
         if (hasScopeObject_) {
             cur_ = &cur_->as<CallObject>().enclosingScope();
             if (CallObjectLambdaName(*frame_.fun()))
                 cur_ = &cur_->as<DeclEnvObject>().enclosingScope();
         }
         frame_ = NullFramePtr();
         break;
       case Block:
-        block_ = block_->enclosingBlock();
+        staticScope_ = staticScope_->as<StaticBlockObject>().enclosingBlock();
         if (hasScopeObject_)
             cur_ = &cur_->as<ClonedBlockObject>().enclosingScope();
         settle();
         break;
       case With:
         JS_ASSERT(hasScopeObject_);
         cur_ = &cur_->as<WithObject>().enclosingScope();
         settle();
@@ -938,17 +947,17 @@ ScopeIter::operator++()
     }
     return *this;
 }
 
 void
 ScopeIter::settle()
 {
     /*
-     * Given an iterator state (cur_, block_), figure out which (potentially
+     * Given an iterator state (cur_, staticScope_), figure out which (potentially
      * optimized) scope the iterator should report. Thus, the result is a pair
      * (type_, hasScopeObject_) where hasScopeObject_ indicates whether the
      * scope object has been optimized away and does not exist on the scope
      * chain. Beware: while ScopeIter iterates over the scopes of a single
      * frame, the scope chain (pointed to by cur_) continues into the scopes of
      * enclosing frames. Thus, it is important not to look at cur_ until it is
      * certain that cur_ points to a scope object in the current frame. In
      * particular, there are three tricky corner cases:
@@ -961,89 +970,93 @@ ScopeIter::settle()
      * uncloned blocks. In the last case, since we haven't entered the
      * function, we simply return a ScopeIter where done() == true.
      *
      * Note: DebugScopeObject falls nicely into this plan: since they are only
      * ever introduced as the *enclosing* scope of a frame, they should never
      * show up in scope iteration and fall into the final non-scope case.
      */
     if (frame_.isNonEvalFunctionFrame() && !frame_.fun()->isHeavyweight()) {
-        if (block_) {
+        if (staticScope_) {
+            JS_ASSERT(staticScope_->is<StaticBlockObject>());
             type_ = Block;
-            hasScopeObject_ = block_->needsClone();
+            hasScopeObject_ = staticScope_->as<StaticBlockObject>().needsClone();
         } else {
             type_ = Call;
             hasScopeObject_ = false;
         }
     } else if (frame_.isNonStrictDirectEvalFrame() && cur_ == frame_.evalPrevScopeChain(cx)) {
-        if (block_) {
-            JS_ASSERT(!block_->needsClone());
+        if (staticScope_) {
+            JS_ASSERT(staticScope_->is<StaticBlockObject>());
+            JS_ASSERT(!staticScope_->as<StaticBlockObject>().needsClone());
             type_ = Block;
             hasScopeObject_ = false;
         } else {
             frame_ = NullFramePtr();
         }
     } else if (frame_.isNonEvalFunctionFrame() && !frame_.hasCallObj()) {
         JS_ASSERT(cur_ == frame_.fun()->environment());
         frame_ = NullFramePtr();
     } else if (frame_.isStrictEvalFrame() && !frame_.hasCallObj()) {
         JS_ASSERT(cur_ == frame_.evalPrevScopeChain(cx));
         frame_ = NullFramePtr();
     } else if (cur_->is<WithObject>()) {
         JS_ASSERT_IF(frame_.isFunctionFrame(), frame_.fun()->isHeavyweight());
-        JS_ASSERT_IF(block_, block_->needsClone());
-        JS_ASSERT_IF(block_, block_->stackDepth() < cur_->as<WithObject>().stackDepth());
+        JS_ASSERT_IF(staticScope_, staticScope_->as<StaticBlockObject>().needsClone());
+        JS_ASSERT_IF(staticScope_,
+                     staticScope_->as<StaticBlockObject>().stackDepth() <
+                     cur_->as<WithObject>().stackDepth());
         type_ = With;
         hasScopeObject_ = true;
-    } else if (block_) {
+    } else if (staticScope_) {
         type_ = Block;
-        hasScopeObject_ = block_->needsClone();
-        JS_ASSERT_IF(hasScopeObject_, cur_->as<ClonedBlockObject>().staticBlock() == *block_);
+        hasScopeObject_ = staticScope_->as<StaticBlockObject>().needsClone();
+        JS_ASSERT_IF(hasScopeObject_, cur_->as<ClonedBlockObject>().staticBlock() == *staticScope_);
     } else if (cur_->is<CallObject>()) {
         CallObject &callobj = cur_->as<CallObject>();
         type_ = callobj.isForEval() ? StrictEvalScope : Call;
         hasScopeObject_ = true;
         JS_ASSERT_IF(type_ == Call, callobj.callee().nonLazyScript() == frame_.script());
     } else {
         JS_ASSERT(!cur_->is<ScopeObject>());
         JS_ASSERT(frame_.isGlobalFrame() || frame_.isDebuggerFrame());
         frame_ = NullFramePtr();
     }
 }
 
 /* static */ HashNumber
 ScopeIterKey::hash(ScopeIterKey si)
 {
     /* hasScopeObject_ is determined by the other fields. */
-    return size_t(si.frame_.raw()) ^ size_t(si.cur_) ^ size_t(si.block_) ^ si.type_;
+    return size_t(si.frame_.raw()) ^ size_t(si.cur_) ^ size_t(si.staticScope_) ^ si.type_;
 }
 
 /* static */ bool
 ScopeIterKey::match(ScopeIterKey si1, ScopeIterKey si2)
 {
     /* hasScopeObject_ is determined by the other fields. */
     return si1.frame_ == si2.frame_ &&
            (!si1.frame_ ||
             (si1.cur_   == si2.cur_   &&
-             si1.block_ == si2.block_ &&
+             si1.staticScope_ == si2.staticScope_ &&
              si1.type_  == si2.type_));
 }
 
 // Live ScopeIter values may be added to DebugScopes::liveScopes, as
 // ScopeIterVal instances.  They need to have write barriers when they are added
 // to the hash table, but no barriers when rehashing inside GC.  It's a nasty
 // hack, but the important thing is that ScopeIterKey and ScopeIterVal need to
 // alias each other.
 void ScopeIterVal::staticAsserts() {
     static_assert(sizeof(ScopeIterVal) == sizeof(ScopeIterKey),
                   "ScopeIterVal must be same size of ScopeIterKey");
     static_assert(offsetof(ScopeIterVal, cur_) == offsetof(ScopeIterKey, cur_),
                   "ScopeIterVal.cur_ must alias ScopeIterKey.cur_");
-    static_assert(offsetof(ScopeIterVal, block_) == offsetof(ScopeIterKey, block_),
-                  "ScopeIterVal.block_ must alias ScopeIterKey.block_");
+    static_assert(offsetof(ScopeIterVal, staticScope_) == offsetof(ScopeIterKey, staticScope_),
+                  "ScopeIterVal.staticScope_ must alias ScopeIterKey.staticScope_");
 }
 
 /*****************************************************************************/
 
 namespace {
 
 /*
  * DebugScopeProxy is the handler for DebugScopeObject proxy objects. Having a
@@ -1666,23 +1679,23 @@ DebugScopes::checkHashTablesAfterMovingG
      */
     JS::shadow::Runtime *rt = JS::shadow::Runtime::asShadowRuntime(runtime);
     for (ObjectWeakMap::Range r = proxiedScopes.all(); !r.empty(); r.popFront()) {
         JS_ASSERT(!IsInsideNursery(rt, r.front().key().get()));
         JS_ASSERT(!IsInsideNursery(rt, r.front().value().get()));
     }
     for (MissingScopeMap::Range r = missingScopes.all(); !r.empty(); r.popFront()) {
         JS_ASSERT(!IsInsideNursery(rt, r.front().key().cur()));
-        JS_ASSERT(!IsInsideNursery(rt, r.front().key().block()));
+        JS_ASSERT(!IsInsideNursery(rt, r.front().key().staticScope()));
         JS_ASSERT(!IsInsideNursery(rt, r.front().value().get()));
     }
     for (LiveScopeMap::Range r = liveScopes.all(); !r.empty(); r.popFront()) {
         JS_ASSERT(!IsInsideNursery(rt, r.front().key()));
         JS_ASSERT(!IsInsideNursery(rt, r.front().value().cur_.get()));
-        JS_ASSERT(!IsInsideNursery(rt, r.front().value().block_.get()));
+        JS_ASSERT(!IsInsideNursery(rt, r.front().value().staticScope_.get()));
     }
 }
 #endif
 
 /*
  * Unfortunately, GetDebugScopeForFrame needs to work even outside debug mode
  * (in particular, JS_GetFrameScopeChain does not require debug mode). Since
  * DebugScopes::onPop* are only called in debug mode, this means we cannot
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -304,16 +304,47 @@ class NestedScopeObject : public ScopeOb
   protected:
     static const unsigned DEPTH_SLOT = 1;
 
   public:
     /* Return the abstract stack depth right before entering this nested scope. */
     uint32_t stackDepth() const {
         return getReservedSlot(DEPTH_SLOT).toPrivateUint32();
     }
+
+    /*
+     * A refinement of enclosingScope that returns nullptr if the enclosing
+     * scope is not a NestedScopeObject.
+     */
+    inline NestedScopeObject *enclosingNestedScope() const;
+
+    // At compile-time it's possible for the scope chain to be null.
+    JSObject *enclosingScopeForStaticScopeIter() {
+        return getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
+    }
+
+    void initEnclosingNestedScope(JSObject *obj) {
+        JS_ASSERT(getReservedSlot(SCOPE_CHAIN_SLOT).isUndefined());
+        setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(obj));
+    }
+
+    /*
+     * The parser uses 'enclosingNestedScope' as the prev-link in the
+     * pc->staticScope stack. Note: in the case of hoisting, this prev-link will
+     * not ultimately be the same as enclosingNestedScope;
+     * initEnclosingNestedScope must be called separately in the
+     * emitter. 'reset' is just for asserting stackiness.
+     */
+    void initEnclosingNestedScopeFromParser(NestedScopeObject *prev) {
+        setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(prev));
+    }
+
+    void resetEnclosingNestedScopeFromParser() {
+        setReservedSlot(SCOPE_CHAIN_SLOT, UndefinedValue());
+    }
 };
 
 class WithObject : public NestedScopeObject
 {
     static const unsigned THIS_SLOT = 2;
 
     /* Use WithObject::object() instead. */
     JSObject *getProto() const;
@@ -376,24 +407,18 @@ class BlockObject : public NestedScopeOb
     }
 };
 
 class StaticBlockObject : public BlockObject
 {
   public:
     static StaticBlockObject *create(ExclusiveContext *cx);
 
-    /* See StaticScopeIter comment. */
-    JSObject *enclosingStaticScope() const {
-        AutoThreadSafeAccess ts(this);
-        return getFixedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
-    }
-
     /*
-     * A refinement of enclosingStaticScope that returns nullptr if the enclosing
+     * A refinement of enclosingScope that returns nullptr if the enclosing
      * static scope is a JSFunction.
      */
     inline StaticBlockObject *enclosingBlock() const;
 
     /*
      * Return whether this StaticBlockObject contains a variable stored at
      * the given stack depth (i.e., fp->base()[depth]).
      */
@@ -432,51 +457,32 @@ class StaticBlockObject : public BlockOb
         }
     }
 
     void setStackDepth(uint32_t depth) {
         JS_ASSERT(getReservedSlot(DEPTH_SLOT).isUndefined());
         initReservedSlot(DEPTH_SLOT, PrivateUint32Value(depth));
     }
 
-    void initEnclosingStaticScope(JSObject *obj) {
-        JS_ASSERT(getReservedSlot(SCOPE_CHAIN_SLOT).isUndefined());
-        setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(obj));
-    }
-
     /*
      * Frontend compilation temporarily uses the object's slots to link
      * a let var to its associated Definition parse node.
      */
     void setDefinitionParseNode(unsigned i, frontend::Definition *def) {
         JS_ASSERT(slotValue(i).isUndefined());
         setSlotValue(i, PrivateValue(def));
     }
 
     frontend::Definition *maybeDefinitionParseNode(unsigned i) {
         Value v = slotValue(i);
         return v.isUndefined() ? nullptr
                                : reinterpret_cast<frontend::Definition *>(v.toPrivate());
     }
 
     /*
-     * The parser uses 'enclosingBlock' as the prev-link in the pc->blockChain
-     * stack. Note: in the case of hoisting, this prev-link will not ultimately
-     * be the same as enclosingBlock, initEnclosingStaticScope must be called
-     * separately in the emitter. 'reset' is just for asserting stackiness.
-     */
-    void initPrevBlockChainFromParser(StaticBlockObject *prev) {
-        setReservedSlot(SCOPE_CHAIN_SLOT, ObjectOrNullValue(prev));
-    }
-
-    void resetPrevBlockChainFromParser() {
-        setReservedSlot(SCOPE_CHAIN_SLOT, UndefinedValue());
-    }
-
-    /*
      * While ScopeCoordinate can generally reference up to 2^24 slots, block objects have an
      * additional limitation that all slot indices must be storable as uint16_t short-ids in the
      * associated Shape. If we could remove the block dependencies on shape->shortid, we could
      * remove INDEX_LIMIT.
      */
     static const unsigned VAR_INDEX_LIMIT = JS_BIT(16);
 
     static Shape *addVar(ExclusiveContext *cx, Handle<StaticBlockObject*> block, HandleId id,
@@ -510,17 +516,17 @@ class ClonedBlockObject : public BlockOb
 };
 
 template<XDRMode mode>
 bool
 XDRStaticBlockObject(XDRState<mode> *xdr, HandleObject enclosingScope,
                      StaticBlockObject **objp);
 
 extern JSObject *
-CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle<StaticBlockObject*> src);
+CloneNestedScopeObject(JSContext *cx, HandleObject enclosingScope, Handle<NestedScopeObject*> src);
 
 /*****************************************************************************/
 
 class ScopeIterKey;
 class ScopeIterVal;
 
 /*
  * A scope iterator describes the active scopes enclosing the current point of
@@ -542,17 +548,17 @@ class ScopeIter
 
   public:
     enum Type { Call, Block, With, StrictEvalScope };
 
   private:
     JSContext *cx;
     AbstractFramePtr frame_;
     RootedObject cur_;
-    Rooted<StaticBlockObject *> block_;
+    Rooted<NestedScopeObject *> staticScope_;
     Type type_;
     bool hasScopeObject_;
 
     void settle();
 
     /* ScopeIter does not have value semantics. */
     ScopeIter(const ScopeIter &si) MOZ_DELETE;
 
@@ -588,75 +594,78 @@ class ScopeIter
 
     ScopeIter &operator++();
 
     AbstractFramePtr frame() const { JS_ASSERT(!done()); return frame_; }
     Type type() const { JS_ASSERT(!done()); return type_; }
     bool hasScopeObject() const { JS_ASSERT(!done()); return hasScopeObject_; }
     ScopeObject &scope() const;
 
-    StaticBlockObject &staticBlock() const { JS_ASSERT(type() == Block); return *block_; }
+    StaticBlockObject &staticBlock() const {
+        JS_ASSERT(type() == Block);
+        return staticScope_->as<StaticBlockObject>();
+    }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 class ScopeIterKey
 {
     friend class ScopeIterVal;
 
     AbstractFramePtr frame_;
     JSObject *cur_;
-    StaticBlockObject *block_;
+    NestedScopeObject *staticScope_;
     ScopeIter::Type type_;
     bool hasScopeObject_;
 
   public:
     ScopeIterKey(const ScopeIter &si)
-      : frame_(si.frame()), cur_(si.cur_), block_(si.block_), type_(si.type_),
+      : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
         hasScopeObject_(si.hasScopeObject_) {}
 
     AbstractFramePtr frame() const { return frame_; }
     JSObject *cur() const { return cur_; }
-    StaticBlockObject *block() const { return block_; }
+    NestedScopeObject *staticScope() const { return staticScope_; }
     ScopeIter::Type type() const { return type_; }
     bool hasScopeObject() const { return hasScopeObject_; }
     JSObject *enclosingScope() const { return cur_; }
     JSObject *&enclosingScope() { return cur_; }
 
     /* For use as hash policy */
     typedef ScopeIterKey Lookup;
     static HashNumber hash(ScopeIterKey si);
     static bool match(ScopeIterKey si1, ScopeIterKey si2);
     bool operator!=(const ScopeIterKey &other) const {
         return frame_ != other.frame_ ||
                cur_ != other.cur_ ||
-               block_ != other.block_ ||
+               staticScope_ != other.staticScope_ ||
                type_ != other.type_;
     }
     static void rekey(ScopeIterKey &k, const ScopeIterKey& newKey) {
         k = newKey;
     }
 };
 
 class ScopeIterVal
 {
     friend class ScopeIter;
     friend class DebugScopes;
 
     AbstractFramePtr frame_;
     RelocatablePtr<JSObject> cur_;
-    RelocatablePtr<StaticBlockObject> block_;
+    RelocatablePtr<NestedScopeObject> staticScope_;
     ScopeIter::Type type_;
     bool hasScopeObject_;
 
     static void staticAsserts();
 
   public:
     ScopeIterVal(const ScopeIter &si)
-      : frame_(si.frame()), cur_(si.cur_), block_(si.block_), type_(si.type_),
+      : frame_(si.frame()), cur_(si.cur_), staticScope_(si.staticScope_), type_(si.type_),
         hasScopeObject_(si.hasScopeObject_) {}
 
     AbstractFramePtr frame() const { return frame_; }
 };
 
 /*****************************************************************************/
 
 /*
@@ -846,16 +855,23 @@ namespace js {
 
 inline const Value &
 ScopeObject::aliasedVar(ScopeCoordinate sc)
 {
     JS_ASSERT(is<CallObject>() || is<ClonedBlockObject>());
     return getSlot(sc.slot());
 }
 
+inline NestedScopeObject *
+NestedScopeObject::enclosingNestedScope() const
+{
+    JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
+    return obj && obj->is<NestedScopeObject>() ? &obj->as<NestedScopeObject>() : nullptr;
+}
+
 inline StaticBlockObject *
 StaticBlockObject::enclosingBlock() const
 {
     JSObject *obj = getReservedSlot(SCOPE_CHAIN_SLOT).toObjectOrNull();
     return obj && obj->is<StaticBlockObject>() ? &obj->as<StaticBlockObject>() : nullptr;
 }
 
 #ifdef DEBUG
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -904,20 +904,20 @@ CloneObject(JSContext *cx, HandleObject 
         clone = RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr);
     } else if (srcObj->is<DateObject>()) {
         clone = JS_NewDateObjectMsec(cx, srcObj->as<DateObject>().UTCTime().toNumber());
     } else if (srcObj->is<BooleanObject>()) {
         clone = BooleanObject::create(cx, srcObj->as<BooleanObject>().unbox());
     } else if (srcObj->is<NumberObject>()) {
         clone = NumberObject::create(cx, srcObj->as<NumberObject>().unbox());
     } else if (srcObj->is<StringObject>()) {
-        Rooted<JSStableString*> str(cx, srcObj->as<StringObject>().unbox()->ensureStable(cx));
+        Rooted<JSFlatString*> str(cx, srcObj->as<StringObject>().unbox()->ensureFlat(cx));
         if (!str)
             return nullptr;
-        str = js_NewStringCopyN<CanGC>(cx, str->chars().get(), str->length())->ensureStable(cx);
+        str = js_NewStringCopyN<CanGC>(cx, str->chars(), str->length());
         if (!str)
             return nullptr;
         clone = StringObject::create(cx, str);
     } else if (srcObj->is<ArrayObject>()) {
         clone = NewDenseEmptyArray(cx, nullptr, TenuredObject);
     } else {
         JS_ASSERT(srcObj->isNative());
         clone = NewObjectWithGivenProto(cx, srcObj->getClass(), nullptr, cx->global(),
@@ -942,20 +942,20 @@ CloneValue(JSContext *cx, MutableHandleV
         RootedObject obj(cx, &vp.toObject());
         RootedObject clone(cx, CloneObject(cx, obj, clonedObjects));
         if (!clone)
             return false;
         vp.setObject(*clone);
     } else if (vp.isBoolean() || vp.isNumber() || vp.isNullOrUndefined()) {
         // Nothing to do here: these are represented inline in the value
     } else if (vp.isString()) {
-        Rooted<JSStableString*> str(cx, vp.toString()->ensureStable(cx));
+        Rooted<JSFlatString*> str(cx, vp.toString()->ensureFlat(cx));
         if (!str)
             return false;
-        RootedString clone(cx, js_NewStringCopyN<CanGC>(cx, str->chars().get(), str->length()));
+        RootedString clone(cx, js_NewStringCopyN<CanGC>(cx, str->chars(), str->length()));
         if (!clone)
             return false;
         vp.setString(clone);
     } else {
         MOZ_ASSUME_UNREACHABLE("Self-hosting CloneValue can't clone given value.");
     }
     return true;
 }
--- a/js/src/vm/String-inl.h
+++ b/js/src/vm/String-inl.h
@@ -35,17 +35,17 @@ NewShortString(ThreadSafeContext *cx, JS
     for (size_t i = 0; i < len; ++i)
         p[i] = static_cast<jschar>(chars[i]);
     p[len] = '\0';
     return str;
 }
 
 template <AllowGC allowGC>
 static MOZ_ALWAYS_INLINE JSInlineString *
-NewShortString(ExclusiveContext *cx, JS::StableTwoByteChars chars)
+NewShortString(ExclusiveContext *cx, JS::TwoByteChars chars)
 {
     size_t len = chars.length();
 
     /*
      * Don't bother trying to find a static atom; measurement shows that not
      * many get here (for one, Atomize is catching them).
      */
     JS_ASSERT(JSShortString::lengthFits(len));
@@ -56,44 +56,16 @@ NewShortString(ExclusiveContext *cx, JS:
         return nullptr;
 
     jschar *storage = str->init(len);
     mozilla::PodCopy(storage, chars.start().get(), len);
     storage[len] = 0;
     return str;
 }
 
-template <AllowGC allowGC>
-static MOZ_ALWAYS_INLINE JSInlineString *
-NewShortString(ExclusiveContext *cx, JS::TwoByteChars chars)
-{
-    size_t len = chars.length();
-
-    /*
-     * Don't bother trying to find a static atom; measurement shows that not
-     * many get here (for one, Atomize is catching them).
-     */
-    JS_ASSERT(JSShortString::lengthFits(len));
-    JSInlineString *str = JSInlineString::lengthFits(len)
-                          ? JSInlineString::new_<NoGC>(cx)
-                          : JSShortString::new_<NoGC>(cx);
-    if (!str) {
-        if (!allowGC)
-            return nullptr;
-        jschar tmp[JSShortString::MAX_SHORT_LENGTH];
-        mozilla::PodCopy(tmp, chars.start().get(), len);
-        return NewShortString<CanGC>(cx, JS::StableTwoByteChars(tmp, len));
-    }
-
-    jschar *storage = str->init(len);
-    mozilla::PodCopy(storage, chars.start().get(), len);
-    storage[len] = 0;
-    return str;
-}
-
 static inline void
 StringWriteBarrierPost(js::ThreadSafeContext *maybecx, JSString **strp)
 {
 }
 
 static inline void
 StringWriteBarrierPostRemove(js::ThreadSafeContext *maybecx, JSString **strp)
 {
@@ -205,53 +177,53 @@ JSDependentString::new_(js::ExclusiveCon
 
 inline void
 JSString::markBase(JSTracer *trc)
 {
     JS_ASSERT(hasBase());
     js::gc::MarkStringUnbarriered(trc, &d.s.u2.base, "base");
 }
 
+MOZ_ALWAYS_INLINE void
+JSFlatString::init(const jschar *chars, size_t length)
+{
+    d.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
+    d.u1.chars = chars;
+}
+
+template <js::AllowGC allowGC>
+MOZ_ALWAYS_INLINE JSFlatString *
+JSFlatString::new_(js::ThreadSafeContext *cx, const jschar *chars, size_t length)
+{
+    JS_ASSERT(chars[length] == jschar(0));
+
+    if (!validateLength(cx, length))
+        return nullptr;
+    JSFlatString *str = (JSFlatString *)js_NewGCString<allowGC>(cx);
+    if (!str)
+        return nullptr;
+    str->init(chars, length);
+    return str;
+}
+
 inline js::PropertyName *
 JSFlatString::toPropertyName(JSContext *cx)
 {
 #ifdef DEBUG
     uint32_t dummy;
     JS_ASSERT(!isIndex(&dummy));
 #endif
     if (isAtom())
         return asAtom().asPropertyName();
     JSAtom *atom = js::AtomizeString(cx, this);
     if (!atom)
         return nullptr;
     return atom->asPropertyName();
 }
 
-MOZ_ALWAYS_INLINE void
-JSStableString::init(const jschar *chars, size_t length)
-{
-    d.lengthAndFlags = buildLengthAndFlags(length, FIXED_FLAGS);
-    d.u1.chars = chars;
-}
-
-template <js::AllowGC allowGC>
-MOZ_ALWAYS_INLINE JSStableString *
-JSStableString::new_(js::ThreadSafeContext *cx, const jschar *chars, size_t length)
-{
-    JS_ASSERT(chars[length] == jschar(0));
-
-    if (!validateLength(cx, length))
-        return nullptr;
-    JSStableString *str = (JSStableString *)js_NewGCString<allowGC>(cx);
-    if (!str)
-        return nullptr;
-    str->init(chars, length);
-    return str;
-}
-
 template <js::AllowGC allowGC>
 MOZ_ALWAYS_INLINE JSInlineString *
 JSInlineString::new_(js::ThreadSafeContext *cx)
 {
     return (JSInlineString *)js_NewGCString<allowGC>(cx);
 }
 
 MOZ_ALWAYS_INLINE jschar *
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -65,19 +65,19 @@ JSString::sizeOfExcludingThis(mozilla::M
     // JSExternalString: don't count, the chars could be stored anywhere.
     if (isExternal())
         return 0;
 
     // JSInlineString, JSShortString [JSInlineAtom, JSShortAtom]: the chars are inline.
     if (isInline())
         return 0;
 
-    // JSAtom, JSStableString, JSUndependedString: measure the space for the
-    // chars.  For JSUndependedString, there is no need to count the base
-    // string, for the same reason as JSDependentString above.
+    // JSAtom, JSUndependedString: measure the space for the chars.  For
+    // JSUndependedString, there is no need to count the base string, for the
+    // same reason as JSDependentString above.
     JSFlatString &flat = asFlat();
     return mallocSizeOf(flat.chars());
 }
 
 #ifdef DEBUG
 
 void
 JSString::dumpChars(const jschar *s, size_t n)
@@ -465,31 +465,16 @@ JSDependentString::undepend(ExclusiveCon
      * Transform *this into an undepended string so 'base' will remain rooted
      * for the benefit of any other dependent string that depends on *this.
      */
     d.lengthAndFlags = buildLengthAndFlags(n, UNDEPENDED_FLAGS);
 
     return &this->asFlat();
 }
 
-JSStableString *
-JSInlineString::uninline(ExclusiveContext *maybecx)
-{
-    JS_ASSERT(isInline());
-    size_t n = length();
-    jschar *news = maybecx ? maybecx->pod_malloc<jschar>(n + 1) : js_pod_malloc<jschar>(n + 1);
-    if (!news)
-        return nullptr;
-    js_strncpy(news, d.inlineStorage, n);
-    news[n] = 0;
-    d.u1.chars = news;
-    JS_ASSERT(!isInline());
-    return &asStable();
-}
-
 bool
 JSFlatString::isIndexSlow(uint32_t *indexp) const
 {
     const jschar *s = charsZ();
     jschar ch = *s;
 
     if (!JS7_ISDEC(ch))
         return false;
@@ -596,25 +581,25 @@ const StaticStrings::SmallChar StaticStr
 bool
 StaticStrings::init(JSContext *cx)
 {
     AutoLockForExclusiveAccess lock(cx);
     AutoCompartment ac(cx, cx->runtime()->atomsCompartment());
 
     for (uint32_t i = 0; i < UNIT_STATIC_LIMIT; i++) {
         jschar buffer[] = { jschar(i), '\0' };
-        JSFlatString *s = js_NewStringCopyN<CanGC>(cx, buffer, 1);
+        JSFlatString *s = js_NewStringCopyN<NoGC>(cx, buffer, 1);
         if (!s)
             return false;
         unitStaticTable[i] = s->morphAtomizedStringIntoAtom();
     }
 
     for (uint32_t i = 0; i < NUM_SMALL_CHARS * NUM_SMALL_CHARS; i++) {
         jschar buffer[] = { FROM_SMALL_CHAR(i >> 6), FROM_SMALL_CHAR(i & 0x3F), '\0' };
-        JSFlatString *s = js_NewStringCopyN<CanGC>(cx, buffer, 2);
+        JSFlatString *s = js_NewStringCopyN<NoGC>(cx, buffer, 2);
         if (!s)
             return false;
         length2StaticTable[i] = s->morphAtomizedStringIntoAtom();
     }
 
     for (uint32_t i = 0; i < INT_STATIC_LIMIT; i++) {
         if (i < 10) {
             intStaticTable[i] = unitStaticTable[i + '0'];
@@ -622,17 +607,17 @@ StaticStrings::init(JSContext *cx)
             size_t index = ((size_t)TO_SMALL_CHAR((i / 10) + '0') << 6) +
                 TO_SMALL_CHAR((i % 10) + '0');
             intStaticTable[i] = length2StaticTable[index];
         } else {
             jschar buffer[] = { jschar('0' + (i / 100)),
                                 jschar('0' + ((i / 10) % 10)),
                                 jschar('0' + (i % 10)),
                                 '\0' };
-            JSFlatString *s = js_NewStringCopyN<CanGC>(cx, buffer, 3);
+            JSFlatString *s = js_NewStringCopyN<NoGC>(cx, buffer, 3);
             if (!s)
                 return false;
             intStaticTable[i] = s->morphAtomizedStringIntoAtom();
         }
     }
 
     return true;
 }
--- a/js/src/vm/String.h
+++ b/js/src/vm/String.h
@@ -20,17 +20,16 @@
 #include "gc/Rooting.h"
 #include "js/CharacterEncoding.h"
 #include "js/RootingAPI.h"
 
 class JSDependentString;
 class JSExtensibleString;
 class JSExternalString;
 class JSInlineString;
-class JSStableString;
 class JSRope;
 
 namespace js {
 
 class StaticStrings;
 class PropertyName;
 
 /* The buffer length required to contain any unsigned 32-bit integer. */
@@ -91,18 +90,16 @@ static const size_t UINT32_CHAR_BUFFER_L
  *  | JSRope                    leftChild, rightChild / -
  *  |
  * JSLinearString (abstract)    chars / might be null-terminated
  *  | \
  *  | JSDependentString         base / -
  *  |
  * JSFlatString                 - / null terminated
  *  |  |
- *  |  +-- JSStableString       - / may have external pointers into char array
- *  |  |
  *  |  +-- JSExternalString     - / char array memory managed by embedding
  *  |  |
  *  |  +-- JSExtensibleString   capacity / no external pointers into char array
  *  |  |
  *  |  +-- JSUndependedString   original dependent base / -
  *  |  |
  *  |  +-- JSInlineString       - / chars stored in header
  *  |         \
@@ -116,17 +113,16 @@ static const size_t UINT32_CHAR_BUFFER_L
  * Classes (since there are no virtual functions, pure or not, in this
  * hierarchy), but have the same meaning: there are no strings with this type as
  * its most-derived type.
  *
  * Technically, there are three additional most-derived types that satisfy the
  * invariants of more than one of the abovementioned most-derived types:
  *  - InlineAtom = JSInlineString + JSAtom (atom with inline chars)
  *  - ShortAtom  = JSShortString  + JSAtom (atom with (more) inline chars)
- *  - StableAtom = JSStableString + JSAtom (atom with out-of-line chars)
  *
  * Derived string types can be queried from ancestor types via isX() and
  * retrieved with asX() debug-only-checked casts.
  *
  * The ensureX() operations mutate 'this' in place to effectively the type to be
  * at least X (e.g., ensureLinear will change a JSRope to be a JSFlatString).
  */
 
@@ -183,17 +179,16 @@ class JSString : public js::gc::Barriere
      *   Rope         0000       0000
      *   Linear       -         !0000
      *   HasBase      -          xxx1
      *   Dependent    0001       0001
      *   Flat         -          isLinear && !isDependent
      *   Undepended   0011       0011
      *   Extensible   0010       0010
      *   Inline       0100       isFlat && !isExtensible && (u1.chars == inlineStorage) || isInt32)
-     *   Stable       0100       isFlat && !isExtensible && (u1.chars != inlineStorage)
      *   Short        0100       header in FINALIZE_SHORT_STRING arena
      *   External     0100       header in FINALIZE_EXTERNAL_STRING arena
      *   Int32        0110       x110 (NYI, Bug 654190)
      *   Atom         1000       1xxx
      *   InlineAtom   1000       1000 && is Inline
      *   ShortAtom    1000       1000 && is Short
      *   Int32Atom    1110       1110 (NYI, Bug 654190)
      *
@@ -286,17 +281,16 @@ class JSString : public js::gc::Barriere
                                  js::ScopedJSFreePtr<jschar> &out) const;
     inline bool copyNonPureCharsZ(js::ThreadSafeContext *cx,
                                   js::ScopedJSFreePtr<jschar> &out) const;
 
     /* Fallible conversions to more-derived string types. */
 
     inline JSLinearString *ensureLinear(js::ExclusiveContext *cx);
     inline JSFlatString *ensureFlat(js::ExclusiveContext *cx);
-    inline JSStableString *ensureStable(js::ExclusiveContext *cx);
 
     static bool ensureLinear(js::ExclusiveContext *cx, JSString *str) {
         return str->ensureLinear(cx) != nullptr;
     }
 
     /* Type query and debug-checked casts */
 
     MOZ_ALWAYS_INLINE
@@ -362,22 +356,16 @@ class JSString : public js::gc::Barriere
     MOZ_ALWAYS_INLINE
     JSInlineString &asInline() const {
         JS_ASSERT(isInline());
         return *(JSInlineString *)this;
     }
 
     bool isShort() const;
 
-    MOZ_ALWAYS_INLINE
-    JSStableString &asStable() const {
-        JS_ASSERT(!isInline());
-        return *(JSStableString *)this;
-    }
-
     /* For hot code, prefer other type queries. */
     bool isExternal() const;
 
     MOZ_ALWAYS_INLINE
     JSExternalString &asExternal() const {
         JS_ASSERT(isExternal());
         return *(JSExternalString *)this;
     }
@@ -539,17 +527,23 @@ class JSFlatString : public JSLinearStri
 {
     /* Vacuous and therefore unimplemented. */
     JSFlatString *ensureFlat(JSContext *cx) MOZ_DELETE;
     bool isFlat() const MOZ_DELETE;
     JSFlatString &asFlat() const MOZ_DELETE;
 
     bool isIndexSlow(uint32_t *indexp) const;
 
+    void init(const jschar *chars, size_t length);
+
   public:
+    template <js::AllowGC allowGC>
+    static inline JSFlatString *new_(js::ThreadSafeContext *cx,
+                                     const jschar *chars, size_t length);
+
     MOZ_ALWAYS_INLINE
     const jschar *charsZ() const {
         JS_ASSERT(JSString::isFlat());
         return chars();
     }
 
     /*
      * Returns true if this string's characters store an unsigned 32-bit
@@ -578,98 +572,16 @@ class JSFlatString : public JSLinearStri
         return &asAtom();
     }
 
     inline void finalize(js::FreeOp *fop);
 };
 
 JS_STATIC_ASSERT(sizeof(JSFlatString) == sizeof(JSString));
 
-class JSStableString : public JSFlatString
-{
-    void init(const jschar *chars, size_t length);
-
-  public:
-    template <js::AllowGC allowGC>
-    static inline JSStableString *new_(js::ThreadSafeContext *cx,
-                                       const jschar *chars, size_t length);
-
-    MOZ_ALWAYS_INLINE
-    JS::StableCharPtr chars() const {
-        JS_ASSERT(!JSString::isInline());
-        return JS::StableCharPtr(d.u1.chars, length());
-    }
-
-    MOZ_ALWAYS_INLINE
-    JS::StableTwoByteChars range() const {
-        JS_ASSERT(!JSString::isInline());
-        return JS::StableTwoByteChars(d.u1.chars, length());
-    }
-};
-
-JS_STATIC_ASSERT(sizeof(JSStableString) == sizeof(JSString));
-
-#if !(defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING))
-namespace JS {
-/*
- * Specialization of Rooted<T> to explicitly root the string rather than
- * relying on conservative stack scanning.
- *
- * In exact-gc builds, Rooted<T> already keeps the T reachable, so this hack is
- * ifdef'd out. In non-exact-gc builds, conservative scanning would ordinarily
- * pick up the slack. However in the case where the Rooted pointer is no longer
- * used, but some subobject or malloc'd memory with the same lifetime may be
- * used, conservative scanning can fail. JSStableString's chars() method makes
- * it particularly attractive to use that way, so we explicitly keep the
- * JSString gc-reachable for the full lifetime of the Rooted<JSStableString *>.
- *
- * It would suffice simply to force the pointer to remain on the stack, a la
- * JS::Anchor<T>, but for some reason using that voodoo here seems to cause
- * some compilers (clang, VC++ with PGO) to generate incorrect code.
- */
-template <>
-class Rooted<JSStableString *>
-{
-  public:
-    Rooted(JSContext *cx, JSStableString *initial = nullptr
-           MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-      : rooter(cx, initial)
-    {
-        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-    }
-
-    operator JSStableString *() const { return get(); }
-    JSStableString * operator ->() const { return get(); }
-    JSStableString ** address() { return reinterpret_cast<JSStableString **>(rooter.addr()); }
-    JSStableString * const * address() const {
-        return reinterpret_cast<JSStableString * const *>(rooter.addr());
-    }
-    JSStableString * get() const { return static_cast<JSStableString *>(rooter.string()); }
-
-    Rooted & operator =(JSStableString *value)
-    {
-        JS_ASSERT(!js::GCMethods<JSStableString *>::poisoned(value));
-        rooter.setString(value);
-        return *this;
-    }
-
-    Rooted & operator =(const Rooted &value)
-    {
-        rooter.setString(value.get());
-        return *this;
-    }
-
-  private:
-    JS::AutoStringRooter rooter;
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-    Rooted(const Rooted &) MOZ_DELETE;
-};
-}
-#endif
-
 class JSExtensibleString : public JSFlatString
 {
     /* Vacuous and therefore unimplemented. */
     bool isExtensible() const MOZ_DELETE;
     JSExtensibleString &asExtensible() const MOZ_DELETE;
 
   public:
     MOZ_ALWAYS_INLINE
@@ -686,18 +598,16 @@ class JSInlineString : public JSFlatStri
     static const size_t MAX_INLINE_LENGTH = NUM_INLINE_CHARS - 1;
 
   public:
     template <js::AllowGC allowGC>
     static inline JSInlineString *new_(js::ThreadSafeContext *cx);
 
     inline jschar *init(size_t length);
 
-    JSStableString *uninline(js::ExclusiveContext *cx);
-
     inline void resetLength(size_t length);
 
     static bool lengthFits(size_t length) {
         return length <= MAX_INLINE_LENGTH;
     }
 
     static size_t offsetOfInlineStorage() {
         return offsetof(JSInlineString, d.inlineStorage);
@@ -1089,41 +999,16 @@ JSString::ensureFlat(js::ExclusiveContex
 {
     return isFlat()
            ? &asFlat()
            : isDependent()
              ? asDependent().undepend(cx)
              : asRope().flatten(cx);
 }
 
-MOZ_ALWAYS_INLINE JSStableString *
-JSString::ensureStable(js::ExclusiveContext *maybecx)
-{
-    if (isRope()) {
-        JSFlatString *flat = asRope().flatten(maybecx);
-        if (!flat)
-            return nullptr;
-        JS_ASSERT(!flat->isInline());
-        return &flat->asStable();
-    }
-
-    if (isDependent()) {
-        JSFlatString *flat = asDependent().undepend(maybecx);
-        if (!flat)
-            return nullptr;
-        return &flat->asStable();
-    }
-
-    if (!isInline())
-        return &asStable();
-
-    JS_ASSERT(isInline());
-    return asInline().uninline(maybecx);
-}
-
 inline JSLinearString *
 JSString::base() const
 {
     JS_ASSERT(hasBase());
     JS_ASSERT(!d.s.u2.base->isInline());
     return d.s.u2.base;
 }
 
--- a/js/src/vm/StringBuffer.h
+++ b/js/src/vm/StringBuffer.h
@@ -42,17 +42,17 @@ class StringBuffer
 
   public:
     explicit StringBuffer(ExclusiveContext *cx) : cb(cx) { }
 
     inline bool reserve(size_t len) { return cb.reserve(len); }
     inline bool resize(size_t len) { return cb.resize(len); }
     inline bool append(const jschar c) { return cb.append(c); }
     inline bool append(const jschar *chars, size_t len) { return cb.append(chars, len); }
-    inline bool append(const JS::CharPtr chars, size_t len) { return cb.append(chars.get(), len); }
+    inline bool append(const JS::ConstCharPtr chars, size_t len) { return cb.append(chars.get(), len); }
     inline bool append(const jschar *begin, const jschar *end) { return cb.append(begin, end); }
     inline bool append(JSString *str);
     inline bool append(JSLinearString *str);
     inline bool appendN(const jschar c, size_t n) { return cb.appendN(c, n); }
     inline bool appendInflated(const char *cstr, size_t len);
 
     template <size_t ArrayLength>
     bool append(const char (&array)[ArrayLength]) {
@@ -61,17 +61,17 @@ class StringBuffer
 
     /* Infallible variants usable when the corresponding space is reserved. */
     void infallibleAppend(const jschar c) {
         cb.infallibleAppend(c);
     }
     void infallibleAppend(const jschar *chars, size_t len) {
         cb.infallibleAppend(chars, len);
     }
-    void infallibleAppend(const JS::CharPtr chars, size_t len) {
+    void infallibleAppend(const JS::ConstCharPtr chars, size_t len) {
         cb.infallibleAppend(chars.get(), len);
     }
     void infallibleAppend(const jschar *begin, const jschar *end) {
         cb.infallibleAppend(begin, end);
     }
     void infallibleAppendN(const jschar c, size_t n) {
         cb.infallibleAppendN(c, n);
     }
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -1334,24 +1334,22 @@ JSStructuredCloneReader::startRead(Value
         if (tag2 != SCTAG_STRING) {
             JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
                                  JSMSG_SC_BAD_SERIALIZED_DATA, "regexp");
             return false;
         }
         JSString *str = readString(nchars);
         if (!str)
             return false;
-        JSStableString *stable = str->ensureStable(context());
-        if (!stable)
+        JSFlatString *flat = str->ensureFlat(context());
+        if (!flat)
             return false;
 
-        size_t length = stable->length();
-        const StableCharPtr chars = stable->chars();
-        RegExpObject *reobj = RegExpObject::createNoStatics(context(), chars.get(), length,
-                                                            flags, nullptr);
+        RegExpObject *reobj = RegExpObject::createNoStatics(context(), flat->chars(),
+                                                            flat->length(), flags, nullptr);
         if (!reobj)
             return false;
         vp->setObject(*reobj);
         break;
       }
 
       case SCTAG_ARRAY_OBJECT:
       case SCTAG_OBJECT_OBJECT: {
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -3461,16 +3461,17 @@ const Class ArrayBufferObject::class_ = 
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     nullptr,        /* finalize    */
     nullptr,        /* call        */
     nullptr,        /* hasInstance */
     nullptr,        /* construct   */
     ArrayBufferObject::obj_trace,
+    JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     {
         ArrayBufferObject::obj_lookupGeneric,
         ArrayBufferObject::obj_lookupProperty,
         ArrayBufferObject::obj_lookupElement,
         ArrayBufferObject::obj_lookupSpecial,
         ArrayBufferObject::obj_defineGeneric,
         ArrayBufferObject::obj_defineProperty,
@@ -3620,22 +3621,18 @@ IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Flo
     JS_EnumerateStub,                                                          \
     JS_ResolveStub,                                                            \
     JS_ConvertStub,                                                            \
     nullptr,                 /* finalize */                                    \
     nullptr,                 /* call        */                                 \
     nullptr,                 /* hasInstance */                                 \
     nullptr,                 /* construct   */                                 \
     ArrayBufferViewObject::trace, /* trace  */                                 \
-    {                                                                          \
-        nullptr,    /* outerObject */                                          \
-        nullptr,    /* innerObject */                                          \
-        nullptr,    /* iteratorObject  */                                      \
-        false,      /* isWrappedNative */                                      \
-    },                                                                         \
+    JS_NULL_CLASS_SPEC,                                                        \
+    JS_NULL_CLASS_EXT,                                                         \
     {                                                                          \
         _typedArray##Object::obj_lookupGeneric,                                \
         _typedArray##Object::obj_lookupProperty,                               \
         _typedArray##Object::obj_lookupElement,                                \
         _typedArray##Object::obj_lookupSpecial,                                \
         _typedArray##Object::obj_defineGeneric,                                \
         _typedArray##Object::obj_defineProperty,                               \
         _typedArray##Object::obj_defineElement,                                \
@@ -3833,18 +3830,16 @@ const Class DataViewObject::class_ = {
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     nullptr,                 /* finalize */
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
     ArrayBufferViewObject::trace, /* trace  */
-    JS_NULL_CLASS_EXT,
-    JS_NULL_OBJECT_OPS
 };
 
 const JSFunctionSpec DataViewObject::jsfuncs[] = {
     JS_FN("getInt8",    DataViewObject::fun_getInt8,      1,0),
     JS_FN("getUint8",   DataViewObject::fun_getUint8,     1,0),
     JS_FN("getInt16",   DataViewObject::fun_getInt16,     2,0),
     JS_FN("getUint16",  DataViewObject::fun_getUint16,    2,0),
     JS_FN("getInt32",   DataViewObject::fun_getInt32,     2,0),
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -669,16 +669,17 @@ const XPCWrappedNativeJSClass XPC_WN_NoH
     XPC_WN_Shared_Convert,             // convert
     XPC_WN_NoHelper_Finalize,          // finalize
 
     /* Optionally non-null members start here. */
     nullptr,                         // call
     nullptr,                         // construct
     nullptr,                         // hasInstance
     XPC_WN_NoHelper_Trace,          // trace
+    JS_NULL_CLASS_SPEC,
 
     // ClassExtension
     {
         nullptr, // outerObject
         nullptr, // innerObject
         nullptr, // iteratorObject
         true,   // isWrappedNative
     },
@@ -1422,16 +1423,17 @@ const js::Class XPC_WN_ModsAllowed_WithC
     XPC_WN_Shared_Proto_Finalize,   // finalize;
 
     /* Optionally non-null members start here. */
     nullptr,                         // call;
     nullptr,                         // construct;
     nullptr,                         // hasInstance;
     XPC_WN_Shared_Proto_Trace,      // trace;
 
+    JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     XPC_WN_WithCall_ObjectOps
 };
 
 const js::Class XPC_WN_ModsAllowed_NoCall_Proto_JSClass = {
     "XPC_WN_ModsAllowed_NoCall_Proto_JSClass", // name;
     WRAPPER_SLOTS,                  // flags;
 
@@ -1446,16 +1448,17 @@ const js::Class XPC_WN_ModsAllowed_NoCal
     XPC_WN_Shared_Proto_Finalize,   // finalize;
 
     /* Optionally non-null members start here. */
     nullptr,                         // call;
     nullptr,                         // construct;
     nullptr,                         // hasInstance;
     XPC_WN_Shared_Proto_Trace,      // trace;
 
+    JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     XPC_WN_NoCall_ObjectOps
 };
 
 /***************************************************************************/
 
 static bool
 XPC_WN_OnlyIWrite_Proto_AddPropertyStub(JSContext *cx, HandleObject obj, HandleId id,
@@ -1532,16 +1535,17 @@ const js::Class XPC_WN_NoMods_WithCall_P
     XPC_WN_Shared_Proto_Finalize,              // finalize;
 
     /* Optionally non-null members start here. */
     nullptr,                         // call;
     nullptr,                         // construct;
     nullptr,                         // hasInstance;
     XPC_WN_Shared_Proto_Trace,      // trace;
 
+    JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     XPC_WN_WithCall_ObjectOps
 };
 
 const js::Class XPC_WN_NoMods_NoCall_Proto_JSClass = {
     "XPC_WN_NoMods_NoCall_Proto_JSClass",      // name;
     WRAPPER_SLOTS,                             // flags;
 
@@ -1556,16 +1560,17 @@ const js::Class XPC_WN_NoMods_NoCall_Pro
     XPC_WN_Shared_Proto_Finalize,              // finalize;
 
     /* Optionally non-null members start here. */
     nullptr,                         // call;
     nullptr,                         // construct;
     nullptr,                         // hasInstance;
     XPC_WN_Shared_Proto_Trace,      // trace;
 
+    JS_NULL_CLASS_SPEC,
     JS_NULL_CLASS_EXT,
     XPC_WN_NoCall_ObjectOps
 };
 
 /***************************************************************************/
 
 static bool
 XPC_WN_TearOff_Enumerate(JSContext *cx, HandleObject obj)
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -1556,17 +1556,20 @@ SetVisibleRegionForLayer(Layer* aLayer, 
 {
   gfx3DMatrix transform;
   To3DMatrix(aLayer->GetTransform(), transform);
 
   // if 'transform' is not invertible, then nothing will be displayed
   // for the layer, so it doesn't really matter what we do here
   gfxRect itemVisible(aRestrictToRect.x, aRestrictToRect.y,
                       aRestrictToRect.width, aRestrictToRect.height);
-  gfxRect layerVisible = transform.Inverse().ProjectRectBounds(itemVisible);
+  nsIntRect childBounds = aLayerVisibleRegion.GetBounds();
+  gfxRect childGfxBounds(childBounds.x, childBounds.y,
+                         childBounds.width, childBounds.height);
+  gfxRect layerVisible = transform.UntransformBounds(itemVisible, childGfxBounds);
   layerVisible.RoundOut();
 
   nsIntRect visibleRect;
   if (!gfxUtils::GfxRectToIntRect(layerVisible, &visibleRect)) {
     aLayer->SetVisibleRegion(nsIntRegion());
   } else {
     nsIntRegion rgn;
     rgn.And(aLayerVisibleRegion, visibleRect);
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -21,16 +21,17 @@
 #include "nsIPresShell.h"
 #include "nsRegion.h"
 #include "nsStyleStructInlines.h"
 #include "nsStyleTransformMatrix.h"
 #include "gfxMatrix.h"
 #include "nsSVGIntegrationUtils.h"
 #include "nsLayoutUtils.h"
 #include "nsIScrollableFrame.h"
+#include "nsIFrameInlines.h"
 #include "nsThemeConstants.h"
 #include "LayerTreeInvalidation.h"
 
 #include "imgIContainer.h"
 #include "BasicLayers.h"
 #include "nsBoxFrame.h"
 #include "nsViewportFrame.h"
 #include "nsSubDocumentFrame.h"
@@ -1395,17 +1396,17 @@ void nsDisplayList::HitTest(nsDisplayLis
           item->Frame()->Preserves3D()) {
         if (outFrames.Length()) {
           nsDisplayTransform *transform = static_cast<nsDisplayTransform*>(item);
           nsPoint point = aRect.TopLeft();
           // A 1x1 rect means a point, otherwise use the center of the rect
           if (aRect.width != 1 || aRect.height != 1) {
             point = aRect.Center();
           }
-          temp.AppendElement(FramesWithDepth(transform->GetHitDepthAtPoint(point)));
+          temp.AppendElement(FramesWithDepth(transform->GetHitDepthAtPoint(aBuilder, point)));
           writeFrames = &temp[temp.Length() - 1].mFrames;
         }
       } else {
         // We may have just finished a run of consecutive preserve-3d transforms,
         // so flush these into the destination array before processing our frame list.
         FlushFramesArray(temp, aOutFrames);
       }
 
@@ -3647,31 +3648,60 @@ nsDisplayScrollLayer::GetLayerState(nsDi
                                     LayerManager* aManager,
                                     const ContainerLayerParameters& aParameters)
 {
   // Force this as a layer so we can scroll asynchronously.
   // This causes incorrect rendering for rounded clips!
   return LAYER_ACTIVE_FORCE;
 }
 
+// Check if we are going to clip an abs pos item that we don't contain.
+// Root scroll frames clip all their descendants, so we don't need to worry
+// about them.
+bool
+WouldCauseIncorrectClippingOnAbsPosItem(nsDisplayListBuilder* aBuilder,
+                                        nsDisplayScrollLayer* aItem)
+{
+  nsIFrame* scrollFrame = aItem->GetScrollFrame();
+  nsIPresShell* presShell = scrollFrame->PresContext()->PresShell();
+  if (scrollFrame == presShell->GetRootScrollFrame()) {
+    return false;
+  }
+  nsIFrame* scrolledFrame = aItem->GetScrolledFrame();
+  nsIFrame* frame = aItem->Frame();
+  if (frame == scrolledFrame || !frame->IsAbsolutelyPositioned() ||
+      nsLayoutUtils::IsAncestorFrameCrossDoc(scrollFrame, frame, presShell->GetRootFrame())) {
+    return false;
+  }
+  if (!aItem->GetClip().IsRectAffectedByClip(aItem->GetChildren()->GetBounds(aBuilder))) {
+    return false;
+  }
+  return true;
+}
+
 bool
 nsDisplayScrollLayer::TryMerge(nsDisplayListBuilder* aBuilder,
                                nsDisplayItem* aItem)
 {
   if (aItem->GetType() != TYPE_SCROLL_LAYER) {
     return false;
   }
   nsDisplayScrollLayer* other = static_cast<nsDisplayScrollLayer*>(aItem);
   if (other->mScrolledFrame != this->mScrolledFrame) {
     return false;
   }
   if (aItem->GetClip() != GetClip()) {
     return false;
   }
 
+  if (WouldCauseIncorrectClippingOnAbsPosItem(aBuilder, this) ||
+      WouldCauseIncorrectClippingOnAbsPosItem(aBuilder, other)) {
+    return false;
+  }
+
   NS_ASSERTION(other->mReferenceFrame == mReferenceFrame,
                "Must have the same reference frame!");
 
   FrameProperties props = mScrolledFrame->Properties();
   props.Set(nsIFrame::ScrollLayerCount(),
     reinterpret_cast<void*>(GetScrollLayerCount() - 1));
 
   // Swap frames with the other item before doing MergeFrom.
@@ -3699,22 +3729,26 @@ PropagateClip(nsDisplayListBuilder* aBui
       PropagateClip(aBuilder, aClip, list);
     }
   }
 }
 
 bool
 nsDisplayScrollLayer::ShouldFlattenAway(nsDisplayListBuilder* aBuilder)
 {
-  if (GetScrollLayerCount() > 1) {
+  bool badAbsPosClip = WouldCauseIncorrectClippingOnAbsPosItem(aBuilder, this);
+  if (GetScrollLayerCount() > 1 || badAbsPosClip) {
     // Propagate our clip to our children. The clip for the scroll frame is
     // on this item, but not our child items so that they can draw non-visible
     // parts of the display port. But if we are flattening we failed and can't
     // draw the extra content, so it needs to be clipped.
-    PropagateClip(aBuilder, GetClip(), &mList);
+    // But don't induce our clip on abs pos frames that we shouldn't be clipping.
+    if (!badAbsPosClip) {
+      PropagateClip(aBuilder, GetClip(), &mList);
+    }
     return true;
   }
   if (mFrame != mScrolledFrame) {
     mMergedFrames.AppendElement(mFrame);
     mFrame = mScrolledFrame;
   }
   return false;
 }
@@ -4454,23 +4488,18 @@ bool nsDisplayTransform::ComputeVisibili
                                              nsRegion *aVisibleRegion,
                                              const nsRect& aAllowVisibleRegionExpansion)
 {
   /* As we do this, we need to be sure to
    * untransform the visible rect, since we want everything that's painting to
    * think that it's painting in its original rectangular coordinate space.
    * If we can't untransform, take the entire overflow rect */
   nsRect untransformedVisibleRect;
-  // GetTransform always operates in dev pixels.
-  float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
   if (ShouldPrerenderTransformedContent(aBuilder, mFrame) ||
-      !UntransformRectMatrix(mVisibleRect,
-                             GetTransform(),
-                             factor,
-                             &untransformedVisibleRect))
+      !UntransformVisibleRect(aBuilder, &untransformedVisibleRect))
   {
     untransformedVisibleRect = mFrame->GetVisualOverflowRectRelativeToSelf();
   }
   nsRegion untransformedVisible = untransformedVisibleRect;
   // Call RecomputeVisiblity instead of ComputeVisibility since
   // nsDisplayItem::ComputeVisibility should only be called from
   // nsDisplayList::ComputeVisibility (which sets mVisibleRect on the item)
   mStoredList.RecomputeVisibility(aBuilder, &untransformedVisible);
@@ -4501,43 +4530,57 @@ void nsDisplayTransform::HitTest(nsDispl
   if (!IsFrameVisible(mFrame, matrix)) {
     return;
   }
 
   /* We want to go from transformed-space to regular space.
    * Thus we have to invert the matrix, which normally does
    * the reverse operation (e.g. regular->transformed)
    */
+  bool snap;
+  nsRect childBounds = mStoredList.GetBounds(aBuilder, &snap);
+  gfxRect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor),
+                         NSAppUnitsToFloatPixels(childBounds.y, factor),
+                         NSAppUnitsToFloatPixels(childBounds.width, factor),
+                         NSAppUnitsToFloatPixels(childBounds.height, factor));
 
   /* Now, apply the transform and pass it down the channel. */
   nsRect resultingRect;
   if (aRect.width == 1 && aRect.height == 1) {
     // Magic width/height indicating we're hit testing a point, not a rect
-    gfxPoint point = matrix.Inverse().ProjectPoint(
-                       gfxPoint(NSAppUnitsToFloatPixels(aRect.x, factor),
-                                NSAppUnitsToFloatPixels(aRect.y, factor)));
+    gfxPoint point;
+    if (!matrix.UntransformPoint(gfxPoint(NSAppUnitsToFloatPixels(aRect.x, factor),
+                                          NSAppUnitsToFloatPixels(aRect.y, factor)),
+                                 childGfxBounds,
+                                 &point)) {
+      return;
+    }
 
     resultingRect = nsRect(NSFloatPixelsToAppUnits(float(point.x), factor),
                            NSFloatPixelsToAppUnits(float(point.y), factor),
                            1, 1);
 
   } else {
     gfxRect originalRect(NSAppUnitsToFloatPixels(aRect.x, factor),
                          NSAppUnitsToFloatPixels(aRect.y, factor),
                          NSAppUnitsToFloatPixels(aRect.width, factor),
                          NSAppUnitsToFloatPixels(aRect.height, factor));
 
-    gfxRect rect = matrix.Inverse().ProjectRectBounds(originalRect);;
+    gfxRect rect = matrix.UntransformBounds(originalRect, childGfxBounds);
 
     resultingRect = nsRect(NSFloatPixelsToAppUnits(float(rect.X()), factor),
                            NSFloatPixelsToAppUnits(float(rect.Y()), factor),
                            NSFloatPixelsToAppUnits(float(rect.Width()), factor),
                            NSFloatPixelsToAppUnits(float(rect.Height()), factor));
   }
 
+  if (resultingRect.IsEmpty()) {
+    return;
+  }
+
 
 #ifdef DEBUG_HIT
   printf("Frame: %p\n", dynamic_cast<void *>(mFrame));
   printf("  Untransformed point: (%f, %f)\n", resultingRect.X(), resultingRect.Y());
   uint32_t originalFrameCount = aOutFrames.Length();
 #endif
 
   mStoredList.HitTest(aBuilder, resultingRect, aState, aOutFrames);
@@ -4547,27 +4590,37 @@ void nsDisplayTransform::HitTest(nsDispl
     printf("  Hit! Time: %f, first frame: %p\n", static_cast<double>(clock()),
            dynamic_cast<void *>(aOutFrames.ElementAt(0)));
   printf("=== end of hit test ===\n");
 #endif
 
 }
 
 float
-nsDisplayTransform::GetHitDepthAtPoint(const nsPoint& aPoint)
+nsDisplayTransform::GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, const nsPoint& aPoint)
 {
   // GetTransform always operates in dev pixels.
   float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
   gfx3DMatrix matrix = GetTransform();
 
   NS_ASSERTION(IsFrameVisible(mFrame, matrix), "We can't have hit a frame that isn't visible!");
 
-  gfxPoint point =
-    matrix.Inverse().ProjectPoint(gfxPoint(NSAppUnitsToFloatPixels(aPoint.x, factor),
-                                           NSAppUnitsToFloatPixels(aPoint.y, factor)));
+  bool snap;
+  nsRect childBounds = mStoredList.GetBounds(aBuilder, &snap);
+  gfxRect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor),
+                         NSAppUnitsToFloatPixels(childBounds.y, factor),
+                         NSAppUnitsToFloatPixels(childBounds.width, factor),
+                         NSAppUnitsToFloatPixels(childBounds.height, factor));
+
+  gfxPoint point;
+  DebugOnly<bool> result = matrix.UntransformPoint(gfxPoint(NSAppUnitsToFloatPixels(aPoint.x, factor),
+                                                            NSAppUnitsToFloatPixels(aPoint.y, factor)),
+                                                   childGfxBounds,
+                                                   &point);
+  NS_ASSERTION(result, "Why are we trying to get the depth for a point we didn't hit?");
 
   gfxPoint3D transformed = matrix.Transform3D(gfxPoint3D(point.x, point.y, 0));
   return transformed.z;
 }
 
 /* The bounding rectangle for the object is the overflow rectangle translated
  * by the reference point.
  */
@@ -4601,26 +4654,24 @@ nsRect nsDisplayTransform::GetBounds(nsD
  * mStoredList.GetVisibleRect().Contains(untransformedVisible), then it
  * certainly contains the actual (non-axis-aligned) untransformed rect.
  */
 nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder *aBuilder,
                                              bool* aSnap)
 {
   *aSnap = false;
   nsRect untransformedVisible;
-  // GetTransform always operates in dev pixels.
-  float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
   // If we're going to prerender all our content, pretend like we
   // don't have opqaue content so that everything under us is rendered
   // as well.  That will increase graphics memory usage if our frame
   // covers the entire window, but it allows our transform to be
   // updated extremely cheaply, without invalidating any other
   // content.
   if (ShouldPrerenderTransformedContent(aBuilder, mFrame) ||
-      !UntransformRectMatrix(mVisibleRect, GetTransform(), factor, &untransformedVisible)) {
+      !UntransformVisibleRect(aBuilder, &untransformedVisible)) {
       return nsRegion();
   }
 
   const gfx3DMatrix& matrix = GetTransform();
 
   nsRegion result;
   gfxMatrix matrix2d;
   bool tmpSnap;
@@ -4634,19 +4685,17 @@ nsRegion nsDisplayTransform::GetOpaqueRe
 
 /* The transform is uniform if it fills the entire bounding rect and the
  * wrapped list is uniform.  See GetOpaqueRegion for discussion of why this
  * works.
  */
 bool nsDisplayTransform::IsUniform(nsDisplayListBuilder *aBuilder, nscolor* aColor)
 {
   nsRect untransformedVisible;
-  // GetTransform always operates in dev pixels.
-  float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
-  if (!UntransformRectMatrix(mVisibleRect, GetTransform(), factor, &untransformedVisible)) {
+  if (!UntransformVisibleRect(aBuilder, &untransformedVisible)) {
     return false;
   }
   const gfx3DMatrix& matrix = GetTransform();
 
   gfxMatrix matrix2d;
   return matrix.Is2D(&matrix2d) &&
          matrix2d.PreservesAxisAlignedRectangles() &&
          mStoredList.GetVisibleRect().Contains(untransformedVisible) &&
@@ -4732,53 +4781,45 @@ nsRect nsDisplayTransform::TransformRect
 
   float factor = aFrame->PresContext()->AppUnitsPerDevPixel();
   return nsLayoutUtils::MatrixTransformRectOut
     (aUntransformedBounds,
      GetResultingTransformMatrix(aFrame, aOrigin, factor, aBoundsOverride),
      factor);
 }
 
-bool nsDisplayTransform::UntransformRectMatrix(const nsRect &aUntransformedBounds,
-                                               const gfx3DMatrix& aMatrix,
-                                               float aAppUnitsPerPixel,
-                                               nsRect *aOutRect)
+bool nsDisplayTransform::UntransformVisibleRect(nsDisplayListBuilder* aBuilder,
+                                                nsRect *aOutRect)
 {
-  if (aMatrix.IsSingular())
+  const gfx3DMatrix& matrix = GetTransform();
+  if (matrix.IsSingular())
     return false;
 
-  gfxRect result(NSAppUnitsToFloatPixels(aUntransformedBounds.x, aAppUnitsPerPixel),
-                 NSAppUnitsToFloatPixels(aUntransformedBounds.y, aAppUnitsPerPixel),
-                 NSAppUnitsToFloatPixels(aUntransformedBounds.width, aAppUnitsPerPixel),
-                 NSAppUnitsToFloatPixels(aUntransformedBounds.height, aAppUnitsPerPixel));
+  // GetTransform always operates in dev pixels.
+  float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+  gfxRect result(NSAppUnitsToFloatPixels(mVisibleRect.x, factor),
+                 NSAppUnitsToFloatPixels(mVisibleRect.y, factor),
+                 NSAppUnitsToFloatPixels(mVisibleRect.width, factor),
+                 NSAppUnitsToFloatPixels(mVisibleRect.height, factor));
+
+  bool snap;
+  nsRect childBounds = mStoredList.GetBounds(aBuilder, &snap);
+  gfxRect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor),
+                         NSAppUnitsToFloatPixels(childBounds.y, factor),
+                         NSAppUnitsToFloatPixels(childBounds.width, factor),
+                         NSAppUnitsToFloatPixels(childBounds.height, factor));
 
   /* We want to untransform the matrix, so invert the transformation first! */
-  result = aMatrix.Inverse().ProjectRectBounds(result);
-
-  *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(result, aAppUnitsPerPixel);
+  result = matrix.UntransformBounds(result, childGfxBounds);
+
+  *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(result, factor);
 
   return true;
 }
 
-bool nsDisplayTransform::UntransformRect(const nsRect &aUntransformedBounds,
-                                           const nsIFrame* aFrame,
-                                           const nsPoint &aOrigin,
-                                           nsRect* aOutRect)
-{
-  NS_PRECONDITION(aFrame, "Can't take the transform based on a null frame!");
-
-  /* Grab the matrix.  If the transform is degenerate, just hand back the
-   * empty rect.
-   */
-  float factor = aFrame->PresContext()->AppUnitsPerDevPixel();
-  gfx3DMatrix matrix = GetResultingTransformMatrix(aFrame, aOrigin, factor);
-
-  return UntransformRectMatrix(aUntransformedBounds, matrix, factor, aOutRect);
-}
-
 nsDisplaySVGEffects::nsDisplaySVGEffects(nsDisplayListBuilder* aBuilder,
                                          nsIFrame* aFrame, nsDisplayList* aList)
     : nsDisplayWrapList(aBuilder, aFrame, aList),
       mEffectsBounds(aFrame->GetVisualOverflowRectRelativeToSelf())
 {
   MOZ_COUNT_CTOR(nsDisplaySVGEffects);
 }
 
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -2805,16 +2805,17 @@ public:
 
   virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE;
 
   // Get the number of nsDisplayScrollLayers for a scroll frame. Note that this
   // number does not include nsDisplayScrollInfoLayers. If this number is not 1
   // after merging, all the nsDisplayScrollLayers should flatten away.
   intptr_t GetScrollLayerCount();
 
+  virtual nsIFrame* GetScrollFrame() { return mScrollFrame; }
   virtual nsIFrame* GetScrolledFrame() { return mScrolledFrame; }
 
 #ifdef MOZ_DUMP_PAINTING
   virtual void WriteDebugInfo(nsACString& aTo) MOZ_OVERRIDE;
 #endif
 
 protected:
   nsIFrame* mScrollFrame;
@@ -3046,17 +3047,17 @@ public:
   }
 
   enum {
     INDEX_MAX = UINT32_MAX >> nsDisplayItem::TYPE_BITS
   };
 
   const gfx3DMatrix& GetTransform();
 
-  float GetHitDepthAtPoint(const nsPoint& aPoint);
+  float GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, const nsPoint& aPoint);
 
   /**
    * TransformRect takes in as parameters a rectangle (in aFrame's coordinate
    * space) and returns the smallest rectangle (in aFrame's coordinate space)
    * containing the transformed image of that rectangle.  That is, it takes
    * the four corners of the rectangle, transforms them according to the
    * matrix associated with the specified frame, then returns the smallest
    * rectangle containing the four transformed points.
@@ -3079,25 +3080,18 @@ public:
   static nsRect TransformRectOut(const nsRect &aUntransformedBounds, 
                                  const nsIFrame* aFrame,
                                  const nsPoint &aOrigin,
                                  const nsRect* aBoundsOverride = nullptr);
 
   /* UntransformRect is like TransformRect, except that it inverts the
    * transform.
    */
-  static bool UntransformRect(const nsRect &aUntransformedBounds, 
-                                const nsIFrame* aFrame,
-                                const nsPoint &aOrigin,
-                                nsRect* aOutRect);
-  
-  static bool UntransformRectMatrix(const nsRect &aUntransformedBounds, 
-                                    const gfx3DMatrix& aMatrix,
-                                    float aAppUnitsPerPixel,
-                                    nsRect* aOutRect);
+  bool UntransformVisibleRect(nsDisplayListBuilder* aBuilder,
+                              nsRect* aOutRect);
 
   static gfxPoint3D GetDeltaToTransformOrigin(const nsIFrame* aFrame,
                                               float aAppUnitsPerPixel,
                                               const nsRect* aBoundsOverride);
 
   static gfxPoint3D GetDeltaToPerspectiveOrigin(const nsIFrame* aFrame,
                                                 float aAppUnitsPerPixel);
 
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -1493,18 +1493,17 @@ DetachContainerRecurse(nsIDocShell *aShe
   if (viewer) {
     nsIDocument* doc = viewer->GetDocument();
     if (doc) {
       doc->SetContainer(nullptr);
     }
     nsRefPtr<nsPresContext> pc;
     viewer->GetPresContext(getter_AddRefs(pc));
     if (pc) {
-      pc->SetContainer(nullptr);
-      pc->SetLinkHandler(nullptr);
+      pc->Detach();
     }
     nsCOMPtr<nsIPresShell> presShell;
     viewer->GetPresShell(getter_AddRefs(presShell));
     if (presShell) {
       auto weakShell = static_cast<nsDocShell*>(aShell)->asWeakPtr();
       presShell->SetForwardingContainer(weakShell);
     }
   }
@@ -1617,18 +1616,17 @@ nsDocumentViewer::Destroy()
     // link traversals cannot affect the currently-loaded document.
     // When the presentation is restored, Open() and InitInternal() will reset
     // these pointers to their original values.
 
     if (mDocument) {
       mDocument->SetContainer(nullptr);
     }
     if (mPresContext) {
-      mPresContext->SetLinkHandler(nullptr);
-      mPresContext->SetContainer(nullptr);
+      mPresContext->Detach();
     }
     if (mPresShell) {
       mPresShell->SetForwardingContainer(mContainer);
     }
 
     // Do the same for our children.  Note that we need to get the child
     // docshells from the SHEntry now; the docshell will have cleared them.
     nsCOMPtr<nsIDocShellTreeItem> item;
@@ -4334,18 +4332,17 @@ nsDocumentViewer::DestroyPresShell()
   nsAutoScriptBlocker scriptBlocker;
   mPresShell->Destroy();
   mPresShell = nullptr;
 }
 
 void
 nsDocumentViewer::DestroyPresContext()
 {
-  mPresContext->SetContainer(nullptr);
-  mPresContext->SetLinkHandler(nullptr);
+  mPresContext->Detach();
   mPresContext = nullptr;
 }
 
 bool
 nsDocumentViewer::IsInitializedForPrintPreview()
 {
   return mInitializedForPrintPreview;
 }
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1833,32 +1833,31 @@ nsLayoutUtils::GetLayerTransformForFrame
 
   *aTransform =
     item->GetTransform();
   item->~nsDisplayTransform();
 
   return true;
 }
 
-static gfxPoint
+static bool
 TransformGfxPointFromAncestor(nsIFrame *aFrame,
                               const gfxPoint &aPoint,
-                              nsIFrame *aAncestor)
+                              nsIFrame *aAncestor,
+                              gfxPoint* aOut)
 {
   gfx3DMatrix ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor);
-  return ctm.Inverse().ProjectPoint(aPoint);
-}
-
-static gfxRect
-TransformGfxRectFromAncestor(nsIFrame *aFrame,
-                             const gfxRect &aRect,
-                             const nsIFrame *aAncestor)
-{
-  gfx3DMatrix ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor);
-  return ctm.Inverse().ProjectRectBounds(aRect);
+
+  float factor = aFrame->PresContext()->AppUnitsPerDevPixel();
+  nsRect childBounds = aFrame->GetVisualOverflowRectRelativeToSelf();
+  gfxRect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor),
+                         NSAppUnitsToFloatPixels(childBounds.y, factor),
+                         NSAppUnitsToFloatPixels(childBounds.width, factor),
+                         NSAppUnitsToFloatPixels(childBounds.height, factor));
+  return ctm.UntransformPoint(aPoint, childGfxBounds, aOut);
 }
 
 static gfxRect
 TransformGfxRectToAncestor(nsIFrame *aFrame,
                            const gfxRect &aRect,
                            const nsIFrame *aAncestor,
                            bool* aPreservesAxisAlignedRectangles = nullptr)
 {
@@ -1890,53 +1889,30 @@ nsLayoutUtils::TransformAncestorPointToF
 {
     SVGTextFrame* text = GetContainingSVGTextFrame(aFrame);
 
     float factor = aFrame->PresContext()->AppUnitsPerDevPixel();
     gfxPoint result(NSAppUnitsToFloatPixels(aPoint.x, factor),
                     NSAppUnitsToFloatPixels(aPoint.y, factor));
 
     if (text) {
-        result = TransformGfxPointFromAncestor(text, result, aAncestor);
+        if (!TransformGfxPointFromAncestor(text, result, aAncestor, &result)) {
+            return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+        }
         result = text->TransformFramePointToTextChild(result, aFrame);
     } else {
-        result = TransformGfxPointFromAncestor(aFrame, result, nullptr);
+        if (!TransformGfxPointFromAncestor(aFrame, result, nullptr, &result)) {
+            return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+        }
     }
 
     return nsPoint(NSFloatPixelsToAppUnits(float(result.x), factor),
                    NSFloatPixelsToAppUnits(float(result.y), factor));
 }
 
-nsRect 
-nsLayoutUtils::TransformAncestorRectToFrame(nsIFrame* aFrame,
-                                            const nsRect &aRect,
-                                            const nsIFrame* aAncestor)
-{
-    SVGTextFrame* text = GetContainingSVGTextFrame(aFrame);
-
-    float srcAppUnitsPerDevPixel = aAncestor->PresContext()->AppUnitsPerDevPixel();
-    gfxRect result(NSAppUnitsToFloatPixels(aRect.x, srcAppUnitsPerDevPixel),
-                   NSAppUnitsToFloatPixels(aRect.y, srcAppUnitsPerDevPixel),
-                   NSAppUnitsToFloatPixels(aRect.width, srcAppUnitsPerDevPixel),
-                   NSAppUnitsToFloatPixels(aRect.height, srcAppUnitsPerDevPixel));
-
-    if (text) {
-      result = TransformGfxRectFromAncestor(text, result, aAncestor);
-      result = text->TransformFrameRectToTextChild(result, aFrame);
-    } else {
-      result = TransformGfxRectFromAncestor(aFrame, result, aAncestor);
-    }
-
-    float destAppUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
-    return nsRect(NSFloatPixelsToAppUnits(float(result.x), destAppUnitsPerDevPixel),
-                  NSFloatPixelsToAppUnits(float(result.y), destAppUnitsPerDevPixel),
-                  NSFloatPixelsToAppUnits(float(result.width), destAppUnitsPerDevPixel),
-                  NSFloatPixelsToAppUnits(float(result.height), destAppUnitsPerDevPixel));
-}
-
 nsRect
 nsLayoutUtils::TransformFrameRectToAncestor(nsIFrame* aFrame,
                                             const nsRect& aRect,
                                             const nsIFrame* aAncestor,
                                             bool* aPreservesAxisAlignedRectangles /* = nullptr */)
 {
   SVGTextFrame* text = GetContainingSVGTextFrame(aFrame);
 
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -649,24 +649,16 @@ public:
    * @param aOutFrames an array to add all the frames found
    * @param aFlags some combination of FrameForPointFlags
    */
   static nsresult GetFramesForArea(nsIFrame* aFrame, const nsRect& aRect,
                                    nsTArray<nsIFrame*> &aOutFrames,
                                    uint32_t aFlags = 0);
 
   /**
-   * Transform aRect relative to aAncestor down to the coordinate system of
-   * aFrame. Computes the bounding-box of the true quadrilateral.
-   */
-  static nsRect TransformAncestorRectToFrame(nsIFrame* aFrame,
-                                             const nsRect& aRect,
-                                             const nsIFrame* aAncestor);
-
-  /**
    * Transform aRect relative to aFrame up to the coordinate system of
    * aAncestor. Computes the bounding-box of the true quadrilateral.
    * Pass non-null aPreservesAxisAlignedRectangles and it will be set to true if
    * we only need to use a 2d transform that PreservesAxisAlignedRectangles().
    */
   static nsRect TransformFrameRectToAncestor(nsIFrame* aFrame,
                                              const nsRect& aRect,
                                              const nsIFrame* aAncestor,
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -1569,16 +1569,23 @@ nsPresContext::GetContainerWeakExternal(
 }
 
 nsIDocShell*
 nsPresContext::GetDocShell() const
 {
   return mContainer;
 }
 
+/* virtual */ void
+nsPresContext::Detach()
+{
+  SetContainer(nullptr);
+  SetLinkHandler(nullptr);
+}
+
 bool
 nsPresContext::ThrottledTransitionStyleIsUpToDate() const
 {
   return
     mLastUpdateThrottledTransitionStyle == mRefreshDriver->MostRecentRefresh();
 }
 
 void
@@ -2350,16 +2357,18 @@ nsPresContext::NotifyInvalidation(const 
               DevPixelsToAppUnits(aRect.width),
               DevPixelsToAppUnits(aRect.height));
   NotifyInvalidation(rect, aFlags);
 }
 
 void
 nsPresContext::NotifyInvalidation(const nsRect& aRect, uint32_t aFlags)
 {
+  MOZ_ASSERT(GetContainerWeak(), "Invalidation in detached pres context");
+
   // If there is no paint event listener, then we don't need to fire
   // the asynchronous event. We don't even need to record invalidation.
   // MayHavePaintEventListener is pretty cheap and we could make it
   // even cheaper by providing a more efficient
   // nsPIDOMWindow::GetListenerManager.
   
   if (mAllInvalidated) {
     return;
@@ -2449,21 +2458,27 @@ NotifyDidPaintSubdocumentCallback(nsIDoc
 }
 
 class DelayedFireDOMPaintEvent : public nsRunnable {
 public:
   DelayedFireDOMPaintEvent(nsPresContext* aPresContext,
                            nsInvalidateRequestList* aList)
     : mPresContext(aPresContext)
   {
+    MOZ_ASSERT(mPresContext->GetContainerWeak(),
+               "DOMPaintEvent requested for a detached pres context");
     mList.TakeFrom(aList);
   }
   NS_IMETHOD Run()
   {
-    mPresContext->FireDOMPaintEvent(&mList);
+    // The pres context might have been detached during the delay -
+    // that's fine, just don't fire the event.
+    if (mPresContext->GetContainerWeak()) {
+      mPresContext->FireDOMPaintEvent(&mList);
+    }
     return NS_OK;
   }
 
   nsRefPtr<nsPresContext> mPresContext;
   nsInvalidateRequestList mList;
 };
 
 void
@@ -2790,16 +2805,24 @@ nsRootPresContext::nsRootPresContext(nsI
 nsRootPresContext::~nsRootPresContext()
 {
   NS_ASSERTION(mRegisteredPlugins.Count() == 0,
                "All plugins should have been unregistered");
   CancelDidPaintTimer();
   CancelApplyPluginGeometryTimer();
 }
 
+/* virtual */ void
+nsRootPresContext::Detach()
+{
+  CancelDidPaintTimer();
+  // XXXmats maybe also CancelApplyPluginGeometryTimer(); ?
+  nsPresContext::Detach();
+}
+
 void
 nsRootPresContext::RegisterPluginForGeometryUpdates(nsIContent* aPlugin)
 {
   mRegisteredPlugins.PutEntry(aPlugin);
 }
 
 void
 nsRootPresContext::UnregisterPluginForGeometryUpdates(nsIContent* aPlugin)
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -429,16 +429,23 @@ public:
 
   nsIDocShell* GetDocShell() const;
 
   // XXX this are going to be replaced with set/get container
   void SetLinkHandler(nsILinkHandler* aHandler) { mLinkHandler = aHandler; }
   nsILinkHandler* GetLinkHandler() { return mLinkHandler; }
 
   /**
+   * Detach this pres context - i.e. cancel relevant timers,
+   * SetLinkHandler(null), SetContainer(null) etc.
+   * Only to be used by the DocumentViewer.
+   */
+  virtual void Detach();
+
+  /**
    * Get the visible area associated with this presentation context.
    * This is the size of the visible area that is used for
    * presenting the document. The returned value is in the standard
    * nscoord units (as scaled by the device context).
    */
   nsRect GetVisibleArea() { return mVisibleArea; }
 
   /**
@@ -1350,20 +1357,21 @@ private:
 public:
   uint32_t LayoutPhaseCount(nsLayoutPhase aPhase) {
     return mLayoutPhaseCount[aPhase];
   }
 #endif
 
 };
 
-class nsRootPresContext : public nsPresContext {
+class nsRootPresContext MOZ_FINAL : public nsPresContext {
 public:
   nsRootPresContext(nsIDocument* aDocument, nsPresContextType aType) NS_HIDDEN;
   virtual ~nsRootPresContext();
+  virtual void Detach() MOZ_OVERRIDE;
 
   /**
    * Ensure that NotifyDidPaintForSubtree is eventually called on this
    * object after a timeout.
    */
   void EnsureEventualDidPaintEvent();
 
   void CancelDidPaintTimer()
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -4891,16 +4891,17 @@ nsIFrame::SchedulePaint(PaintType aType)
   nsPresContext *pres = displayRoot->PresContext()->GetRootPresContext();
 
   // No need to schedule a paint for an external document since they aren't
   // painted directly.
   if (!pres || (pres->Document() && pres->Document()->IsResourceDoc())) {
     return;
   }
   
+  MOZ_ASSERT(pres->GetContainerWeak(), "SchedulePaint in a detached pres context");
   pres->PresShell()->ScheduleViewManagerFlush(aType == PAINT_DELAYED_COMPRESS ?
                                               nsIPresShell::PAINT_DELAYED_COMPRESS :
                                               nsIPresShell::PAINT_DEFAULT);
 
   if (aType == PAINT_DELAYED_COMPRESS) {
     return;
   }
 
--- a/layout/generic/nsPageContentFrame.cpp
+++ b/layout/generic/nsPageContentFrame.cpp
@@ -44,18 +44,16 @@ nsPageContentFrame::Reflow(nsPresContext
   // A PageContentFrame must always have one child: the canvas frame.
   // Resize our frame allowing it only to be as big as we are
   // XXX Pay attention to the page's border and padding...
   if (mFrames.NotEmpty()) {
     nsIFrame* frame = mFrames.FirstChild();
     nsHTMLReflowState kidReflowState(aPresContext, aReflowState, frame, maxSize);
     kidReflowState.SetComputedHeight(maxSize.height);
 
-    mPD->mPageContentSize = maxSize.width;
-
     // Reflow the page content area
     rv = ReflowChild(frame, aPresContext, aDesiredSize, kidReflowState, 0, 0, 0, aStatus);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // The document element's background should cover the entire canvas, so
     // take into account the combined area and any space taken up by
     // absolutely positioned elements
     nsMargin padding(0,0,0,0);
@@ -68,20 +66,21 @@ nsPageContentFrame::Reflow(nsPresContext
     // scrollable overflow, since the purpose of shrink to fit is to
     // make the content that ought to be reachable (represented by the
     // scrollable overflow) fit in the page.
     if (frame->HasOverflowAreas()) {
       // The background covers the content area and padding area, so check
       // for children sticking outside the child frame's padding edge
       nscoord xmost = aDesiredSize.ScrollableOverflow().XMost();
       if (xmost > aDesiredSize.Width()) {
-        mPD->mPageContentXMost =
-          xmost +
-          kidReflowState.mStyleBorder->GetComputedBorderWidth(NS_SIDE_RIGHT) +
-          padding.right;
+        nscoord widthToFit = xmost + padding.right +
+          kidReflowState.mStyleBorder->GetComputedBorderWidth(NS_SIDE_RIGHT);
+        float ratio = float(maxSize.width) / widthToFit;
+        NS_ASSERTION(ratio >= 0.0 && ratio < 1.0, "invalid shrink-to-fit ratio");
+        mPD->mShrinkToFitRatio = std::min(mPD->mShrinkToFitRatio, ratio);
       }
     }
 
     // Place and size the child
     FinishReflowChild(frame, aPresContext, aDesiredSize, &kidReflowState, 0, 0, 0);
 
     NS_ASSERTION(aPresContext->IsDynamic() || !NS_FRAME_IS_FULLY_COMPLETE(aStatus) ||
                   !frame->GetNextInFlow(), "bad child flow list");
--- a/layout/generic/nsSimplePageSequence.cpp
+++ b/layout/generic/nsSimplePageSequence.cpp
@@ -836,14 +836,11 @@ nsSimplePageSequenceFrame::SetDateTimeSt
 // For Shrink To Fit
 //
 // Return the percentage that the page needs to shrink to 
 //
 NS_IMETHODIMP
 nsSimplePageSequenceFrame::GetSTFPercent(float& aSTFPercent)
 {
   NS_ENSURE_TRUE(mPageData, NS_ERROR_UNEXPECTED);
-  aSTFPercent = 1.0f;
-  if (mPageData && (mPageData->mPageContentXMost > mPageData->mPageContentSize)) {
-    aSTFPercent = float(mPageData->mPageContentSize) / float(mPageData->mPageContentXMost);
-  }
+  aSTFPercent = mPageData->mShrinkToFitRatio;
   return NS_OK;
 }
--- a/layout/generic/nsSimplePageSequence.h
+++ b/layout/generic/nsSimplePageSequence.h
@@ -24,21 +24,17 @@ class HTMLCanvasElement;
 //-----------------------------------------------
 // This class maintains all the data that 
 // is used by all the page frame
 // It lives while the nsSimplePageSequenceFrame lives
 class nsSharedPageData {
 public:
   // This object a shared by all the nsPageFrames
   // parented to a SimplePageSequenceFrame
-  nsSharedPageData() :
-    mPageContentXMost(0),
-    mPageContentSize(0)
-  {
-  }
+  nsSharedPageData() : mShrinkToFitRatio(1.0f) {}
 
   nsString    mDateTimeStr;
   nsString    mPageNumFormat;
   nsString    mPageNumAndTotalsFormat;
   nsString    mDocTitle;
   nsString    mDocURL;
   nsFont      mHeadFootFont;
 
@@ -47,18 +43,20 @@ public:
   // Margin for headers and footers; it defaults to 4/100 of an inch on UNIX 
   // and 0 elsewhere; I think it has to do with some inconsistency in page size
   // computations
   nsMargin    mEdgePaperMargin;
 
   nsCOMPtr<nsIPrintSettings> mPrintSettings;
   nsCOMPtr<nsIPrintOptions> mPrintOptions;
 
-  nscoord      mPageContentXMost;      // xmost size from Reflow(width)
-  nscoord      mPageContentSize;       // constrained size (width)
+  // The scaling ratio we need to apply to make all pages fit horizontally.  It's
+  // the minimum "ComputedWidth / OverflowWidth" ratio of all page content frames
+  // that overflowed.  It's 1.0 if none overflowed horizontally.
+  float mShrinkToFitRatio;
 };
 
 // Simple page sequence frame class. Used when we're in paginated mode
 class nsSimplePageSequenceFrame : public nsContainerFrame,
                                   public nsIPageSequenceFrame {
 public:
   friend nsIFrame* NS_NewSimplePageSequenceFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 
--- a/layout/generic/nsSubDocumentFrame.cpp
+++ b/layout/generic/nsSubDocumentFrame.cpp
@@ -922,19 +922,24 @@ DestroyDisplayItemDataForFrames(nsIFrame
 }
 
 static bool
 BeginSwapDocShellsForDocument(nsIDocument* aDocument, void*)
 {
   NS_PRECONDITION(aDocument, "");
 
   nsIPresShell* shell = aDocument->GetShell();
-  nsIFrame* rootFrame = shell ? shell->GetRootFrame() : nullptr;
-  if (rootFrame) {
-    ::DestroyDisplayItemDataForFrames(rootFrame);
+  if (shell) {
+    // Disable painting while the views are detached, see bug 946929.
+    shell->SetNeverPainting(true);
+
+    nsIFrame* rootFrame = shell->GetRootFrame();
+    if (rootFrame) {
+      ::DestroyDisplayItemDataForFrames(rootFrame);
+    }
   }
   aDocument->EnumerateFreezableElements(
     nsObjectFrame::BeginSwapDocShells, nullptr);
   aDocument->EnumerateSubDocuments(BeginSwapDocShellsForDocument, nullptr);
   return true;
 }
 
 static nsView*
@@ -1009,16 +1014,19 @@ EndSwapDocShellsForDocument(nsIDocument*
   // container view in the new hierarchy.
   nsCOMPtr<nsIDocShell> ds = aDocument->GetDocShell();
   if (ds) {
     nsCOMPtr<nsIContentViewer> cv;
     ds->GetContentViewer(getter_AddRefs(cv));
     while (cv) {
       nsCOMPtr<nsPresContext> pc;
       cv->GetPresContext(getter_AddRefs(pc));
+      if (pc && pc->GetPresShell()) {
+        pc->GetPresShell()->SetNeverPainting(ds->IsInvisible());
+      }
       nsDeviceContext* dc = pc ? pc->DeviceContext() : nullptr;
       if (dc) {
         nsView* v = cv->FindContainerView();
         dc->Init(v ? v->GetNearestWidget(nullptr) : nullptr);
       }
       nsCOMPtr<nsIContentViewer> prev;
       cv->GetPreviousViewer(getter_AddRefs(prev));
       cv = prev;
--- a/layout/printing/nsPrintEngine.cpp
+++ b/layout/printing/nsPrintEngine.cpp
@@ -2028,16 +2028,32 @@ nsPrintEngine::UpdateSelectionAndShrinkP
   // we must first locate the "pageContent" frame
   // Then we walk the frame tree and look for the "xmost" frame
   // this is the frame where the right-hand side of the frame extends
   // the furthest
   if (mPrt->mShrinkToFit && aDocumentIsTopLevel) {
     nsIPageSequenceFrame* pageSequence = aPO->mPresShell->GetPageSequenceFrame();
     NS_ENSURE_STATE(pageSequence);
     pageSequence->GetSTFPercent(aPO->mShrinkRatio);
+    // Limit the shrink-to-fit scaling for some text-ish type of documents.
+    nsAutoString contentType;
+    aPO->mPresShell->GetDocument()->GetContentType(contentType);
+    bool applyLimit = contentType.EqualsLiteral("application/xhtml+xml");
+    if (contentType.Length() > 5) {
+      contentType.Truncate(5);
+    }
+    applyLimit = applyLimit || contentType.EqualsLiteral("text/");
+    if (applyLimit) {
+      int32_t limitPercent = 
+        Preferences::GetInt("print.shrink-to-fit.scale-limit-percent", 20);
+      limitPercent = std::max(0, limitPercent);
+      limitPercent = std::min(100, limitPercent);
+      float minShrinkRatio = float(limitPercent) / 100;
+      aPO->mShrinkRatio = std::max(aPO->mShrinkRatio, minShrinkRatio);
+    }
   }
   return NS_OK;
 }
 
 bool
 nsPrintEngine::DoSetPixelScale()
 {
   // This is an Optimization
new file mode 100644
--- /dev/null
+++ b/layout/reftests/printing/960822-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<head><meta charset="utf-8"></head>
+<body>
+<div style="width:100in; border:solid blue;">line</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/printing/960822.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<head><meta charset="utf-8"></head>
+<body>
+<div style="width:30in; border:solid blue;">line</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/printing/966419-1-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<head><meta charset="utf-8"></head>
+<body>
+<div style="width:8in; border:solid blue">line</div>
+<div style="page-break-before:always"></div>
+<div style="width:1in;height:1em"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/printing/966419-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<head><meta charset="utf-8"></head>
+<body>
+<div style="width:8in; border:solid blue">line</div>
+<div style="page-break-before:always"></div>
+<div style="width:6in;height:1em"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/printing/966419-2-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<head><meta charset="utf-8"></head>
+<body>
+<div style="width:1in;height:1em"></div>
+<div style="page-break-before:always"></div>
+<div style="width:8in; border:solid blue">line</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/printing/966419-2.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html class="reftest-print">
+<head><meta charset="utf-8"></head>
+<body>
+<div style="width:6in;height:1em"></div>
+<div style="page-break-before:always"></div>
+<div style="width:8in; border:solid blue">line</div>
+</body>
+</html>
--- a/layout/reftests/printing/reftest.list
+++ b/layout/reftests/printing/reftest.list
@@ -21,8 +21,14 @@
 == 626395-2d.html 626395-2-ref.html
 == 652178-1.html 652178-1-ref.html
 == 115199-1.html 115199-1-ref.html
 == 115199-2a.html 115199-2-ref.html
 == 115199-2b.html 115199-2-ref.html
 == 652178-1.html 652178-1-ref2.html
 fuzzy-if(cocoaWidget,1,5000) fuzzy-if(/^Windows\x20NT\x206\.1/.test(http.oscpu),255,100) == 745025-1.html 745025-1-ref.html
 == 820496-1.html 820496-1-ref.html
+
+# NOTE: These tests don't yet rigorously test what they're
+# trying to test (shrink-to-fit behavior), due to bug 967311.
+== 960822.html 960822-ref.html
+== 966419-1.html 966419-1-ref.html
+== 966419-2.html 966419-2-ref.html
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -166,16 +166,19 @@ pref("browser.chrome.toolbar_tips",     
 // 0 = Pictures Only, 1 = Text Only, 2 = Pictures and Text
 pref("browser.chrome.toolbar_style",        2);
 // max image size for which it is placed in the tab icon for tabbrowser.
 // if 0, no images are used for tab icons for image documents.
 pref("browser.chrome.image_icons.max_size", 1024);
 
 pref("browser.triple_click_selects_paragraph", true);
 
+// Print/Preview Shrink-To-Fit won't shrink below 20% for text-ish documents.
+pref("print.shrink-to-fit.scale-limit-percent", 20);
+
 // Media cache size in kilobytes
 pref("media.cache_size", 512000);
 
 // Master HTML5 media volume scale.
 pref("media.volume_scale", "1.0");
 
 // Timeout for wakelock release
 pref("media.wakelock_timeout", 2000);
--- a/netwerk/base/public/nsNetUtil.h
+++ b/netwerk/base/public/nsNetUtil.h
@@ -1458,16 +1458,36 @@ NS_ShouldCheckAppCache(nsIURI *aURI, boo
 
     bool allowed;
     nsresult rv = offlineService->OfflineAppAllowedForURI(aURI,
                                                           nullptr,
                                                           &allowed);
     return NS_SUCCEEDED(rv) && allowed;
 }
 
+inline bool
+NS_ShouldCheckAppCache(nsIPrincipal * aPrincipal, bool usePrivateBrowsing)
+{
+    if (usePrivateBrowsing) {
+        return false;
+    }
+
+    nsCOMPtr<nsIOfflineCacheUpdateService> offlineService =
+        do_GetService("@mozilla.org/offlinecacheupdate-service;1");
+    if (!offlineService) {
+        return false;
+    }
+
+    bool allowed;
+    nsresult rv = offlineService->OfflineAppAllowed(aPrincipal,
+                                                    nullptr,
+                                                    &allowed);
+    return NS_SUCCEEDED(rv) && allowed;
+}
+
 /**
  * Wraps an nsIAuthPrompt so that it can be used as an nsIAuthPrompt2. This
  * method is provided mainly for use by other methods in this file.
  *
  * *aAuthPrompt2 should be set to null before calling this function.
  */
 inline void
 NS_WrapAuthPrompt(nsIAuthPrompt *aAuthPrompt, nsIAuthPrompt2** aAuthPrompt2)
--- a/netwerk/cache/nsCacheService.cpp
+++ b/netwerk/cache/nsCacheService.cpp
@@ -3145,23 +3145,25 @@ nsCacheService::MoveOrRemoveDiskCache(ns
     nsAutoCString newPath;
     rv = aNewCacheSubdir->GetNativePath(newPath);
     if (NS_FAILED(rv))
         return;
         
     if (NS_SUCCEEDED(aNewCacheSubdir->Exists(&exists)) && !exists) {
         // New cache directory does not exist, try to move the old one here
         // rename needs an empty target directory
-        rv = aNewCacheSubdir->Create(nsIFile::DIRECTORY_TYPE, 0777); 
-        if (NS_SUCCEEDED(rv)) {
+
+        // Make sure the parent of the target sub-dir exists
+        rv = aNewCacheDir->Create(nsIFile::DIRECTORY_TYPE, 0777);
+        if (NS_SUCCEEDED(rv) || NS_ERROR_FILE_ALREADY_EXISTS == rv) {
             nsAutoCString oldPath;
             rv = aOldCacheSubdir->GetNativePath(oldPath);
             if (NS_FAILED(rv))
                 return;
-            if(rename(oldPath.get(), newPath.get()) == 0)
+            if (rename(oldPath.get(), newPath.get()) == 0)
                 return;
         }
     }
     
     // Delay delete by 1 minute to avoid IO thrash on startup.
     nsDeleteDir::DeleteDir(aOldCacheSubdir, false, 60000);
 }
 
--- a/netwerk/cache2/CacheEntry.cpp
+++ b/netwerk/cache2/CacheEntry.cpp
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CacheLog.h"
 #include "CacheEntry.h"
 #include "CacheStorageService.h"
+#include "CacheObserver.h"
 
 #include "nsIInputStream.h"
 #include "nsIOutputStream.h"
 #include "nsISeekableStream.h"
 #include "nsIURI.h"
 #include "nsICacheEntryOpenCallback.h"
 #include "nsICacheStorage.h"
 #include "nsISerializable.h"
@@ -391,34 +392,35 @@ NS_IMETHODIMP CacheEntry::OnFileDoomed(n
     nsRefPtr<DoomCallbackRunnable> event =
       new DoomCallbackRunnable(this, aResult);
     NS_DispatchToMainThread(event);
   }
 
   return NS_OK;
 }
 
-already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(nsICacheEntryOpenCallback* aCallback)
+already_AddRefed<CacheEntryHandle> CacheEntry::ReopenTruncated(bool aMemoryOnly,
+                                                               nsICacheEntryOpenCallback* aCallback)
 {
   LOG(("CacheEntry::ReopenTruncated [this=%p]", this));
 
   mLock.AssertCurrentThreadOwns();
 
   // Hold callbacks invocation, AddStorageEntry would invoke from doom prematurly
   mPreventCallbacks = true;
 
   nsRefPtr<CacheEntryHandle> handle;
   nsRefPtr<CacheEntry> newEntry;
   {
     mozilla::MutexAutoUnlock unlock(mLock);
 
     // The following call dooms this entry (calls DoomAlreadyRemoved on us)
     nsresult rv = CacheStorageService::Self()->AddStorageEntry(
       GetStorageID(), GetURI(), GetEnhanceID(),
-      mUseDisk,
+      mUseDisk && !aMemoryOnly,
       true, // always create
       true, // truncate existing (this one)
       getter_AddRefs(handle));
 
     LOG(("  exchanged entry %p by entry %p, rv=0x%08x", this, newEntry.get(), rv));
 
     if (NS_SUCCEEDED(rv)) {
       newEntry = handle->Entry();
@@ -843,46 +845,24 @@ uint32_t CacheEntry::GetMetadataMemoryCo
   if (NS_FAILED(mFile->ElementsSize(&size)))
     return 0;
 
   return size;
 }
 
 // nsICacheEntry
 
-NS_IMETHODIMP CacheEntry::GetPersistToDisk(bool *aPersistToDisk)
+NS_IMETHODIMP CacheEntry::GetPersistent(bool *aPersistToDisk)
 {
   // No need to sync when only reading.
   // When consumer needs to be consistent with state of the memory storage entries
   // table, then let it use GetUseDisk getter that must be called under the service lock.
   *aPersistToDisk = mUseDisk;
   return NS_OK;
 }
-NS_IMETHODIMP CacheEntry::SetPersistToDisk(bool aPersistToDisk)
-{
-  LOG(("CacheEntry::SetPersistToDisk [this=%p, persist=%d]", this, aPersistToDisk));
-
-  if (mState >= READY) {
-    LOG(("  failed, called after filling the entry"));
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  if (mUseDisk == aPersistToDisk)
-    return NS_OK;
-
-  mozilla::MutexAutoLock lock(CacheStorageService::Self()->Lock());
-
-  mUseDisk = aPersistToDisk;
-  CacheStorageService::Self()->RecordMemoryOnlyEntry(
-    this, !aPersistToDisk, false /* don't overwrite */);
-
-  // File persistence is setup just before we open output stream on it.
-
-  return NS_OK;
-}
 
 NS_IMETHODIMP CacheEntry::GetKey(nsACString & aKey)
 {
   return mURI->GetAsciiSpec(aKey);
 }
 
 NS_IMETHODIMP CacheEntry::GetFetchCount(int32_t *aFetchCount)
 {
@@ -961,17 +941,17 @@ NS_IMETHODIMP CacheEntry::OpenOutputStre
   LOG(("CacheEntry::OpenOutputStream [this=%p]", this));
 
   nsresult rv;
 
   mozilla::MutexAutoLock lock(mLock);
 
   MOZ_ASSERT(mState > EMPTY);
 
-  if (mOutputStream) {
+  if (mOutputStream && !mIsDoomed) {
     LOG(("  giving phantom output stream"));
     mOutputStream.forget(_retval);
   }
   else {
     rv = OpenOutputStreamInternal(offset, _retval);
     if (NS_FAILED(rv)) return rv;
   }
 
@@ -1033,16 +1013,24 @@ nsresult CacheEntry::OpenOutputStreamInt
 NS_IMETHODIMP CacheEntry::GetPredictedDataSize(int64_t *aPredictedDataSize)
 {
   *aPredictedDataSize = mPredictedDataSize;
   return NS_OK;
 }
 NS_IMETHODIMP CacheEntry::SetPredictedDataSize(int64_t aPredictedDataSize)
 {
   mPredictedDataSize = aPredictedDataSize;
+
+  if (CacheObserver::EntryIsTooBig(mPredictedDataSize, mUseDisk)) {
+    LOG(("CacheEntry::SetPredictedDataSize [this=%p] too big, dooming", this));
+    AsyncDoom(nullptr);
+
+    return NS_ERROR_FILE_TOO_BIG;
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP CacheEntry::GetSecurityInfo(nsISupports * *aSecurityInfo)
 {
   {
     mozilla::MutexAutoLock lock(mLock);
     if (mSecurityInfoLoaded) {
@@ -1205,23 +1193,24 @@ NS_IMETHODIMP CacheEntry::SetValid()
   if (outputStream) {
     LOG(("  abandoning phantom output stream"));
     outputStream->Close();
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP CacheEntry::Recreate(nsICacheEntry **_retval)
+NS_IMETHODIMP CacheEntry::Recreate(bool aMemoryOnly,
+                                   nsICacheEntry **_retval)
 {
   LOG(("CacheEntry::Recreate [this=%p, state=%s]", this, StateString(mState)));
 
   mozilla::MutexAutoLock lock(mLock);
 
-  nsRefPtr<CacheEntryHandle> handle = ReopenTruncated(nullptr);
+  nsRefPtr<CacheEntryHandle> handle = ReopenTruncated(aMemoryOnly, nullptr);
   if (handle) {
     handle.forget(_retval);
     return NS_OK;
   }
 
   BackgroundOp(Ops::CALLBACKS, true);
   return NS_OK;
 }
--- a/netwerk/cache2/CacheEntry.h
+++ b/netwerk/cache2/CacheEntry.h
@@ -219,17 +219,18 @@ private:
   // When executed on the management thread directly, the operation(s)
   // is (are) executed immediately.
   void BackgroundOp(uint32_t aOperation, bool aForceAsync = false);
   void StoreFrecency();
 
   // Called only from DoomAlreadyRemoved()
   void DoomFile();
 
-  already_AddRefed<CacheEntryHandle> ReopenTruncated(nsICacheEntryOpenCallback* aCallback);
+  already_AddRefed<CacheEntryHandle> ReopenTruncated(bool aMemoryOnly,
+                                                     nsICacheEntryOpenCallback* aCallback);
   void TransferCallbacks(CacheEntry & aFromEntry);
 
   mozilla::Mutex mLock;
 
   // Reflects the number of existing handles for this entry
   friend class CacheEntryHandle;
   ::mozilla::ThreadSafeAutoRefCnt mHandlersCount;
 
--- a/netwerk/cache2/CacheObserver.cpp
+++ b/netwerk/cache2/CacheObserver.cpp
@@ -27,16 +27,34 @@ static uint32_t const kDefaultUseNewCach
 uint32_t CacheObserver::sUseNewCache = kDefaultUseNewCache;
 
 static int32_t const kDefaultHalfLifeExperiment = -1; // Disabled
 int32_t CacheObserver::sHalfLifeExperiment = kDefaultHalfLifeExperiment;
 
 static uint32_t const kDefaultHalfLifeHours = 6; // 6 hours
 uint32_t CacheObserver::sHalfLifeHours = kDefaultHalfLifeHours;
 
+static bool const kDefaultUseDiskCache = true;
+bool CacheObserver::sUseDiskCache = kDefaultUseDiskCache;
+
+static bool const kDefaultUseMemoryCache = true;
+bool CacheObserver::sUseMemoryCache = kDefaultUseMemoryCache;
+
+static uint32_t const kDefaultDiskCacheCapacity = 250 * 1024; // 250 MB
+uint32_t CacheObserver::sDiskCacheCapacity = kDefaultDiskCacheCapacity;
+
+static uint32_t const kDefaultMaxMemoryEntrySize = 4 * 1024; // 4 MB
+uint32_t CacheObserver::sMaxMemoryEntrySize = kDefaultMaxMemoryEntrySize;
+
+static uint32_t const kDefaultMaxDiskEntrySize = 50 * 1024; // 50 MB
+uint32_t CacheObserver::sMaxDiskEntrySize = kDefaultMaxDiskEntrySize;
+
+static uint32_t const kDefaultCompressionLevel = 1;
+uint32_t CacheObserver::sCompressionLevel = kDefaultCompressionLevel;
+
 NS_IMPL_ISUPPORTS2(CacheObserver,
                    nsIObserver,
                    nsISupportsWeakReference)
 
 nsresult
 CacheObserver::Init()
 {
   if (sSelf) {
@@ -72,19 +90,37 @@ CacheObserver::Shutdown()
   NS_RELEASE(sSelf);
   return NS_OK;
 }
 
 void
 CacheObserver::AttachToPreferences()
 {
   mozilla::Preferences::AddUintVarCache(
+    &sUseNewCache, "browser.cache.use_new_backend", kDefaultUseNewCache);
+
+  mozilla::Preferences::AddBoolVarCache(
+    &sUseDiskCache, "browser.cache.disk.enable", kDefaultUseDiskCache);
+  mozilla::Preferences::AddBoolVarCache(
+    &sUseMemoryCache, "browser.cache.memory.enable", kDefaultUseMemoryCache);
+
+  mozilla::Preferences::AddUintVarCache(
     &sMemoryLimit, "browser.cache.memory_limit", kDefaultMemoryLimit);
+
   mozilla::Preferences::AddUintVarCache(
-    &sUseNewCache, "browser.cache.use_new_backend", kDefaultUseNewCache);
+    &sDiskCacheCapacity, "browser.cache.disk.capacity", kDefaultDiskCacheCapacity);
+
+  mozilla::Preferences::AddUintVarCache(
+    &sMaxMemoryEntrySize, "browser.cache.memory.max_entry_size", kDefaultMaxMemoryEntrySize);
+  mozilla::Preferences::AddUintVarCache(
+    &sMaxDiskEntrySize, "browser.cache.disk.max_entry_size", kDefaultMaxDiskEntrySize);
+
+  // http://mxr.mozilla.org/mozilla-central/source/netwerk/cache/nsCacheEntryDescriptor.cpp#367
+  mozilla::Preferences::AddUintVarCache(
+    &sCompressionLevel, "browser.cache.compression_level", kDefaultCompressionLevel);
 
   sHalfLifeExperiment = mozilla::Preferences::GetInt(
     "browser.cache.frecency_experiment", kDefaultHalfLifeExperiment);
 
   if (sHalfLifeExperiment == 0) {
     // The experiment has not yet been initialized, do it now
     // Store the experiemnt value, since we need it not to change between
     // browser sessions.
@@ -121,22 +157,16 @@ CacheObserver::AttachToPreferences()
 bool const CacheObserver::UseNewCache()
 {
   switch (sUseNewCache) {
     case 0: // use the old cache backend
       return false;
 
     case 1: // use the new cache backend
       return true;
-
-    case 2: // use A/B testing
-    {
-      static bool const sABTest = rand() & 1;
-      return sABTest;
-    }
   }
 
   return true;
 }
 
 namespace { // anon
 
 class CacheStorageEvictHelper
@@ -209,30 +239,53 @@ CacheStorageEvictHelper::ClearStorage(bo
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 } // anon
 
+// static
+bool const CacheObserver::EntryIsTooBig(int64_t aSize, bool aUsingDisk)
+{
+  // If custom limit is set, check it.
+  int64_t preferredLimit = aUsingDisk
+    ? static_cast<int64_t>(sMaxDiskEntrySize) << 10
+    : static_cast<int64_t>(sMaxMemoryEntrySize) << 10;
+
+  if (preferredLimit != -1 && aSize > preferredLimit)
+    return true;
+
+  // Otherwise (or when in the custom limit), check limit
+  // based on the global limit.
+  int64_t derivedLimit = aUsingDisk
+    ? (static_cast<int64_t>(sDiskCacheCapacity) << 7) // << 7 == * 1024 / 8
+    : (static_cast<int64_t>(sMemoryLimit) << 7);
+
+  if (aSize > derivedLimit)
+    return true;
+
+  return false;
+}
+
 NS_IMETHODIMP
 CacheObserver::Observe(nsISupports* aSubject,
                        const char* aTopic,
                        const char16_t* aData)
 {
   if (!strcmp(aTopic, "prefservice:after-app-defaults")) {
     CacheFileIOManager::Init();
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "profile-do-change")) {
+    AttachToPreferences();
     CacheFileIOManager::Init();
     CacheFileIOManager::OnProfile();
-    AttachToPreferences();
     return NS_OK;
   }
 
   if (!strcmp(aTopic, "profile-before-change")) {
     nsRefPtr<CacheStorageService> service = CacheStorageService::Self();
     if (service)
       service->Shutdown();
 
--- a/netwerk/cache2/CacheObserver.h
+++ b/netwerk/cache2/CacheObserver.h
@@ -20,31 +20,51 @@ class CacheObserver : public nsIObserver
 
   virtual ~CacheObserver() {}
 
   static nsresult Init();
   static nsresult Shutdown();
   static CacheObserver* Self() { return sSelf; }
 
   // Access to preferences
+  static bool const UseNewCache();
+  static bool const UseDiskCache()
+    { return sUseDiskCache; }
+  static bool const UseMemoryCache()
+    { return sUseMemoryCache; }
   static uint32_t const MemoryLimit() // <0.5MB,1024MB>, result in bytes.
     { return std::max(512U, std::min(1048576U, sMemoryLimit)) << 10; }
-  static bool const UseNewCache();
+  static uint32_t const DiskCacheCapacity() // result in bytes.
+    { return sDiskCacheCapacity << 10; }
+  static uint32_t const MaxMemoryEntrySize() // result in bytes.
+    { return sMaxMemoryEntrySize << 10; }
+  static uint32_t const MaxDiskEntrySize() // result in bytes.
+    { return sMaxDiskEntrySize << 10; }
+  static uint32_t const CompressionLevel()
+    { return sCompressionLevel; }
   static uint32_t const HalfLifeSeconds()
     { return sHalfLifeHours * 60 * 60; }
   static int32_t const HalfLifeExperiment()
     { return sHalfLifeExperiment; }
 
+  static bool const EntryIsTooBig(int64_t aSize, bool aUsingDisk);
+
 private:
   static CacheObserver* sSelf;
 
   void AttachToPreferences();
 
+  static uint32_t sUseNewCache;
+  static bool sUseDiskCache;
+  static bool sUseMemoryCache;
   static uint32_t sMemoryLimit;
-  static uint32_t sUseNewCache;
+  static uint32_t sDiskCacheCapacity;
+  static uint32_t sMaxMemoryEntrySize;
+  static uint32_t sMaxDiskEntrySize;
+  static uint32_t sCompressionLevel;
   static uint32_t sHalfLifeHours;
   static int32_t sHalfLifeExperiment;
 };
 
 } // net
 } // mozilla
 
 #endif
--- a/netwerk/cache2/CacheStorage.cpp
+++ b/netwerk/cache2/CacheStorage.cpp
@@ -1,16 +1,17 @@
 /* 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 "CacheLog.h"
 #include "CacheStorage.h"
 #include "CacheStorageService.h"
 #include "CacheEntry.h"
+#include "CacheObserver.h"
 
 #include "OldWrappers.h"
 
 #include "nsICacheEntryDoomCallback.h"
 
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheService.h"
 #include "nsIURI.h"
@@ -38,16 +39,26 @@ CacheStorage::~CacheStorage()
 NS_IMETHODIMP CacheStorage::AsyncOpenURI(nsIURI *aURI,
                                          const nsACString & aIdExtension,
                                          uint32_t aFlags,
                                          nsICacheEntryOpenCallback *aCallback)
 {
   if (!CacheStorageService::Self())
     return NS_ERROR_NOT_INITIALIZED;
 
+  if (MOZ_UNLIKELY(!CacheObserver::UseDiskCache()) && mWriteToDisk) {
+    aCallback->OnCacheEntryAvailable(nullptr, false, nullptr, NS_ERROR_NOT_AVAILABLE);
+    return NS_OK;
+  }
+
+  if (MOZ_UNLIKELY(!CacheObserver::UseMemoryCache()) && !mWriteToDisk) {
+    aCallback->OnCacheEntryAvailable(nullptr, false, nullptr, NS_ERROR_NOT_AVAILABLE);
+    return NS_OK;
+  }
+
   NS_ENSURE_ARG(aURI);
   NS_ENSURE_ARG(aCallback);
 
   nsresult rv;
 
   bool truncate = aFlags & nsICacheStorage::OPEN_TRUNCATE;
 
   nsCOMPtr<nsIURI> noRefURI;
--- a/netwerk/cache2/CacheStorageService.cpp
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -440,22 +440,26 @@ NS_IMETHODIMP CacheStorageService::DiskC
                                                     bool aLookupAppCache,
                                                     nsICacheStorage * *_retval)
 {
   NS_ENSURE_ARG(aLoadContextInfo);
   NS_ENSURE_ARG(_retval);
 
   // TODO save some heap granularity - cache commonly used storages.
 
+  // When disk cache is disabled, still provide a storage, but just keep stuff
+  // in memory.
+  bool useDisk = CacheObserver::UseDiskCache();
+
   nsCOMPtr<nsICacheStorage> storage;
   if (CacheObserver::UseNewCache()) {
-    storage = new CacheStorage(aLoadContextInfo, true, aLookupAppCache);
+    storage = new CacheStorage(aLoadContextInfo, useDisk, aLookupAppCache);
   }
   else {
-    storage = new _OldStorage(aLoadContextInfo, true, aLookupAppCache, false, nullptr);
+    storage = new _OldStorage(aLoadContextInfo, useDisk, aLookupAppCache, false, nullptr);
   }
 
   storage.forget(_retval);
   return NS_OK;
 }
 
 NS_IMETHODIMP CacheStorageService::AppCacheStorage(nsILoadContextInfo *aLoadContextInfo,
                                                    nsIApplicationCache *aApplicationCache,
--- a/netwerk/cache2/OldWrappers.cpp
+++ b/netwerk/cache2/OldWrappers.cpp
@@ -241,73 +241,50 @@ NS_IMETHODIMP _OldCacheEntryWrapper::Get
   nsresult rv = GetDataSize(&size);
   if (NS_FAILED(rv))
     return rv;
 
   *aSize = size;
   return NS_OK;
 }
 
-NS_IMETHODIMP _OldCacheEntryWrapper::GetPersistToDisk(bool *aPersistToDisk)
+NS_IMETHODIMP _OldCacheEntryWrapper::GetPersistent(bool *aPersistToDisk)
 {
   if (!mOldDesc) {
     return NS_ERROR_NULL_POINTER;
   }
 
   nsresult rv;
 
   nsCacheStoragePolicy policy;
   rv = mOldDesc->GetStoragePolicy(&policy);
   NS_ENSURE_SUCCESS(rv, rv);
 
   *aPersistToDisk = policy != nsICache::STORE_IN_MEMORY;
 
   return NS_OK;
 }
 
-NS_IMETHODIMP _OldCacheEntryWrapper::SetPersistToDisk(bool aPersistToDisk)
-{
-  if (!mOldDesc) {
-    return NS_ERROR_NULL_POINTER;
-  }
-
-  nsresult rv;
-
-  nsCacheStoragePolicy policy;
-  rv = mOldDesc->GetStoragePolicy(&policy);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (policy == nsICache::STORE_OFFLINE) {
-    return aPersistToDisk
-      ? NS_OK
-      : NS_ERROR_NOT_AVAILABLE;
-  }
-
-  policy = aPersistToDisk
-    ? nsICache::STORE_ON_DISK
-    : nsICache::STORE_IN_MEMORY;
-  rv = mOldDesc->SetStoragePolicy(policy);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP _OldCacheEntryWrapper::Recreate(nsICacheEntry** aResult)
+NS_IMETHODIMP _OldCacheEntryWrapper::Recreate(bool aMemoryOnly,
+                                              nsICacheEntry** aResult)
 {
   NS_ENSURE_TRUE(mOldDesc, NS_ERROR_NOT_AVAILABLE);
 
   nsCacheAccessMode mode;
   nsresult rv = mOldDesc->GetAccessGranted(&mode);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!(mode & nsICache::ACCESS_WRITE))
     return NS_ERROR_NOT_AVAILABLE;
 
   LOG(("_OldCacheEntryWrapper::Recreate [this=%p]", this));
 
+  if (aMemoryOnly)
+    mOldDesc->SetStoragePolicy(nsICache::STORE_IN_MEMORY);
+
   nsCOMPtr<nsICacheEntry> self(this);
   self.forget(aResult);
   return NS_OK;
 }
 
 NS_IMETHODIMP _OldCacheEntryWrapper::OpenInputStream(int64_t offset,
                                                      nsIInputStream * *_retval)
 {
--- a/netwerk/cache2/OldWrappers.h
+++ b/netwerk/cache2/OldWrappers.h
@@ -25,21 +25,20 @@ class CacheStorage;
 class _OldCacheEntryWrapper : public nsICacheEntry
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_FORWARD_SAFE_NSICACHEENTRYDESCRIPTOR(mOldDesc)
   NS_FORWARD_NSICACHEENTRYINFO(mOldInfo->)
 
   NS_IMETHOD AsyncDoom(nsICacheEntryDoomCallback* listener);
-  NS_IMETHOD GetPersistToDisk(bool *aPersistToDisk);
-  NS_IMETHOD SetPersistToDisk(bool aPersistToDisk);
+  NS_IMETHOD GetPersistent(bool *aPersistToDisk);
   NS_IMETHOD SetValid() { return NS_OK; }
   NS_IMETHOD MetaDataReady() { return NS_OK; }
-  NS_IMETHOD Recreate(nsICacheEntry**);
+  NS_IMETHOD Recreate(bool, nsICacheEntry**);
   NS_IMETHOD GetDataSize(int64_t *size);
   NS_IMETHOD OpenInputStream(int64_t offset, nsIInputStream * *_retval);
   NS_IMETHOD OpenOutputStream(int64_t offset, nsIOutputStream * *_retval);
   NS_IMETHOD MaybeMarkValid();
   NS_IMETHOD HasWriteAccess(bool aWriteOnly, bool *aWriteAccess);
 
   _OldCacheEntryWrapper(nsICacheEntryDescriptor* desc);
   _OldCacheEntryWrapper(nsICacheEntryInfo* info);
--- a/netwerk/cache2/nsICacheEntry.idl
+++ b/netwerk/cache2/nsICacheEntry.idl
@@ -11,32 +11,30 @@ interface nsICacheEntryDoomCallback;
 // ************************ REMOVE **********************
 typedef long nsCacheAccessMode;
 typedef long nsCacheStoragePolicy;
 
 interface nsICacheListener;
 interface nsIFile;
 interface nsICacheMetaDataVisitor;
 
-[scriptable, uuid(1785f6f1-18b3-4cb4-ae99-6c5545c1de19)]
+[scriptable, uuid(d3fbd879-6d3a-4bd0-b12e-7d86ab27ea90)]
 interface nsICacheEntry : nsISupports
 {
   /**
    * Get the key identifying the cache entry.
    */
   readonly attribute ACString key;
 
   /**
-   * Whether the data can be persist to disk.
-   * NOTE: This attribute must be set BEFORE opening the output stream.
-   * Switching this flag does not immediately affect creation of the disk
-   * file from memory-only data or eviction of the disk file and loading it
-   * to memory-only.
+   * Whether the entry is memory/only or persisted to disk.
+   * Note: private browsing entries are reported as persistent for consistency
+   * while are not actually persisted to disk.
    */
-  attribute boolean persistToDisk;
+  readonly attribute boolean persistent;
 
   /**
    * Get the number of times the cache entry has been opened.
    */
   readonly attribute long  fetchCount;
 
   /**
    * Get the last time the cache entry was opened (in seconds since the Epoch).
@@ -137,23 +135,26 @@ interface nsICacheEntry : nsISupports
    */
   void setValid();
 
   /**
    * Doom this entry and open a new, empty, entry for write.  Consumer has
    * to exchange this entry for the newly created.
    * Used on 200 responses to conditional requests.
    *
+   * @param aMemoryOnly
+   *    - whether the entry is to be created as memory/only regardless how
+   *      the entry being recreated persistence is set
    * @returns
    *    - an entry that can be used to write to
    * @throws
    *    - NS_ERROR_NOT_AVAILABLE when the entry cannot be from some reason
    *      recreated for write
    */
-  nsICacheEntry recreate();
+  nsICacheEntry recreate([optional] in boolean aMemoryOnly);
 
   /**
    * Returns the length of data this entry holds.
    * @throws
    *    NS_ERROR_IN_PROGRESS when the write is still in progress.
    */
   readonly attribute long long dataSize;
 
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -797,16 +797,18 @@ CallTypeSniffers(void *aClosure, const u
   if (!newType.IsEmpty()) {
     chan->SetContentType(newType);
   }
 }
 
 nsresult
 nsHttpChannel::CallOnStartRequest()
 {
+    nsresult rv;
+
     mTracingEnabled = false;
 
     // Allow consumers to override our content type
     if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
         // NOTE: We can have both a txn pump and a cache pump when the cache
         // content is partial. In that case, we need to read from the cache,
         // because that's the one that has the initial contents. If that fails
         // then give the transaction pump a shot.
@@ -837,17 +839,17 @@ nsHttpChannel::CallOnStartRequest()
             mResponseHead->SetContentType(NS_LITERAL_CSTRING(TEXT_PLAIN));
         else {
             // Uh-oh.  We had better find out what type we are!
 
             // XXX This does not work with content-encodings...  but
             // neither does applying the conversion from the URILoader
 
             nsCOMPtr<nsIStreamConverterService> serv;
-            nsresult rv = gHttpHandler->
+            rv = gHttpHandler->
                 GetStreamConverterService(getter_AddRefs(serv));
             // If we failed, we just fall through to the "normal" case
             if (NS_SUCCEEDED(rv)) {
                 nsCOMPtr<nsIStreamListener> converter;
                 rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE,
                                             "*/*",
                                             mListener,
                                             mListenerContext,
@@ -860,25 +862,29 @@ nsHttpChannel::CallOnStartRequest()
     }
 
     if (mResponseHead && mResponseHead->ContentCharset().IsEmpty())
         mResponseHead->SetContentCharset(mContentCharsetHint);
 
     if (mResponseHead && mCacheEntry) {
         // If we have a cache entry, set its predicted size to ContentLength to
         // avoid caching an entry that will exceed the max size limit.
-        nsresult rv = mCacheEntry->SetPredictedDataSize(
+        rv = mCacheEntry->SetPredictedDataSize(
             mResponseHead->ContentLength());
-        NS_ENSURE_SUCCESS(rv, rv);
+        if (NS_ERROR_FILE_TOO_BIG == rv) {
+          mCacheEntry = nullptr;
+          LOG(("  entry too big, throwing away"));
+        } else {
+          NS_ENSURE_SUCCESS(rv, rv);
+        }
     }
 
     LOG(("  calling mListener->OnStartRequest\n"));
-    nsresult rv;
     if (mListener) {
-        nsresult rv = mListener->OnStartRequest(this, mListenerContext);
+        rv = mListener->OnStartRequest(this, mListenerContext);
         if (NS_FAILED(rv))
             return rv;
     } else {
         NS_WARNING("OnStartRequest skipped because of null listener");
     }
 
     // install stream converter if required
     rv = ApplyContentConversions();
@@ -3635,34 +3641,40 @@ nsHttpChannel::InitCacheEntry()
 
     // Don't cache the response again if already cached...
     if (mCachedContentIsValid)
         return NS_OK;
 
     LOG(("nsHttpChannel::InitCacheEntry [this=%p entry=%p]\n",
         this, mCacheEntry.get()));
 
-    if (!mCacheEntryIsWriteOnly) {
+    bool recreate = !mCacheEntryIsWriteOnly;
+    bool dontPersist = mLoadFlags & INHIBIT_PERSISTENT_CACHING;
+
+    if (!recreate && dontPersist) {
+        // If the current entry is persistent but we inhibit peristence
+        // then force recreation of the entry as memory/only.
+        rv = mCacheEntry->GetPersistent(&recreate);
+        if (NS_FAILED(rv))
+            return rv;
+    }
+
+    if (recreate) {
         LOG(("  we have a ready entry, but reading it again from the server -> recreating cache entry\n"));
         nsCOMPtr<nsICacheEntry> currentEntry;
         currentEntry.swap(mCacheEntry);
-        rv = currentEntry->Recreate(getter_AddRefs(mCacheEntry));
+        rv = currentEntry->Recreate(dontPersist, getter_AddRefs(mCacheEntry));
         if (NS_FAILED(rv)) {
           LOG(("  recreation failed, the response will not be cached"));
           return NS_OK;
         }
 
         mCacheEntryIsWriteOnly = true;
     }
 
-    if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
-        rv = mCacheEntry->SetPersistToDisk(false);
-        if (NS_FAILED(rv)) return rv;
-    }
-
     // Set the expiration time for this cache entry
     rv = UpdateExpirationTime();
     if (NS_FAILED(rv)) return rv;
 
     rv = AddCacheEntryHeaders(mCacheEntry);
     if (NS_FAILED(rv)) return rv;
 
     mInitedCacheEntry = true;
--- a/netwerk/test/unit/test_bug650995.js
+++ b/netwerk/test/unit/test_bug650995.js
@@ -134,22 +134,16 @@ function TestCacheEntrySize(setSizeFunc,
     this.testAndTriggerNext = function(request, data, ctx) {
         do_check_eq(secondExpectedReply, data);
         do_execute_soon(nextTest);
     }
 }
 
 function run_test()
 {
-    if (newCacheBackEndUsed()) {
-        // Test that "max_entry_size" prefs for disk- and memory-cache prevents caching resources with size out of bounds
-        do_check_true(true, "This test doesn't run with the new cache backend, the test or the cache needs to be fixed");
-        return;
-    }
-
     httpserver.registerPathHandler("/bug650995", handler);
     httpserver.start(-1);
 
     prefService.setBoolPref("browser.cache.offline.enable", false);
 
     nextTest();
     do_test_pending();
 }
--- a/netwerk/test/unit/test_bug812167.js
+++ b/netwerk/test/unit/test_bug812167.js
@@ -62,32 +62,32 @@ function check_response(path, request, b
   do_check_eq(buffer, responseBody);
 
   // Entry is always there, old cache wrapping code does session->SetDoomEntriesIfExpired(false),
   // just check it's not persisted or is expired (dep on the test).
   asyncOpenCacheEntry(path, "disk", Ci.nsICacheStorage.OPEN_READONLY, null, function(status, entry) {
     do_check_eq(status, 0);
 
     // Expired entry is on disk, no-store entry is in memory
-    do_check_eq(entry.persistToDisk, expectedExpiration);
+    do_check_eq(entry.persistent, expectedExpiration);
 
     // Do the request again and check the server handler is called appropriately
     var chan = make_channel(path);
     chan.asyncOpen(new ChannelListener(function(request, buffer) {
       do_check_eq(buffer, responseBody);
 
       if (expectedExpiration) {
         // Handler had to be called second time
         do_check_eq(redirectHandler_ExpiresInPast_calls, 2);
       }
       else {
         // Handler had to be called second time (no-store forces validate),
         // and we are just in memory
         do_check_eq(redirectHandler_NoStore_calls, 2);
-        do_check_true(!entry.persistToDisk);
+        do_check_true(!entry.persistent);
       }
 
       continuation();
     }, null), null);
   });
 }
 
 function run_test_no_store()
--- a/netwerk/test/unit/test_offlinecache_custom-directory.js
+++ b/netwerk/test/unit/test_offlinecache_custom-directory.js
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
- * This test executes nsIOfflineCacheUpdateService.scheduleCust