Merge m-c to fx-team.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 26 Jul 2013 10:49:26 -0400
changeset 140104 8da2f00eb92ea5dc7f8b0e2f312153f6c03fd607
parent 140007 8c3ee4235ec1e5cbb311fb52feb23c95b7003a7e (current diff)
parent 140103 52f9e8ffe111884e934c7efeae4ffea67a44b128 (diff)
child 140105 6f8cc9245141fb1cdf7131981d87ab8426ec973c
child 140130 343f7c10ed89efdaafca4367e595f3b083088ae3
child 140177 8b32dad46ea1c5434719f3c31e7cdd115002aebf
child 145690 0608a3b4c9d92738b3bca824af1f4a3ff7672bee
push id1940
push userryanvm@gmail.com
push dateFri, 26 Jul 2013 14:49:30 +0000
treeherderfx-team@8da2f00eb92e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone25.0a1
Merge m-c to fx-team.
browser/base/content/browser.js
dom/plugins/test/mochitest/test_instance_re-parent-windowed.html
testing/mozbase/manifestdestiny/README.md
testing/mozbase/mozfile/tests/test.py
testing/mozbase/mozhttpd/README.md
testing/mozbase/mozhttpd/mozhttpd/iface.py
testing/mozbase/mozlog/README.md
testing/mozbase/moztest/README.md
--- a/accessible/src/base/nsAccessiblePivot.cpp
+++ b/accessible/src/base/nsAccessiblePivot.cpp
@@ -293,36 +293,119 @@ nsAccessiblePivot::MoveLast(nsIAccessibl
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (accessible)
     *aResult = MovePivotInternal(accessible, nsAccessiblePivot::REASON_LAST);
 
   return NS_OK;
 }
 
-// TODO: Implement
 NS_IMETHODIMP
 nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary, bool* aResult)
 {
   NS_ENSURE_ARG(aResult);
 
   *aResult = false;
 
-  return NS_ERROR_NOT_IMPLEMENTED;
+  int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+  HyperTextAccessible* text = mPosition->AsHyperText();
+  Accessible* oldPosition = mPosition;
+  while (!text) {
+    oldPosition = mPosition;
+    mPosition = mPosition->Parent();
+    text = mPosition->AsHyperText();
+  }
+
+  if (mEndOffset == -1)
+    mEndOffset = text != oldPosition ? text->GetChildOffset(oldPosition) : 0;
+
+  if (mEndOffset == text->CharacterCount())
+    return NS_OK;
+
+  AccessibleTextBoundary startBoundary, endBoundary;
+  switch (aBoundary) {
+    case CHAR_BOUNDARY:
+      startBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+      endBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+      break;
+    case WORD_BOUNDARY:
+      startBoundary = nsIAccessibleText::BOUNDARY_WORD_START;
+      endBoundary = nsIAccessibleText::BOUNDARY_WORD_END;
+      break;
+    default:
+      return NS_ERROR_INVALID_ARG;
+  }
+
+  nsAutoString unusedText;
+  int32_t newStart = 0, newEnd = 0;
+  text->GetTextAtOffset(mEndOffset, endBoundary, &newStart, &mEndOffset, unusedText);
+  text->GetTextBeforeOffset(mEndOffset, startBoundary, &newStart, &newEnd,
+                            unusedText);
+  mStartOffset = newEnd == mEndOffset ? newStart : newEnd;
+
+  *aResult = true;
+
+  NotifyOfPivotChange(mPosition, oldStart, oldEnd,
+                      nsIAccessiblePivot::REASON_TEXT);
+  return NS_OK;
 }
 
-// TODO: Implement
 NS_IMETHODIMP
 nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary, bool* aResult)
 {
   NS_ENSURE_ARG(aResult);
 
   *aResult = false;
 
-  return NS_ERROR_NOT_IMPLEMENTED;
+  int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
+  HyperTextAccessible* text = mPosition->AsHyperText();
+  Accessible* oldPosition = mPosition;
+  while (!text) {
+    oldPosition = mPosition;
+    mPosition = mPosition->Parent();
+    text = mPosition->AsHyperText();
+  }
+
+  if (mStartOffset == -1)
+    mStartOffset = text != oldPosition ? text->GetChildOffset(oldPosition) : 0;
+
+  if (mStartOffset == 0)
+    return NS_OK;
+
+  AccessibleTextBoundary startBoundary, endBoundary;
+  switch (aBoundary) {
+    case CHAR_BOUNDARY:
+      startBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+      endBoundary = nsIAccessibleText::BOUNDARY_CHAR;
+      break;
+    case WORD_BOUNDARY:
+      startBoundary = nsIAccessibleText::BOUNDARY_WORD_START;
+      endBoundary = nsIAccessibleText::BOUNDARY_WORD_END;
+      break;
+    default:
+      return NS_ERROR_INVALID_ARG;
+  }
+
+  nsAutoString unusedText;
+  int32_t newStart = 0, newEnd = 0;
+  text->GetTextBeforeOffset(mStartOffset, startBoundary, &newStart, &newEnd,
+                            unusedText);
+  if (newStart < mStartOffset)
+    mStartOffset = newEnd == mStartOffset ? newStart : newEnd;
+  else // XXX: In certain odd cases newStart is equal to mStartOffset
+    text->GetTextBeforeOffset(mStartOffset - 1, startBoundary, &newStart,
+                              &mStartOffset, unusedText);
+  text->GetTextAtOffset(mStartOffset, endBoundary, &newStart, &mEndOffset,
+                        unusedText);
+
+  *aResult = true;
+
+  NotifyOfPivotChange(mPosition, oldStart, oldEnd,
+                      nsIAccessiblePivot::REASON_TEXT);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAccessiblePivot::MoveToPoint(nsIAccessibleTraversalRule* aRule,
                                int32_t aX, int32_t aY, bool aIgnoreNoMatch,
                                bool* aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
--- a/accessible/src/base/nsCoreUtils.cpp
+++ b/accessible/src/base/nsCoreUtils.cpp
@@ -27,16 +27,17 @@
 #include "nsEventStateManager.h"
 #include "nsISelectionPrivate.h"
 #include "nsISelectionController.h"
 #include "nsPIDOMWindow.h"
 #include "nsGUIEvent.h"
 #include "nsView.h"
 #include "nsLayoutUtils.h"
 #include "nsGkAtoms.h"
+#include "nsDOMTouchEvent.h"
 
 #include "nsComponentManagerUtils.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "mozilla/dom/Element.h"
 
 #include "nsITreeBoxObject.h"
 #include "nsITreeColumns.h"
 
@@ -108,50 +109,24 @@ nsCoreUtils::DispatchClickEvent(nsITreeB
 
   nsPresContext* presContext = presShell->GetPresContext();
 
   int32_t cnvdX = presContext->CSSPixelsToDevPixels(tcX + x + 1) +
     presContext->AppUnitsToDevPixels(offset.x);
   int32_t cnvdY = presContext->CSSPixelsToDevPixels(tcY + y + 1) +
     presContext->AppUnitsToDevPixels(offset.y);
 
+  // XUL is just desktop, so there is no real reason for senfing touch events.
   DispatchMouseEvent(NS_MOUSE_BUTTON_DOWN, cnvdX, cnvdY,
                      tcContent, tcFrame, presShell, rootWidget);
 
   DispatchMouseEvent(NS_MOUSE_BUTTON_UP, cnvdX, cnvdY,
                      tcContent, tcFrame, presShell, rootWidget);
 }
 
-bool
-nsCoreUtils::DispatchMouseEvent(uint32_t aEventType,
-                                nsIPresShell *aPresShell,
-                                nsIContent *aContent)
-{
-  nsIFrame *frame = aContent->GetPrimaryFrame();
-  if (!frame)
-    return false;
-
-  // Compute x and y coordinates.
-  nsPoint point;
-  nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(point);
-  if (!widget)
-    return false;
-
-  nsSize size = frame->GetSize();
-
-  nsPresContext* presContext = aPresShell->GetPresContext();
-
-  int32_t x = presContext->AppUnitsToDevPixels(point.x + size.width / 2);
-  int32_t y = presContext->AppUnitsToDevPixels(point.y + size.height / 2);
-
-  // Fire mouse event.
-  DispatchMouseEvent(aEventType, x, y, aContent, frame, aPresShell, widget);
-  return true;
-}
-
 void
 nsCoreUtils::DispatchMouseEvent(uint32_t aEventType, int32_t aX, int32_t aY,
                                 nsIContent *aContent, nsIFrame *aFrame,
                                 nsIPresShell *aPresShell, nsIWidget *aRootWidget)
 {
   nsMouseEvent event(true, aEventType, aRootWidget,
                      nsMouseEvent::eReal, nsMouseEvent::eNormal);
 
@@ -161,16 +136,38 @@ nsCoreUtils::DispatchMouseEvent(uint32_t
   event.button = nsMouseEvent::eLeftButton;
   event.time = PR_IntervalNow();
   event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
 
   nsEventStatus status = nsEventStatus_eIgnore;
   aPresShell->HandleEventWithTarget(&event, aFrame, aContent, &status);
 }
 
+void
+nsCoreUtils::DispatchTouchEvent(uint32_t aEventType, int32_t aX, int32_t aY,
+                                nsIContent* aContent, nsIFrame* aFrame,
+                                nsIPresShell* aPresShell, nsIWidget* aRootWidget)
+{
+  if (!nsDOMTouchEvent::PrefEnabled())
+    return;
+
+  nsTouchEvent event(true, aEventType, aRootWidget);
+
+  event.time = PR_IntervalNow();
+
+  // XXX: Touch has an identifier of -1 to hint that it is synthesized.
+  nsRefPtr<mozilla::dom::Touch> t =
+    new mozilla::dom::Touch(-1, nsIntPoint(aX, aY),
+                            nsIntPoint(1, 1), 0.0f, 1.0f);
+  t->SetTarget(aContent);
+  event.touches.AppendElement(t);
+  nsEventStatus status = nsEventStatus_eIgnore;
+  aPresShell->HandleEventWithTarget(&event, aFrame, aContent, &status);
+}
+
 uint32_t
 nsCoreUtils::GetAccessKeyFor(nsIContent* aContent)
 {
   // Accesskeys are registered by @accesskey attribute only. At first check
   // whether it is presented on the given element to avoid the slow
   // nsEventStateManager::GetRegisteredAccessKey() method.
   if (!aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::accesskey))
     return 0;
--- a/accessible/src/base/nsCoreUtils.h
+++ b/accessible/src/base/nsCoreUtils.h
@@ -44,39 +44,42 @@ public:
    */
   static void DispatchClickEvent(nsITreeBoxObject *aTreeBoxObj,
                                  int32_t aRowIndex, nsITreeColumn *aColumn,
                                  const nsCString& aPseudoElt = EmptyCString());
 
   /**
    * Send mouse event to the given element.
    *
-   * @param  aEventType  [in] an event type (see nsGUIEvent.h for constants)
-   * @param  aPresShell  [in] the presshell for the given element
-   * @param  aContent    [in] the element
-   */
-  static bool DispatchMouseEvent(uint32_t aEventType,
-                                   nsIPresShell *aPresShell,
-                                   nsIContent *aContent);
-
-  /**
-   * Send mouse event to the given element.
-   *
    * @param aEventType   [in] an event type (see nsGUIEvent.h for constants)
    * @param aX           [in] x coordinate in dev pixels
    * @param aY           [in] y coordinate in dev pixels
    * @param aContent     [in] the element
    * @param aFrame       [in] frame of the element
    * @param aPresShell   [in] the presshell for the element
    * @param aRootWidget  [in] the root widget of the element
    */
   static void DispatchMouseEvent(uint32_t aEventType, int32_t aX, int32_t aY,
                                  nsIContent *aContent, nsIFrame *aFrame,
-                                 nsIPresShell *aPresShell,
-                                 nsIWidget *aRootWidget);
+                                 nsIPresShell *aPresShell, nsIWidget *aRootWidget);
+
+  /**
+   * Send a touch event with a single touch point to the given element.
+   *
+   * @param aEventType   [in] an event type (see nsGUIEvent.h for constants)
+   * @param aX           [in] x coordinate in dev pixels
+   * @param aY           [in] y coordinate in dev pixels
+   * @param aContent     [in] the element
+   * @param aFrame       [in] frame of the element
+   * @param aPresShell   [in] the presshell for the element
+   * @param aRootWidget  [in] the root widget of the element
+   */
+  static void DispatchTouchEvent(uint32_t aEventType, int32_t aX, int32_t aY,
+                                 nsIContent* aContent, nsIFrame* aFrame,
+                                 nsIPresShell* aPresShell, nsIWidget* aRootWidget);
 
   /**
    * Return an accesskey registered on the given element by
    * nsEventStateManager or 0 if there is no registered accesskey.
    *
    * @param aContent - the given element.
    */
   static uint32_t GetAccessKeyFor(nsIContent *aContent);
--- a/accessible/src/generic/Accessible.cpp
+++ b/accessible/src/generic/Accessible.cpp
@@ -2288,23 +2288,38 @@ Accessible::DispatchClickEvent(nsIConten
   nsIPresShell* presShell = mDoc->PresShell();
 
   // Scroll into view.
   presShell->ScrollContentIntoView(aContent,
                                    nsIPresShell::ScrollAxis(),
                                    nsIPresShell::ScrollAxis(),
                                    nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
 
-  // Fire mouse down and mouse up events.
-  bool res = nsCoreUtils::DispatchMouseEvent(NS_MOUSE_BUTTON_DOWN, presShell,
-                                               aContent);
-  if (!res)
+  nsIFrame* frame = aContent->GetPrimaryFrame();
+  if (!frame)
+    return;
+
+  // Compute x and y coordinates.
+  nsPoint point;
+  nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(point);
+  if (!widget)
     return;
 
-  nsCoreUtils::DispatchMouseEvent(NS_MOUSE_BUTTON_UP, presShell, aContent);
+  nsSize size = frame->GetSize();
+
+  nsPresContext* presContext = presShell->GetPresContext();
+
+  int32_t x = presContext->AppUnitsToDevPixels(point.x + size.width / 2);
+  int32_t y = presContext->AppUnitsToDevPixels(point.y + size.height / 2);
+
+  // Simulate a touch interaction by dispatching touch events with mouse events.
+  nsCoreUtils::DispatchTouchEvent(NS_TOUCH_START, x, y, aContent, frame, presShell, widget);
+  nsCoreUtils::DispatchMouseEvent(NS_MOUSE_BUTTON_DOWN, x, y, aContent, frame, presShell, widget);
+  nsCoreUtils::DispatchTouchEvent(NS_TOUCH_END, x, y, aContent, frame, presShell, widget);
+  nsCoreUtils::DispatchMouseEvent(NS_MOUSE_BUTTON_UP, x, y, aContent, frame, presShell, widget);
 }
 
 NS_IMETHODIMP
 Accessible::ScrollTo(uint32_t aHow)
 {
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
--- a/accessible/src/jsat/AccessFu.jsm
+++ b/accessible/src/jsat/AccessFu.jsm
@@ -111,17 +111,17 @@ this.AccessFu = {
     TouchAdapter.start();
 
     Services.obs.addObserver(this, 'remote-browser-frame-shown', false);
     Services.obs.addObserver(this, 'in-process-browser-or-app-frame-shown', false);
     Services.obs.addObserver(this, 'Accessibility:NextObject', false);
     Services.obs.addObserver(this, 'Accessibility:PreviousObject', false);
     Services.obs.addObserver(this, 'Accessibility:Focus', false);
     Services.obs.addObserver(this, 'Accessibility:ActivateObject', false);
-    Services.obs.addObserver(this, 'Accessibility:MoveCaret', false);
+    Services.obs.addObserver(this, 'Accessibility:MoveByGranularity', false);
     Utils.win.addEventListener('TabOpen', this);
     Utils.win.addEventListener('TabClose', this);
     Utils.win.addEventListener('TabSelect', this);
 
     if (this.readyCallback) {
       this.readyCallback();
       delete this.readyCallback;
     }
@@ -154,17 +154,17 @@ this.AccessFu = {
     Utils.win.removeEventListener('TabSelect', this);
 
     Services.obs.removeObserver(this, 'remote-browser-frame-shown');
     Services.obs.removeObserver(this, 'in-process-browser-or-app-frame-shown');
     Services.obs.removeObserver(this, 'Accessibility:NextObject');
     Services.obs.removeObserver(this, 'Accessibility:PreviousObject');
     Services.obs.removeObserver(this, 'Accessibility:Focus');
     Services.obs.removeObserver(this, 'Accessibility:ActivateObject');
-    Services.obs.removeObserver(this, 'Accessibility:MoveCaret');
+    Services.obs.removeObserver(this, 'Accessibility:MoveByGranularity');
 
     if (this.doneCallback) {
       this.doneCallback();
       delete this.doneCallback;
     }
   },
 
   _enableOrDisable: function _enableOrDisable() {
@@ -272,18 +272,18 @@ this.AccessFu = {
         this.Input.activateCurrent(JSON.parse(aData));
         break;
       case 'Accessibility:Focus':
         this._focused = JSON.parse(aData);
         if (this._focused) {
           this.showCurrent(true);
         }
         break;
-      case 'Accessibility:MoveCaret':
-        this.Input.moveCaret(JSON.parse(aData));
+      case 'Accessibility:MoveByGranularity':
+        this.Input.moveByGranularity(JSON.parse(aData));
         break;
       case 'remote-browser-frame-shown':
       case 'in-process-browser-or-app-frame-shown':
       {
         let mm = aSubject.QueryInterface(Ci.nsIFrameLoader).messageManager;
         this._handleMessageManager(mm);
         break;
       }
@@ -783,26 +783,33 @@ var Input = {
 
   moveCursor: function moveCursor(aAction, aRule, aInputType) {
     let mm = Utils.getMessageManager(Utils.CurrentBrowser);
     mm.sendAsyncMessage('AccessFu:MoveCursor',
                         {action: aAction, rule: aRule,
                          origin: 'top', inputType: aInputType});
   },
 
-  moveCaret: function moveCaret(aDetails) {
+  moveByGranularity: function moveByGranularity(aDetails) {
+    const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
+
     if (!this.editState.editing) {
-      return;
+      if (aDetails.granularity === MOVEMENT_GRANULARITY_PARAGRAPH) {
+        this.moveCursor('move' + aDetails.direction, 'Paragraph', 'gesture');
+        return;
+      }
+    } else {
+      aDetails.atStart = this.editState.atStart;
+      aDetails.atEnd = this.editState.atEnd;
     }
 
-    aDetails.atStart = this.editState.atStart;
-    aDetails.atEnd = this.editState.atEnd;
-
     let mm = Utils.getMessageManager(Utils.CurrentBrowser);
-    mm.sendAsyncMessage('AccessFu:MoveCaret', aDetails);
+    let type = this.editState.editing ? 'AccessFu:MoveCaret' :
+                                        'AccessFu:MoveByGranularity';
+    mm.sendAsyncMessage(type, aDetails);
   },
 
   activateCurrent: function activateCurrent(aData) {
     let mm = Utils.getMessageManager(Utils.CurrentBrowser);
     let offset = aData && typeof aData.keyIndex === 'number' ?
                  aData.keyIndex - Output.brailleState.startOffset : -1;
 
     mm.sendAsyncMessage('AccessFu:Activate', {offset: offset});
--- a/accessible/src/jsat/EventManager.jsm
+++ b/accessible/src/jsat/EventManager.jsm
@@ -151,21 +151,22 @@ this.EventManager.prototype = {
           QueryInterface(Ci.nsIAccessibleDocument).virtualCursor;
         let position = pivot.position;
         if (position && position.role == ROLE_INTERNAL_FRAME)
           break;
         let event = aEvent.
           QueryInterface(Ci.nsIAccessibleVirtualCursorChangeEvent);
         let reason = event.reason;
 
-        if (this.editState.editing)
+        if (this.editState.editing) {
           aEvent.accessibleDocument.takeFocus();
-
+        }
         this.present(
-          Presentation.pivotChanged(position, event.oldAccessible, reason));
+          Presentation.pivotChanged(position, event.oldAccessible, reason,
+                                    pivot.startOffset, pivot.endOffset));
 
         break;
       }
       case EVENT_STATE_CHANGE:
       {
         let event = aEvent.QueryInterface(Ci.nsIAccessibleStateChangeEvent);
         if (event.state == Ci.nsIAccessibleStates.STATE_CHECKED &&
             !(event.isExtraState)) {
--- a/accessible/src/jsat/Presentation.jsm
+++ b/accessible/src/jsat/Presentation.jsm
@@ -119,47 +119,63 @@ VisualPresenter.prototype = {
   type: 'Visual',
 
   /**
    * The padding in pixels between the object and the highlight border.
    */
   BORDER_PADDING: 2,
 
   viewportChanged: function VisualPresenter_viewportChanged(aWindow) {
-    let currentAcc = this._displayedAccessibles.get(aWindow);
+    let currentDisplay = this._displayedAccessibles.get(aWindow);
+    if (!currentDisplay) {
+      return null;
+    }
+
+    let currentAcc = currentDisplay.accessible;
+    let start = currentDisplay.startOffset;
+    let end = currentDisplay.endOffset;
     if (Utils.isAliveAndVisible(currentAcc)) {
-      let bounds = Utils.getBounds(currentAcc);
+      let bounds = (start === -1 && end === -1) ? Utils.getBounds(currentAcc) :
+                   Utils.getTextBounds(currentAcc, start, end);
+
       return {
         type: this.type,
         details: {
           method: 'showBounds',
           bounds: bounds,
           padding: this.BORDER_PADDING
         }
       };
     }
 
     return null;
   },
 
   pivotChanged: function VisualPresenter_pivotChanged(aContext, aReason) {
     this._displayedAccessibles.set(aContext.accessible.document.window,
-                                   aContext.accessible);
+                                   { accessible: aContext.accessible,
+                                     startOffset: aContext.startOffset,
+                                     endOffset: aContext.endOffset });
 
     if (!aContext.accessible)
       return {type: this.type, details: {method: 'hideBounds'}};
 
     try {
       aContext.accessible.scrollTo(
         Ci.nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE);
+
+      let bounds = (aContext.startOffset === -1 && aContext.endOffset === -1) ?
+                   aContext.bounds : Utils.getTextBounds(aContext.accessible,
+                                     aContext.startOffset, aContext.endOffset);
+
       return {
         type: this.type,
         details: {
           method: 'showBounds',
-          bounds: aContext.bounds,
+          bounds: bounds,
           padding: this.BORDER_PADDING
         }
       };
     } catch (e) {
       Logger.logException(e, 'Failed to get bounds');
       return null;
     }
   },
@@ -227,37 +243,49 @@ AndroidPresenter.prototype = {
       this.ANDROID_VIEW_FOCUSED;
 
     if (isExploreByTouch) {
       // This isn't really used by TalkBack so this is a half-hearted attempt
       // for now.
       androidEvents.push({eventType: this.ANDROID_VIEW_HOVER_EXIT, text: []});
     }
 
-    let state = Utils.getStates(aContext.accessible)[0];
-
     let brailleOutput = {};
     if (Utils.AndroidSdkVersion >= 16) {
       if (!this._braillePresenter) {
         this._braillePresenter = new BraillePresenter();
       }
       brailleOutput = this._braillePresenter.pivotChanged(aContext, aReason).
                          details;
     }
 
-    androidEvents.push({eventType: (isExploreByTouch) ?
-                          this.ANDROID_VIEW_HOVER_ENTER : focusEventType,
-                        text: UtteranceGenerator.genForContext(aContext).output,
-                        bounds: aContext.bounds,
-                        clickable: aContext.accessible.actionCount > 0,
-                        checkable: !!(state &
-                                      Ci.nsIAccessibleStates.STATE_CHECKABLE),
-                        checked: !!(state &
-                                    Ci.nsIAccessibleStates.STATE_CHECKED),
-                        brailleOutput: brailleOutput});
+    if (aReason === Ci.nsIAccessiblePivot.REASON_TEXT) {
+      if (Utils.AndroidSdkVersion >= 16) {
+        let adjustedText = aContext.textAndAdjustedOffsets;
+
+        androidEvents.push({
+          eventType: this.ANDROID_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+          text: [adjustedText.text],
+          fromIndex: adjustedText.startOffset,
+          toIndex: adjustedText.endOffset
+        });
+      }
+    } else {
+      let state = Utils.getStates(aContext.accessible)[0];
+      androidEvents.push({eventType: (isExploreByTouch) ?
+                           this.ANDROID_VIEW_HOVER_ENTER : focusEventType,
+                         text: UtteranceGenerator.genForContext(aContext).output,
+                         bounds: aContext.bounds,
+                         clickable: aContext.accessible.actionCount > 0,
+                         checkable: !!(state &
+                                       Ci.nsIAccessibleStates.STATE_CHECKABLE),
+                         checked: !!(state &
+                                     Ci.nsIAccessibleStates.STATE_CHECKED),
+                         brailleOutput: brailleOutput});
+    }
 
 
     return {
       type: this.type,
       details: androidEvents
     };
   },
 
@@ -489,20 +517,19 @@ this.Presentation = {
     } else {
       this.presenters.push(new SpeechPresenter());
       this.presenters.push(new HapticPresenter());
     }
 
     return this.presenters;
   },
 
-  pivotChanged: function Presentation_pivotChanged(aPosition,
-                                                   aOldPosition,
-                                                   aReason) {
-    let context = new PivotContext(aPosition, aOldPosition);
+  pivotChanged: function Presentation_pivotChanged(aPosition, aOldPosition, aReason,
+                                                   aStartOffset, aEndOffset) {
+    let context = new PivotContext(aPosition, aOldPosition, aStartOffset, aEndOffset);
     return [p.pivotChanged(context, aReason)
               for each (p in this.presenters)];
   },
 
   actionInvoked: function Presentation_actionInvoked(aObject, aActionName) {
     return [p.actionInvoked(aObject, aActionName)
               for each (p in this.presenters)];
   },
--- a/accessible/src/jsat/TouchAdapter.jsm
+++ b/accessible/src/jsat/TouchAdapter.jsm
@@ -37,16 +37,19 @@ this.TouchAdapter = {
   DWELL_REPEAT_DELAY: 300,
 
   // maximum distance the mouse could move during a tap in inches
   TAP_MAX_RADIUS: 0.2,
 
   // The virtual touch ID generated by a mouse event.
   MOUSE_ID: 'mouse',
 
+  // Synthesized touch ID.
+  SYNTH_ID: -1,
+
   start: function TouchAdapter_start() {
     Logger.info('TouchAdapter.start');
 
     this._touchPoints = {};
     this._dwellTimeout = 0;
     this._prevGestures = {};
     this._lastExploreTime = 0;
     this._dpi = Utils.win.QueryInterface(Ci.nsIInterfaceRequestor).
@@ -126,29 +129,34 @@ this.TouchAdapter = {
         aEvent.view.top instanceof Ci.nsIDOMChromeWindow) {
       return;
     }
 
     if (aEvent.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN) {
       return;
     }
 
+    let changedTouches = aEvent.changedTouches || [aEvent];
+
+    if (changedTouches.length == 1 &&
+        changedTouches[0].identifier == this.SYNTH_ID) {
+      return;
+    }
+
     if (!this.eventsOfInterest[aEvent.type]) {
       aEvent.preventDefault();
       aEvent.stopImmediatePropagation();
       return;
     }
 
     if (this._delayedEvent) {
       Utils.win.clearTimeout(this._delayedEvent);
       delete this._delayedEvent;
     }
 
-    let changedTouches = aEvent.changedTouches || [aEvent];
-
     // XXX: Until bug 77992 is resolved, on desktop we get microseconds
     // instead of milliseconds.
     let timeStamp = (Utils.OS == 'Android') ? aEvent.timeStamp : Date.now();
     switch (aEvent.type) {
       case 'mousedown':
       case 'mouseenter':
       case 'touchstart':
         for (var i = 0; i < changedTouches.length; i++) {
--- a/accessible/src/jsat/TraversalRules.jsm
+++ b/accessible/src/jsat/TraversalRules.jsm
@@ -38,16 +38,18 @@ const ROLE_LISTITEM = Ci.nsIAccessibleRo
 const ROLE_BUTTONDROPDOWNGRID = Ci.nsIAccessibleRole.ROLE_BUTTONDROPDOWNGRID;
 const ROLE_LISTBOX = Ci.nsIAccessibleRole.ROLE_LISTBOX;
 const ROLE_SLIDER = Ci.nsIAccessibleRole.ROLE_SLIDER;
 const ROLE_HEADING = Ci.nsIAccessibleRole.ROLE_HEADING;
 const ROLE_TERM = Ci.nsIAccessibleRole.ROLE_TERM;
 const ROLE_SEPARATOR = Ci.nsIAccessibleRole.ROLE_SEPARATOR;
 const ROLE_TABLE = Ci.nsIAccessibleRole.ROLE_TABLE;
 const ROLE_INTERNAL_FRAME = Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME;
+const ROLE_PARAGRAPH = Ci.nsIAccessibleRole.ROLE_PARAGRAPH;
+const ROLE_SECTION = Ci.nsIAccessibleRole.ROLE_SECTION;
 
 this.EXPORTED_SYMBOLS = ['TraversalRules'];
 
 Cu.import('resource://gre/modules/accessibility/Utils.jsm');
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 
 let gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images');
 
@@ -243,16 +245,29 @@ this.TraversalRules = {
 
   List: new BaseTraversalRule(
     [ROLE_LIST,
      ROLE_DEFINITION_LIST]),
 
   PageTab: new BaseTraversalRule(
     [ROLE_PAGETAB]),
 
+  Paragraph: new BaseTraversalRule(
+    [ROLE_PARAGRAPH,
+     ROLE_SECTION],
+    function Paragraph_match(aAccessible) {
+      for (let child = aAccessible.firstChild; child; child = child.nextSibling) {
+        if (child.role === ROLE_TEXT_LEAF) {
+          return FILTER_MATCH | FILTER_IGNORE_SUBTREE;
+        }
+      }
+
+      return FILTER_IGNORE;
+    }),
+
   RadioButton: new BaseTraversalRule(
     [ROLE_RADIOBUTTON,
      ROLE_RADIO_MENU_ITEM]),
 
   Separator: new BaseTraversalRule(
     [ROLE_SEPARATOR]),
 
   Table: new BaseTraversalRule(
--- a/accessible/src/jsat/Utils.jsm
+++ b/accessible/src/jsat/Utils.jsm
@@ -222,16 +222,24 @@ this.Utils = {
   },
 
   getBounds: function getBounds(aAccessible) {
       let objX = {}, objY = {}, objW = {}, objH = {};
       aAccessible.getBounds(objX, objY, objW, objH);
       return new Rect(objX.value, objY.value, objW.value, objH.value);
   },
 
+  getTextBounds: function getTextBounds(aAccessible, aStart, aEnd) {
+      let accText = aAccessible.QueryInterface(Ci.nsIAccessibleText);
+      let objX = {}, objY = {}, objW = {}, objH = {};
+      accText.getRangeExtents(aStart, aEnd, objX, objY, objW, objH,
+                              Ci.nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE);
+      return new Rect(objX.value, objY.value, objW.value, objH.value);
+  },
+
   inHiddenSubtree: function inHiddenSubtree(aAccessible) {
     for (let acc=aAccessible; acc; acc=acc.parent) {
       let hidden = Utils.getAttributes(acc).hidden;
       if (hidden && JSON.parse(hidden)) {
         return true;
       }
     }
     return false;
@@ -408,31 +416,73 @@ this.Logger = {
       this._dumpTreeInternal(aLogLevel, aAccessible.getChildAt(i), aIndent + 1);
     }
 };
 
 /**
  * PivotContext: An object that generates and caches context information
  * for a given accessible and its relationship with another accessible.
  */
-this.PivotContext = function PivotContext(aAccessible, aOldAccessible) {
+this.PivotContext = function PivotContext(aAccessible, aOldAccessible,
+                                          aStartOffset, aEndOffset) {
   this._accessible = aAccessible;
   this._oldAccessible =
     this._isDefunct(aOldAccessible) ? null : aOldAccessible;
+  this.startOffset = aStartOffset;
+  this.endOffset = aEndOffset;
 }
 
 PivotContext.prototype = {
   get accessible() {
     return this._accessible;
   },
 
   get oldAccessible() {
     return this._oldAccessible;
   },
 
+  get textAndAdjustedOffsets() {
+    if (this.startOffset === -1 && this.endOffset === -1) {
+      return null;
+    }
+
+    if (!this._textAndAdjustedOffsets) {
+      let result = {startOffset: this.startOffset,
+                    endOffset: this.endOffset,
+                    text: this._accessible.QueryInterface(Ci.nsIAccessibleText).
+                          getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT)};
+      let hypertextAcc = this._accessible.QueryInterface(Ci.nsIAccessibleHyperText);
+
+      // Iterate through the links in backwards order so text replacements don't
+      // affect the offsets of links yet to be processed.
+      for (let i = hypertextAcc.linkCount - 1; i >= 0; i--) {
+        let link = hypertextAcc.getLinkAt(i);
+        let linkText = '';
+        if (link instanceof Ci.nsIAccessibleText) {
+          linkText = link.QueryInterface(Ci.nsIAccessibleText).
+                          getText(0, Ci.nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT);
+        }
+
+        let start = link.startIndex;
+        let end = link.endIndex;
+        for (let offset of ['startOffset', 'endOffset']) {
+          if (this[offset] >= end) {
+            result[offset] += linkText.length - (end - start);
+          }
+        }
+        result.text = result.text.substring(0, start) + linkText +
+                      result.text.substring(end);
+      }
+
+      this._textAndAdjustedOffsets = result;
+    }
+
+    return this._textAndAdjustedOffsets;
+  },
+
   /**
    * Get a list of |aAccessible|'s ancestry up to the root.
    * @param  {nsIAccessible} aAccessible.
    * @return {Array} Ancestry list.
    */
   _getAncestry: function _getAncestry(aAccessible) {
     let ancestry = [];
     let parent = aAccessible;
--- a/accessible/src/jsat/content-script.js
+++ b/accessible/src/jsat/content-script.js
@@ -3,16 +3,20 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 const ROLE_ENTRY = Ci.nsIAccessibleRole.ROLE_ENTRY;
 const ROLE_INTERNAL_FRAME = Ci.nsIAccessibleRole.ROLE_INTERNAL_FRAME;
 
+const MOVEMENT_GRANULARITY_CHARACTER = 1;
+const MOVEMENT_GRANULARITY_WORD = 2;
+const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
+
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
   'resource://gre/modules/accessibility/Utils.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
   'resource://gre/modules/accessibility/Presentation.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules',
   'resource://gre/modules/accessibility/TraversalRules.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
@@ -112,17 +116,18 @@ function showCurrent(aMessage) {
 
   let vc = Utils.getVirtualCursor(content.document);
 
   if (!forwardToChild(vc, showCurrent, aMessage)) {
     if (!vc.position && aMessage.json.move) {
       vc.moveFirst(TraversalRules.Simple);
     } else {
       sendAsyncMessage('AccessFu:Present', Presentation.pivotChanged(
-                         vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE));
+                         vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE,
+                         vc.startOffset, vc.endOffset));
     }
   }
 }
 
 function forwardToParent(aMessage) {
   // XXX: This is a silly way to make a deep copy
   let newJSON = JSON.parse(JSON.stringify(aMessage.json));
   newJSON.origin = 'child';
@@ -221,21 +226,40 @@ function activateContextMenu(aMessage) {
   }
 
   let position = Utils.getVirtualCursor(content.document).position;
   if (!forwardToChild(aMessage, activateContextMenu, position)) {
     sendContextMenuCoordinates(position);
   }
 }
 
+function moveByGranularity(aMessage) {
+  let direction = aMessage.json.direction;
+  let vc = Utils.getVirtualCursor(content.document);
+  let granularity;
+
+  switch(aMessage.json.granularity) {
+    case MOVEMENT_GRANULARITY_CHARACTER:
+      granularity = Ci.nsIAccessiblePivot.CHAR_BOUNDARY;
+      break;
+    case MOVEMENT_GRANULARITY_WORD:
+      granularity = Ci.nsIAccessiblePivot.WORD_BOUNDARY;
+      break;
+    default:
+      return;
+  }
+
+  if (direction === 'Previous') {
+    vc.movePreviousByText(granularity);
+  } else if (direction === 'Next') {
+    vc.moveNextByText(granularity);
+  }
+}
+
 function moveCaret(aMessage) {
-  const MOVEMENT_GRANULARITY_CHARACTER = 1;
-  const MOVEMENT_GRANULARITY_WORD = 2;
-  const MOVEMENT_GRANULARITY_PARAGRAPH = 8;
-
   let direction = aMessage.json.direction;
   let granularity = aMessage.json.granularity;
   let accessible = Utils.getVirtualCursor(content.document).position;
   let accText = accessible.QueryInterface(Ci.nsIAccessibleText);
   let oldOffset = accText.caretOffset;
   let text = accText.getText(0, accText.characterCount);
 
   let start = {}, end = {};
@@ -368,16 +392,17 @@ addMessageListener(
 
     addMessageListener('AccessFu:MoveToPoint', moveToPoint);
     addMessageListener('AccessFu:MoveCursor', moveCursor);
     addMessageListener('AccessFu:ShowCurrent', showCurrent);
     addMessageListener('AccessFu:Activate', activateCurrent);
     addMessageListener('AccessFu:ContextMenu', activateContextMenu);
     addMessageListener('AccessFu:Scroll', scroll);
     addMessageListener('AccessFu:MoveCaret', moveCaret);
+    addMessageListener('AccessFu:MoveByGranularity', moveByGranularity);
 
     if (!eventManager) {
       eventManager = new EventManager(this);
     }
     eventManager.start();
   });
 
 addMessageListener(
@@ -387,13 +412,14 @@ addMessageListener(
 
     removeMessageListener('AccessFu:MoveToPoint', moveToPoint);
     removeMessageListener('AccessFu:MoveCursor', moveCursor);
     removeMessageListener('AccessFu:ShowCurrent', showCurrent);
     removeMessageListener('AccessFu:Activate', activateCurrent);
     removeMessageListener('AccessFu:ContextMenu', activateContextMenu);
     removeMessageListener('AccessFu:Scroll', scroll);
     removeMessageListener('AccessFu:MoveCaret', moveCaret);
+    removeMessageListener('AccessFu:MoveByGranularity', moveByGranularity);
 
     eventManager.stop();
   });
 
 sendAsyncMessage('AccessFu:Ready');
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -1433,16 +1433,39 @@ function moveToLineEnd(aID, aCaretOffset
 
   this.getID = function moveToLineEnd_getID()
   {
     return "move to line end in " + prettyName(aID);
   }
 }
 
 /**
+ * Move the caret to the end of previous line if any.
+ */
+function moveToPrevLineEnd(aID, aCaretOffset)
+{
+  this.__proto__ = new synthAction(aID, new caretMoveChecker(aCaretOffset, aID));
+
+  this.invoke = function moveToPrevLineEnd_invoke()
+  {
+    synthesizeKey("VK_UP", { });
+
+    if (MAC)
+      synthesizeKey("VK_RIGHT", { metaKey: true });
+    else
+      synthesizeKey("VK_END", { });
+  }
+
+  this.getID = function moveToPrevLineEnd_getID()
+  {
+    return "move to previous line end in " + prettyName(aID);
+  }
+}
+
+/**
  * Move the caret to begining of the line.
  */
 function moveToLineStart(aID, aCaretOffset)
 {
   if (MAC) {
     this.__proto__ = new synthKey(aID, "VK_LEFT", { metaKey: true },
                                   new caretMoveChecker(aCaretOffset, aID));
   } else {
--- a/accessible/tests/mochitest/pivot.js
+++ b/accessible/tests/mochitest/pivot.js
@@ -3,16 +3,18 @@ Components.utils.import("resource://gre/
 ////////////////////////////////////////////////////////////////////////////////
 // Constants
 
 const PREFILTER_INVISIBLE = nsIAccessibleTraversalRule.PREFILTER_INVISIBLE;
 const PREFILTER_ARIA_HIDDEN = nsIAccessibleTraversalRule.PREFILTER_ARIA_HIDDEN;
 const FILTER_MATCH = nsIAccessibleTraversalRule.FILTER_MATCH;
 const FILTER_IGNORE = nsIAccessibleTraversalRule.FILTER_IGNORE;
 const FILTER_IGNORE_SUBTREE = nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE;
+const CHAR_BOUNDARY = nsIAccessiblePivot.CHAR_BOUNDARY;
+const WORD_BOUNDARY = nsIAccessiblePivot.WORD_BOUNDARY;
 
 const NS_ERROR_NOT_IN_TREE = 0x80780026;
 const NS_ERROR_INVALID_ARG = 0x80070057;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Traversal rules
 
 /**
@@ -150,16 +152,18 @@ VCChangedChecker.getPreviousPosAndOffset
 };
 
 VCChangedChecker.methodReasonMap = {
   'moveNext': nsIAccessiblePivot.REASON_NEXT,
   'movePrevious': nsIAccessiblePivot.REASON_PREV,
   'moveFirst': nsIAccessiblePivot.REASON_FIRST,
   'moveLast': nsIAccessiblePivot.REASON_LAST,
   'setTextRange': nsIAccessiblePivot.REASON_TEXT,
+  'moveNextByText': nsIAccessiblePivot.REASON_TEXT,
+  'movePreviousByText': nsIAccessiblePivot.REASON_TEXT,
   'moveToPoint': nsIAccessiblePivot.REASON_POINT
 };
 
 /**
  * Set a text range in the pivot and wait for virtual cursor change event.
  *
  * @param aDocAcc         [in] document that manages the virtual cursor
  * @param aTextAccessible [in] accessible to set to virtual cursor's position
@@ -229,16 +233,59 @@ function setVCPosInvoker(aDocAcc, aPivot
     this.eventSeq = [];
     this.unexpectedEventSeq = [
       new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
     ];
   }
 }
 
 /**
+ * Move the pivot by text and wait for virtual cursor change event.
+ *
+ * @param aDocAcc          [in] document that manages the virtual cursor
+ * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.)
+ * @param aBoundary        [in] boundary constant
+ * @param aTextOffsets     [in] start and end offsets of text range to set in
+ *                         virtual cursor.
+ * @param aIdOrNameOrAcc   [in] id, accessible or accessible name to expect
+ *                         virtual cursor to land on after performing move method.
+ *                         false if no move is expected.
+ */
+function setVCTextInvoker(aDocAcc, aPivotMoveMethod, aBoundary, aTextOffsets, aIdOrNameOrAcc)
+{
+  var expectMove = (aIdOrNameOrAcc != false);
+  this.invoke = function virtualCursorChangedInvoker_invoke()
+  {
+    VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor);
+    SimpleTest.info(aDocAcc.virtualCursor.position);
+    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;
+  };
+
+  if (expectMove) {
+    this.eventSeq = [
+      new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod)
+    ];
+  } else {
+    this.eventSeq = [];
+    this.unexpectedEventSeq = [
+      new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc)
+    ];
+  }
+}
+
+
+/**
  * Move the pivot to the position under the point.
  *
  * @param aDocAcc        [in] document that manages the virtual cursor
  * @param aX             [in] screen x coordinate
  * @param aY             [in] screen y coordinate
  * @param aIgnoreNoMatch [in] don't unset position if no object was found at
  *                       point.
  * @param aRule          [in] traversal rule object
--- a/accessible/tests/mochitest/pivot/Makefile.in
+++ b/accessible/tests/mochitest/pivot/Makefile.in
@@ -8,12 +8,14 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir	= @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_A11Y_FILES = \
 		doc_virtualcursor.html \
+		doc_virtualcursor_text.html \
 		test_virtualcursor.html \
+		test_virtualcursor_text.html \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Pivot test document</title>
+  <meta charset="utf-8" />
+</head>
+<body>
+  <p id="paragraph-1">
+  This <b>is</b> <a href="#">the</a> test of text.
+  </p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Tests pivot functionality in virtual cursors</title>
+  <meta charset="utf-8" />
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <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="../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">
+    var gBrowserWnd = null;
+    var gQueue = null;
+
+    function doTest()
+    {
+      var doc = currentTabDocument();
+      var docAcc = getAccessible(doc, [nsIAccessibleDocument]);
+
+      gQueue = new eventQueue();
+
+      gQueue.onFinish = function onFinish()
+      {
+        closeBrowserWindow();
+      }
+
+      gQueue.push(new setVCPosInvoker(docAcc, null, null,
+                                      getAccessible(doc.getElementById('paragraph-1'))));
+
+      gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [0,4],
+                  getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+      gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', CHAR_BOUNDARY, [4,5],
+                  getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+      gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', CHAR_BOUNDARY, [3,4],
+                  getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+      gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [5,7],
+                  getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+      gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [8,9],
+                  getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+      gQueue.push(new setVCTextInvoker(docAcc, 'moveNextByText', WORD_BOUNDARY, [10,14],
+                  getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+      gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [8,9],
+                  getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+      gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, [5,7],
+                  getAccessible(doc.getElementById('paragraph-1'), nsIAccessibleText)));
+
+      gQueue.invoke();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addLoadEvent(function () {
+      /* We open a new browser because we need to test with a top-level content
+         document. */
+      openBrowserWindow(
+        doTest,
+        getRootDirectory(window.location.href) + "doc_virtualcursor_text.html");
+    });
+  </script>
+</head>
+<body id="body">
+
+  <a target="_blank"
+     title="Support Movement By Granularity"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=886076">Mozilla Bug 886076</a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+</body>
+</html>
--- a/accessible/tests/mochitest/text/test_atcaretoffset.html
+++ b/accessible/tests/mochitest/text/test_atcaretoffset.html
@@ -22,220 +22,118 @@
   <script type="application/javascript"
           src="../events.js"></script>
   <script type="application/javascript"
           src="../text.js"></script>
 
   <script type="application/javascript">
     //gA11yEventDumpToConsole = true; // debugging
 
-    // __a__w__o__r__d__\n
-    //  0  1  2  3  4  5
-    // __t__w__o__ (soft line break)
-    //  6  7  8  9
-    // __w__o__r__d__s
-    // 10 11 12 13 14 15
+    function traverseTextByLines(aQueue, aID, aLines)
+    {
+      var baseInvoker = new synthFocus(aID);
+      var baseInvokerID = "move to last line end";
 
-    function moveToLastLineEnd()
-    {
-      this.__proto__ = new synthFocus("textarea");
-
-      this.finalCheck = function moveToLastLineEnd_finalCheck()
-      {
-        testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "", 15, 15,
-                            ["textarea"]);
+      for (var i = aLines.length - 1; i >= 0 ; i--) {
+        var [ ppLineText, ppLineEndChar, ppLineStart, ppLineEnd ] =
+          (i - 2 >= 0) ? aLines[i - 2] : [ "", "", 0, 0 ];
+        var [ pLineText, pLineEndChar, pLineStart, pLineEnd ] =
+          (i - 1 >= 0) ? aLines[i - 1] : [ "", "", 0, 0 ];
+        var [ lineText, lineEndChar, lineStart, lineEnd ] = aLines[i];
 
-        testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "", 15, 15,
-                            [ "textarea" ]);
-
-        testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "words", 10, 15,
-                         [ "textarea" ]);
+        var [ nLineText, nLineEndChar, nLineStart, nLineEnd ] =
+          (i + 1 < aLines.length) ?
+            aLines[i + 1] :
+            [ "", "", lineEnd  + lineEndChar.length, lineEnd + lineEndChar.length ];
 
-        testTextAtOffset(kCaretOffset, BOUNDARY_LINE_END, "words", 10, 15,
-                         [ "textarea" ]);
+        var [ nnLineText, nnLineEndChar, nnLineStart, nnLineEnd ] =
+          (i + 2 < aLines.length) ?
+            aLines[i + 2] :
+            [ "", "", nLineEnd  + nLineEndChar.length, nLineEnd + nLineEndChar.length ];
 
-        testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
-                             [ "textarea" ]);
-
-        testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_END, "\ntwo ", 5, 10,
-                             [ "textarea" ]);
-      }
+        var tests = [
+          [ testTextBeforeOffset, BOUNDARY_LINE_START,
+            pLineText + pLineEndChar, pLineStart, lineStart],
 
-      this.getID = function moveToLastLineEnd_getID()
-      {
-        return "move to last line end";
-      }
-    }
+          [ testTextBeforeOffset, BOUNDARY_LINE_END,
+            ppLineEndChar + pLineText, ppLineEnd, pLineEnd],
+
+          [ testTextAtOffset, BOUNDARY_LINE_START,
+            lineText + lineEndChar, lineStart, nLineStart],
 
-    function moveToLastLineStart()
-    {
-      this.__proto__ = new moveToLineStart("textarea", 10);
+          [ testTextAtOffset, BOUNDARY_LINE_END,
+            pLineEndChar + lineText, pLineEnd, lineEnd],
 
-      this.finalCheck = function moveToLastLineStart_finalCheck()
-      {
-        testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "", 15, 15,
-                            [ "textarea" ]);
+          [ testTextAfterOffset, BOUNDARY_LINE_START,
+            nLineText + nnLineEndChar, nLineStart, nnLineStart],
 
-        testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "", 15, 15,
-                            [ "textarea" ]);
+          [ testTextAfterOffset, BOUNDARY_LINE_END,
+            lineEndChar + nLineText, lineEnd, nLineEnd],
+        ];
 
-        testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "words", 10, 15,
-                         [ "textarea" ]);
-
-        testTextAtOffset(kCaretOffset, BOUNDARY_LINE_END, "words", 10, 15,
-                         [ "textarea" ]);
+        aQueue.push(new tmpl_moveTo(aID, baseInvoker, baseInvokerID, tests));
 
-        testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
-                             [ "textarea" ]);
+        baseInvoker = new moveToLineStart(aID, lineStart);
+        baseInvokerID = "move to " + i + "th line start";
 
-        testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_END, "\ntwo ", 5, 10,
-                             [ "textarea" ]);
-      }
+        aQueue.push(new tmpl_moveTo(aID, baseInvoker, baseInvokerID, tests));
 
-      this.getID = function moveToLastLineStart_getID()
-      {
-        return "move to last line start";
+        baseInvoker = new moveToPrevLineEnd(aID, pLineEnd);
+        baseInvokerID = "move to " + (i - 1) + "th line end";
       }
     }
 
-    function moveToMiddleLineStart()
+    /**
+     * A template invoker to move through the text.
+     */
+    function tmpl_moveTo(aID, aInvoker, aInvokerID, aTests)
     {
-      this.__proto__ = new synthUpKey("textarea",
-                                      new caretMoveChecker(6, "textarea"));
-
-      this.finalCheck = function moveToMiddleLineStart_finalCheck()
-      {
-        testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "words", 10, 15,
-                            [ "textarea" ]);
-
-        testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "words", 10, 15,
-                            [ "textarea" ]);
+      this.__proto__ = aInvoker;
 
-        testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
-                         [ "textarea" ]);
-
-        testTextAtOffset(kCaretOffset, BOUNDARY_LINE_END, "\ntwo ", 5, 10,
-                         [ "textarea" ]);
-
-        testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_START, "aword\n", 0, 6,
-                             [ "textarea" ]);
-
-        testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_END, "aword", 0, 5,
-                             [ "textarea" ]);
-      }
-
-      this.getID = function moveToMiddleLineStart_getID()
+      this.finalCheck = function genericMoveTo_finalCheck()
       {
-        return "move to middle line start";
-      }
-    }
-
-    function moveToMiddleLineEnd()
-    {
-      this.__proto__ = new moveToLineEnd("textarea", 10);
-
-      this.finalCheck = function moveToMiddleLineEnd_finalCheck()
-      {
-        testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "words", 10, 15,
-                            [ "textarea" ]);
-
-        testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "words", 10, 15,
-                            [ "textarea" ]);
-
-        testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
-                         [ "textarea" ]);
-
-        testTextAtOffset(kCaretOffset, BOUNDARY_LINE_END, "\ntwo ", 5, 10,
-                         [ "textarea" ]);
-
-        testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_START, "aword\n", 0, 6,
-                             [ "textarea" ]);
-
-        testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_END, "aword", 0, 5,
-                             [ "textarea" ]);
+        for (var i = 0; i < aTests.length; i++) {
+          aTests[i][0].call(null, kCaretOffset, aTests[i][1],
+                            aTests[i][2], aTests[i][3], aTests[i][4], aID,
+                            kOk, kOk, kOk);
+        }
       }
 
-      this.getID = function moveToMiddleLineEnd_getID()
-      {
-        return "move to middle line end";
-      }
-    }
-
-    function moveToFirstLineStart()
-    {
-      this.__proto__ = new moveToTextStart("textarea");
-
-      this.finalCheck = function moveToFirstLineStart_finalCheck()
-      {
-        testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
-                            [ "textarea" ]);
-
-        testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "\ntwo ", 5, 10,
-                            [ "textarea" ]);
-
-        testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "aword\n", 0, 6,
-                         [ "textarea" ]);
-
-        testTextAtOffset(kCaretOffset, BOUNDARY_LINE_END, "aword", 0, 5,
-                         [ "textarea" ]);
-
-        testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_START, "", 0, 0,
-                             [ "textarea" ]);
-
-        testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_END, "", 0, 0,
-                             [ "textarea" ]);
-      }
-
-      this.getID = function moveToFirstLineStart_getID()
+      this.getID = function genericMoveTo_getID()
       {
-        return "move to first line start";
-      }
-    }
-
-    function moveToFirstLineEnd()
-    {
-      this.__proto__ = new moveToLineEnd("textarea", 5);
-
-      this.finalCheck = function moveToFirstLineStart_finalCheck()
-      {
-        testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_START, "two ", 6, 10,
-                            [ "textarea" ]);
-
-        testTextAfterOffset(kCaretOffset, BOUNDARY_LINE_END, "\ntwo ", 5, 10,
-                            [ "textarea" ]);
-
-        testTextAtOffset(kCaretOffset, BOUNDARY_LINE_START, "aword\n", 0, 6,
-                         [ "textarea" ]);
-
-        testTextAtOffset(kCaretOffset, BOUNDARY_LINE_END, "aword", 0, 5,
-                         [ "textarea" ]);
-
-        testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_START, "", 0, 0,
-                             [ "textarea" ]);
-
-        testTextBeforeOffset(kCaretOffset, BOUNDARY_LINE_END, "", 0, 0,
-                             [ "textarea" ]);
-      }
-
-      this.getID = function moveToFirstLineEnd_getID()
-      {
-        return "move to first line end";
+        return aInvokerID;
       }
     }
 
     var gQueue = null;
     function doTest()
     {
       gQueue = new eventQueue();
-      gQueue.push(new moveToLastLineEnd());
-      gQueue.push(new moveToLastLineStart());
-      gQueue.push(new moveToMiddleLineStart());
-      gQueue.push(new moveToMiddleLineEnd());
-      gQueue.push(new moveToFirstLineStart());
-      gQueue.push(new moveToFirstLineEnd());
+
+      // __a__w__o__r__d__\n
+      //  0  1  2  3  4  5
+      // __t__w__o__ (soft line break)
+      //  6  7  8  9
+      // __w__o__r__d__s
+      // 10 11 12 13 14 15
+
+      traverseTextByLines(gQueue, "textarea", 
+                          [ [ "aword", "\n", 0, 5 ],
+                            [ "two ", "", 6, 10 ],
+                            [ "words", "", 10, 15 ]] );
+
+      traverseTextByLines(gQueue, "ta_wrapped", 
+                          [ [ "hi ", "", 0, 3 ],
+                            [ "hello", "", 3, 8 ],
+                            [ " my ", "", 8, 12 ],
+                            [ "longf", "", 12, 17 ],
+                            [ "riend", "", 17, 22 ],
+                            [ " t ", "", 22, 25 ],
+                            [ "sq t", "", 25, 29 ]] );
+
       gQueue.invoke(); // will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body>
@@ -246,11 +144,13 @@
    Bug 852021
   </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
 
   <textarea id="textarea" cols="5">aword
 two words</textarea>
+
+  <textarea id="ta_wrapped" cols="5">hi hello my longfriend t sq t</textarea>
   </pre>
 </body>
 </html>
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -306,17 +306,17 @@
           <menuitem id="context-openframeintab"
                     label="&openFrameCmdInTab.label;"
                     accesskey="&openFrameCmdInTab.accesskey;"
                     oncommand="gContextMenu.openFrameInTab();"/>
           <menuitem id="context-openframe"
                     label="&openFrameCmd.label;"
                     accesskey="&openFrameCmd.accesskey;"
                     oncommand="gContextMenu.openFrame();"/>
-          <menuseparator/>
+          <menuseparator id="open-frame-sep"/>
           <menuitem id="context-reloadframe"
                     label="&reloadFrameCmd.label;"
                     accesskey="&reloadFrameCmd.accesskey;"
                     oncommand="gContextMenu.reloadFrame();"/>
           <menuseparator/>
           <menuitem id="context-bookmarkframe"
                     label="&bookmarkThisFrameCmd.label;"
                     accesskey="&bookmarkThisFrameCmd.accesskey;"
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -272,16 +272,28 @@ nsContextMenu.prototype = {
                   !(this.isContentSelected || this.onTextInput || this.onLink ||
                     this.onImage || this.onVideo || this.onAudio || this.onSocial));
     this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
                                            !this.onSocial) || this.onPlainTextLink);
     this.showItem("context-searchselect", isTextSelected);
     this.showItem("context-keywordfield",
                   this.onTextInput && this.onKeywordField);
     this.showItem("frame", this.inFrame);
+
+    // srcdoc cannot be opened separately due to concerns about web
+    // content with about:srcdoc in location bar masquerading as trusted
+    // chrome/addon content.
+    // No need to also test for this.inFrame as this is checked in the parent
+    // submenu.
+    this.showItem("context-showonlythisframe", !this.inSrcdocFrame);
+    this.showItem("context-openframeintab", !this.inSrcdocFrame);
+    this.showItem("context-openframe", !this.inSrcdocFrame);
+    this.showItem("context-bookmarkframe", !this.inSrcdocFrame);
+    this.showItem("open-frame-sep", !this.inSrcdocFrame);
+
     this.showItem("frame-sep", this.inFrame && isTextSelected);
 
     // Hide menu entries for images, show otherwise
     if (this.inFrame) {
       if (mimeTypeIsTextBased(this.target.ownerDocument.contentType))
         this.isFrameImage.removeAttribute('hidden');
       else
         this.isFrameImage.setAttribute('hidden', 'true');
@@ -511,16 +523,17 @@ nsContextMenu.prototype = {
     this.onMailtoLink      = false;
     this.onSaveableLink    = false;
     this.link              = null;
     this.linkURL           = "";
     this.linkURI           = null;
     this.linkProtocol      = "";
     this.onMathML          = false;
     this.inFrame           = false;
+    this.inSrcdocFrame     = false;
     this.inSyntheticDoc    = false;
     this.hasBGImage        = false;
     this.bgImageURL        = "";
     this.onEditableArea    = false;
     this.isDesignMode      = false;
     this.onCTPPlugin       = false;
 
     // Remember the node that was clicked.
@@ -674,21 +687,21 @@ nsContextMenu.prototype = {
     if ((this.target.nodeType == Node.TEXT_NODE &&
          this.target.parentNode.namespaceURI == NS_MathML)
          || (this.target.namespaceURI == NS_MathML))
       this.onMathML = true;
 
     // See if the user clicked in a frame.
     var docDefaultView = this.target.ownerDocument.defaultView;
     if (docDefaultView != docDefaultView.top) {
-      // srcdoc iframes are not considered frames for concerns about web
-      // content with about:srcdoc in location bar masqurading as trusted
-      // chrome/addon content.
-      if (!this.target.ownerDocument.isSrcdocDocument)
-        this.inFrame = true;
+      this.inFrame = true;
+
+      if (this.target.ownerDocument.isSrcdocDocument) {
+          this.inSrcdocFrame = true;
+      }
     }
 
     // if the document is editable, show context menu like in text inputs
     if (!this.onEditableArea) {
       var win = this.target.ownerDocument.defaultView;
       if (win) {
         var isEditable = false;
         try {
@@ -708,16 +721,17 @@ nsContextMenu.prototype = {
         if (isEditable) {
           this.onTextInput       = true;
           this.onKeywordField    = false;
           this.onImage           = false;
           this.onLoadedImage     = false;
           this.onCompletedImage  = false;
           this.onMathML          = false;
           this.inFrame           = false;
+          this.inSrcdocFrame     = false;
           this.hasBGImage        = false;
           this.isDesignMode      = true;
           this.onEditableArea = true;
           InlineSpellCheckerUI.init(editingSession.getEditorForWindow(win));
           var canSpell = InlineSpellCheckerUI.canSpellCheck;
           InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
           this.showItem("spell-check-enabled", canSpell);
           this.showItem("spell-separator", canSpell);
--- a/browser/base/content/test/subtst_contextmenu.html
+++ b/browser/base/content/test/subtst_contextmenu.html
@@ -62,10 +62,11 @@ Browser context menu subtest.
 </div>
 <div id="test-select-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
 <div id="test-select-text-link">http://mozilla.com</div>
 <a id="test-image-link" href="#"><img src="ctxmenu-image.png"></a>
 <input id="test-select-input-text" type="text" value="input">
 <input id="test-select-input-text-type-password" type="password" value="password">
 <embed id="test-plugin" style="width: 200px; height: 200px;" type="application/x-test"></embed>
 <img id="test-longdesc" src="ctxmenu-image.png" longdesc="http://www.mozilla.org"></embed>
+<iframe id="test-srcdoc" width="98"  height="98" srcdoc="Hello World" style="border: 1px solid black"></iframe>
 </body>
 </html>
--- a/browser/base/content/test/test_contextmenu.html
+++ b/browser/base/content/test/test_contextmenu.html
@@ -950,16 +950,43 @@ function runTest(testNum) {
                           "---",                          null,
                           "context-saveimage",            true,
                           "context-sendimage",            true,
                           "context-setDesktopBackground", true,
                           "context-viewimageinfo",        true,
                           "context-viewimagedesc",        true
                          ].concat(inspectItems));
         closeContextMenu();
+        openContextMenuFor(srcdoc);
+        return;
+
+    case 31:
+        // Context menu for an iframe with srcdoc attribute set
+        checkContextMenu(["context-back",         false,
+                          "context-forward",      false,
+                          "context-reload",       true,
+                          "---",                  null,
+                          "context-bookmarkpage", true,
+                          "context-savepage",     true,
+                          "---",                  null,
+                          "context-viewbgimage",  false,
+                          "context-selectall",    true,
+                          "frame",                null,
+                              ["context-reloadframe",       true,
+                               "---",                       null,
+                               "context-saveframe",         true,
+                               "---",                       null,
+                               "context-printframe",        true,
+                               "---",                       null,
+                               "context-viewframesource",   true,
+                               "context-viewframeinfo",     true], null,
+                          "---",                  null,
+                          "context-viewsource",   true,
+                          "context-viewinfo",     true
+        ].concat(inspectItems));
 
         // finish test
         subwindow.close();
         SimpleTest.finish();
         return;
 
     /*
      * Other things that would be nice to test:
@@ -979,17 +1006,17 @@ function runTest(testNum) {
 
 
 var testNum = 1;
 var subwindow, chromeWin, contextMenu, lastElement;
 var text, link, mailto, input, img, canvas, video_ok, video_bad, video_bad2,
     iframe, video_in_iframe, image_in_iframe, textarea, contenteditable,
     inputspell, pagemenu, dom_full_screen, plainTextItems, audio_in_video,
     selecttext, selecttextlink, imagelink, select_inputtext, select_inputtext_password,
-    plugin, longdesc;
+    plugin, longdesc, iframe;
 
 function startTest() {
     chromeWin = SpecialPowers.wrap(subwindow)
                     .QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIWebNavigation)
                     .QueryInterface(Ci.nsIDocShellTreeItem)
                     .rootTreeItem
                     .QueryInterface(Ci.nsIInterfaceRequestor)
@@ -1029,16 +1056,17 @@ function startTest() {
     pagemenu = subwindow.document.getElementById("test-pagemenu");
     dom_full_screen = subwindow.document.getElementById("test-dom-full-screen");
     selecttext = subwindow.document.getElementById("test-select-text");
     selecttextlink = subwindow.document.getElementById("test-select-text-link");
     select_inputtext = subwindow.document.getElementById("test-select-input-text");
     select_inputtext_password = subwindow.document.getElementById("test-select-input-text-type-password");
     plugin = subwindow.document.getElementById("test-plugin");
     longdesc = subwindow.document.getElementById("test-longdesc");
+    srcdoc = subwindow.document.getElementById("test-srcdoc");
 
     contextMenu.addEventListener("popupshown", function() { runTest(++testNum); }, false);
     runTest(1);
 }
 
 // We open this in a separate window, because the Mochitests run inside a frame.
 // The frame causes an extra menu item, and prevents running the test
 // standalone (ie, clicking the test name in the Mochitest window) to see
--- a/browser/metro/base/content/ContextUI.js
+++ b/browser/metro/base/content/ContextUI.js
@@ -93,16 +93,17 @@ var ContextUI = {
   /*
    * Show the nav and tabs bar. Returns true if any non-visible UI
    * was shown. Fires context ui events.
    */
   displayNavUI: function () {
     let shown = false;
 
     if (!this.navbarVisible) {
+      BrowserUI.updateURI();
       this.displayNavbar();
       shown = true;
     }
 
     if (!this.tabbarVisible) {
       this.displayTabs();
       shown = true;
     }
--- a/browser/metro/base/content/NavButtonSlider.js
+++ b/browser/metro/base/content/NavButtonSlider.js
@@ -56,17 +56,17 @@ var NavButtonSlider = {
    * logic
    */
 
   init: function init() {
     // Touch drag support provided by input.js
     this._back.customDragger = this;
     this._plus.customDragger = this;
     Elements.browsers.addEventListener("ContentSizeChanged", this, true);
-    let events = ["mousedown", "mouseup", "mousemove", "click"];
+    let events = ["mousedown", "mouseup", "mousemove", "click", "touchstart", "touchmove", "touchend"];
     events.forEach(function (value) {
       this._back.addEventListener(value, this, true);
       this._plus.addEventListener(value, this, true);
     }, this);
 
     this._updateStops();
     this._updateVisibility();
     Services.prefs.addObserver(kNavButtonPref, this, false);
@@ -130,30 +130,57 @@ var NavButtonSlider = {
    * Events
    */
 
   handleEvent: function handleEvent(aEvent) {
     switch (aEvent.type) {
       case "ContentSizeChanged":
         this._updateStops();
         break;
+
+      case "touchstart":
+        if (aEvent.touches.length != 1)
+          break;
+        aEvent.preventDefault();
+        aEvent = aEvent.touches[0];
       case "mousedown":
         this._getPosition();
         this._mouseDown = true;
         this._mouseMoveStarted = false;
         this._mouseY = aEvent.clientY;
-        aEvent.originalTarget.setCapture();
+        if (aEvent.originalTarget)
+          aEvent.originalTarget.setCapture();
         this._back.setAttribute("mousedrag", true);
         this._plus.setAttribute("mousedrag", true);
         break;
+
+      case "touchend":
+        if (aEvent.touches.length != 0)
+          break;
+        this._mouseDown = false;
+        this._back.removeAttribute("mousedrag");
+        this._plus.removeAttribute("mousedrag");
+        if (!this._mouseMoveStarted) {
+          if (aEvent.originalTarget == this._back) {
+            CommandUpdater.doCommand('cmd_back');
+          } else {
+            CommandUpdater.doCommand('cmd_newTab');
+          }
+        }
+        break;
       case "mouseup":
         this._mouseDown = false;
         this._back.removeAttribute("mousedrag");
         this._plus.removeAttribute("mousedrag");
         break;
+
+      case "touchmove":
+        if (aEvent.touches.length != 1)
+          break;
+        aEvent = aEvent.touches[0];
       case "mousemove":
         // Check to be sure this is a drag operation
         if (!this._mouseDown) {
           return;
         }
         // Don't start a drag until we've passed a threshold
         let dy = aEvent.clientY - this._mouseY;
         if (!this._mouseMoveStarted && Math.abs(dy) < kOnClickMargin) {
--- a/browser/metro/base/content/WebProgress.js
+++ b/browser/metro/base/content/WebProgress.js
@@ -136,25 +136,30 @@ const WebProgress = {
       aTab.scrolledAreaChanged(true);
       aTab.updateThumbnailSource();
     });
   },
 
   _networkStart: function _networkStart(aJson, aTab) {
     aTab.startLoading();
 
-    if (aTab == Browser.selectedTab)
-      BrowserUI.update(TOOLBARSTATE_LOADING);
+    if (aTab == Browser.selectedTab) {
+      // NO_STARTUI_VISIBILITY since the current uri for the tab has not
+      // been updated yet. If we're coming off of the start page, this
+      // would briefly show StartUI until _locationChange is called.
+      BrowserUI.update(BrowserUI.NO_STARTUI_VISIBILITY);
+    }
   },
 
   _networkStop: function _networkStop(aJson, aTab) {
     aTab.endLoading();
 
-    if (aTab == Browser.selectedTab)
-      BrowserUI.update(TOOLBARSTATE_LOADED);
+    if (aTab == Browser.selectedTab) {
+      BrowserUI.update();
+    }
   },
 
   _windowStart: function _windowStart(aJson, aTab) {
     this._progressStart(aJson, aTab);
   },
 
   _windowStop: function _windowStop(aJson, aTab) {
     this._progressStop(aJson, aTab);
new file mode 100644
--- /dev/null
+++ b/browser/metro/base/content/apzc.js
@@ -0,0 +1,120 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* 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/. */
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+let Cr = Components.results;
+
+/**
+ * Handler for APZC display port and pan begin/end notifications.
+ * These notifications are only sent by widget/windows/winrt code when
+ * the pref: layers.async-pan-zoom.enabled is true.
+ */
+
+var APZCObserver = {
+  init: function() {
+    this._enabled = Services.prefs.getBoolPref(kAsyncPanZoomEnabled);
+    if (!this._enabled) {
+      return;
+    }
+
+    let os = Services.obs;
+    os.addObserver(this, "apzc-request-content-repaint", false);
+    os.addObserver(this, "apzc-handle-pan-begin", false);
+    os.addObserver(this, "apzc-handle-pan-end", false);
+
+    Elements.tabList.addEventListener("TabSelect", this, true);
+    Elements.tabList.addEventListener("TabOpen", this, true);
+    Elements.tabList.addEventListener("TabClose", this, true);
+  },
+
+  handleEvent: function APZC_handleEvent(aEvent) {
+    switch (aEvent.type) {
+      case 'pageshow':
+      case 'TabSelect':
+        Services.obs.notifyObservers(null, "viewport-needs-updating", null);
+        break;
+      case 'TabOpen': {
+        let browser = aEvent.originalTarget.linkedBrowser;
+        browser.addEventListener("pageshow", this, true);
+        break;
+      }
+      case 'TabClose': {
+        let browser = aEvent.originalTarget.linkedBrowser;
+        browser.removeEventListener("pageshow", this);
+        break;
+      }
+    }
+  },
+  shutdown: function shutdown() {
+    if (!this._enabled) {
+      return;
+    }
+    Elements.tabList.removeEventListener("TabSelect", this, true);
+    Elements.tabList.removeEventListener("TabOpen", this, true);
+    Elements.tabList.removeEventListener("TabClose", this, true);
+
+    let os = Services.obs;
+    os.removeObserver(this, "apzc-request-content-repaint");
+    os.removeObserver(this, "apzc-handle-pan-begin");
+    os.removeObserver(this, "apzc-handle-pan-end");
+  },
+  observe: function ao_observe(aSubject, aTopic, aData) {
+    if (aTopic == "apzc-request-content-repaint") {
+      let frameMetrics = JSON.parse(aData);
+      let scrollTo = frameMetrics.scrollTo;
+      let displayPort = frameMetrics.displayPort;
+      let resolution = frameMetrics.resolution;
+      let compositedRect = frameMetrics.compositedRect;
+
+      if (StartUI.isStartPageVisible) {
+        let windowUtils = Browser.windowUtils;
+        Browser.selectedBrowser.contentWindow.scrollTo(scrollTo.x, scrollTo.y);
+        windowUtils.setResolution(resolution, resolution);
+        windowUtils.setDisplayPortForElement(displayPort.x * resolution,
+                                             displayPort.y * resolution,
+                                             displayPort.width * resolution,
+                                             displayPort.height * resolution,
+                                             Elements.startUI);
+      } else {
+        let windowUtils = Browser.selectedBrowser.contentWindow.
+                                  QueryInterface(Ci.nsIInterfaceRequestor).
+                                  getInterface(Ci.nsIDOMWindowUtils);
+        windowUtils.setScrollPositionClampingScrollPortSize(compositedRect.width,
+                                                            compositedRect.height);
+        Browser.selectedBrowser.messageManager.sendAsyncMessage("Content:SetCacheViewport", {
+          scrollX: scrollTo.x,
+          scrollY: scrollTo.y,
+          x: displayPort.x + scrollTo.x,
+          y: displayPort.y + scrollTo.y,
+          w: displayPort.width,
+          h: displayPort.height,
+          scale: resolution,
+          id: 1
+        });
+      }
+
+      Util.dumpLn("APZC scrollTo.x: " + scrollTo.x + ", scrollTo.y: " + scrollTo.y);
+      Util.dumpLn("APZC setResolution: " + resolution);
+      Util.dumpLn("APZC setDisplayPortForElement: displayPort.x: " +
+                  displayPort.x + ", displayPort.y: " + displayPort.y +
+                  ", displayPort.width: " + displayPort.width +
+                  ", displayort.height: " + displayPort.height);
+    } else if (aTopic == "apzc-handle-pan-begin") {
+      // When we're panning, hide the main scrollbars by setting imprecise
+      // input (which sets a property on the browser which hides the scrollbar
+      // via CSS).  This reduces jittering from left to right. We may be able
+      // to get rid of this once we implement axis locking in /gfx APZC.
+      Util.dumpLn("APZC pan-begin");
+      if (InputSourceHelper.isPrecise) {
+        InputSourceHelper._imprecise();
+      }
+
+    } else if (aTopic == "apzc-handle-pan-end") {
+      Util.dumpLn("APZC pan-end");
+    }
+  }
+};
--- a/browser/metro/base/content/bindings/browser.js
+++ b/browser/metro/base/content/bindings/browser.js
@@ -609,17 +609,17 @@ let ContentScroll =  {
         if (!element)
           break;
 
         let binding = element.ownerDocument.getBindingParent(element);
         if (binding instanceof Ci.nsIDOMHTMLInputElement && binding.mozIsTextField(false))
           break;
 
         // Set the scroll offset for this element if specified
-        if (json.scrollX >= 0 && json.scrollY >= 0) {
+        if (json.scrollX >= 0 || json.scrollY >= 0) {
           this.setScrollOffsetForElement(element, json.scrollX, json.scrollY)
           if (json.id == 1)
             this._scrollOffset = this.getScrollOffset(content);
         }
 
         // Set displayport. We want to set this after setting the scroll offset, because
         // it is calculated based on the scroll offset.
         let scrollOffset = this.getScrollOffsetForElement(element);
--- a/browser/metro/base/content/bindings/flyoutpanel.xml
+++ b/browser/metro/base/content/bindings/flyoutpanel.xml
@@ -12,17 +12,17 @@
     <content>
       <xul:vbox class="flyoutpanel-wrapper">
         <xul:hbox class="flyoutpanel-header" align="top">
           <xul:toolbarbutton class="flyout-close-button"
                              command="cmd_flyout_back"
                              xbl:inherits="command"/>
           <xul:label class="flyout-header-label" xbl:inherits="value=headertext"/>
         </xul:hbox>
-        <xul:scrollbox class="flyoutpanel-contents" flex="1" orient="vertical">
+        <xul:scrollbox class="flyoutpanel-contents" observes="bcast_preciseInput" flex="1" orient="vertical">
           <children/>
         </xul:scrollbox>
       </xul:vbox>
     </content>
 
     <implementation>
       <constructor>
         <![CDATA[
--- a/browser/metro/base/content/bindings/grid.xml
+++ b/browser/metro/base/content/bindings/grid.xml
@@ -353,31 +353,27 @@
           ]]>
         </body>
       </method>
 
       <!-- Interface for grid layout management -->
 
       <field name="_rowCount">0</field>
       <property name="rowCount" readonly="true" onget="return this._rowCount;"/>
-
       <field name="_columnCount">0</field>
       <property name="columnCount" readonly="true" onget="return this._columnCount;"/>
-
       <property name="_containerSize">
         <getter><![CDATA[
             // return the rect that represents our bounding box
-            let containerNode = this.parentNode;
-
+            let containerNode = this.hasAttribute("flex") ? this : this.parentNode;
             // Autocomplete is a binding within a binding, so we have to step
             // up an additional parentNode.
             if (containerNode.id == "results-vbox" ||
                 containerNode.id == "searches-vbox")
                 containerNode = containerNode.parentNode;
-
             let rect = containerNode.getBoundingClientRect();
             // return falsy if the container has no height
             return rect.height ? {
               width: rect.width,
               height: rect.height
             } : null;
         ]]></getter>
       </property>
--- a/browser/metro/base/content/browser-ui.js
+++ b/browser/metro/base/content/browser-ui.js
@@ -6,21 +6,16 @@
 
 Cu.import("resource://gre/modules/PageThumbs.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-server.jsm")
 
 /**
  * Constants
  */
 
-// BrowserUI.update(state) constants. Currently passed in
-// but update doesn't pay attention to them. Can we remove?
-const TOOLBARSTATE_LOADING  = 1;
-const TOOLBARSTATE_LOADED   = 2;
-
 // Page for which the start UI is shown
 const kStartOverlayURI = "about:start";
 
 // Devtools Messages
 const debugServerStateChanged = "devtools.debugger.remote-enabled";
 const debugServerPortChanged = "devtools.debugger.remote-port";
 
 // delay when showing the tab bar briefly after a new (empty) tab opens
@@ -292,21 +287,29 @@ var BrowserUI = {
 #endif
   },
 
 
   /*********************************
    * Navigation
    */
 
-  /* Updates the overall state of the toolbar, but not the URL bar. */
-  update: function(aState) {
-    let uri = this.getDisplayURI(Browser.selectedBrowser);
-    StartUI.update(uri);
+  // BrowserUI update bit flags
+  NO_STARTUI_VISIBILITY:  1, // don't change the start ui visibility
 
+  /*
+   * Updates the overall state of startui visibility and the toolbar, but not
+   * the URL bar.
+   */
+  update: function(aFlags) {
+    let flags = aFlags || 0;
+    if (!(flags & this.NO_STARTUI_VISIBILITY)) {
+      let uri = this.getDisplayURI(Browser.selectedBrowser);
+      StartUI.update(uri);
+    }
     this._updateButtons();
     this._updateToolbar();
   },
 
   /* Updates the URL bar. */
   updateURI: function(aOptions) {
     let uri = this.getDisplayURI(Browser.selectedBrowser);
     let cleanURI = Util.isURLEmpty(uri) ? "" : uri;
--- a/browser/metro/base/content/browser.js
+++ b/browser/metro/base/content/browser.js
@@ -67,16 +67,17 @@ var Browser = {
     // we start processing input in TouchModule.
     InputSourceHelper.init();
 
     TouchModule.init();
     ScrollwheelModule.init(Elements.browsers);
     GestureModule.init();
     BrowserTouchHandler.init();
     PopupBlockerObserver.init();
+    APZCObserver.init();
 
     // Init the touch scrollbox
     this.contentScrollbox = Elements.browsers;
     this.contentScrollboxScroller = {
       scrollBy: function(aDx, aDy) {
         let view = getBrowser().getRootView();
         view.scrollBy(aDx, aDy);
       },
@@ -230,16 +231,17 @@ var Browser = {
     if (closingCancelled.data)
       return false;
 
     Services.obs.notifyObservers(null, "browser-lastwindow-close-granted", null);
     return true;
   },
 
   shutdown: function shutdown() {
+    APZCObserver.shutdown();
     BrowserUI.uninit();
     ContentAreaObserver.shutdown();
 
     messageManager.removeMessageListener("MozScrolledAreaChanged", this);
     messageManager.removeMessageListener("Browser:ViewportMetadata", this);
     messageManager.removeMessageListener("Browser:FormSubmit", this);
     messageManager.removeMessageListener("Browser:ZoomToPoint:Return", this);
     messageManager.removeMessageListener("scroll", this);
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -42,17 +42,18 @@
         xmlns:svg="http://www.w3.org/2000/svg"
         xmlns:html="http://www.w3.org/1999/xhtml">
 
   <script type="application/javascript" src="chrome://browser/content/browser.js"/>
   <script type="application/javascript" src="chrome://browser/content/browser-scripts.js"/>
   <script type="application/javascript" src="chrome://browser/content/browser-ui.js"/>
   <script type="application/javascript" src="chrome://browser/content/Util.js"/>
   <script type="application/javascript" src="chrome://browser/content/input.js"/>
-  <script type="application/javascript;version=1.8" src="chrome://browser/content/appbar.js"/>
+  <script type="application/javascript" src="chrome://browser/content/appbar.js"/>
+  <script type="application/javascript" src="chrome://browser/content/apzc.js"/>
   <broadcasterset id="broadcasterset">
     <broadcaster id="bcast_contentShowing" disabled="false"/>
     <broadcaster id="bcast_urlbarState" mode="view"/>
     <broadcaster id="bcast_preciseInput" input="precise"/>
     <broadcaster id="bcast_windowState" viewstate=""/>
   </broadcasterset>
 
   <observerset id="observerset">
--- a/browser/metro/base/content/downloads.js
+++ b/browser/metro/base/content/downloads.js
@@ -207,18 +207,17 @@ var Downloads = {
       }
     ];
     this.showNotification("download-failed", message, buttons,
       this._notificationBox.PRIORITY_WARNING_HIGH);
   },
 
   _showDownloadCompleteNotification: function (aDownload) {
     let message = "";
-    let showInFilesButtonText = PluralForm.get(this._downloadCount,
-      Strings.browser.GetStringFromName("downloadsShowInFiles"));
+    let showInFilesButtonText = Strings.browser.GetStringFromName("downloadShowInFiles");
 
     let buttons = [
       {
         label: showInFilesButtonText,
         accessKey: "",
         callback: function() {
           let fileURI = aDownload.target;
           let file = Downloads._getLocalFile(fileURI);
--- a/browser/metro/base/content/helperui/ChromeSelectionHandler.js
+++ b/browser/metro/base/content/helperui/ChromeSelectionHandler.js
@@ -50,16 +50,17 @@ var ChromeSelectionHandler = {
     if (!selection.isCollapsed) {
       this._mode = kSelectionMode;
       this._updateSelectionUI("start", true, true);
     } else {
       this._mode = kCaretMode;
       this._updateSelectionUI("caret", false, false, true);
     }
 
+    this._targetElement.addEventListener("blur", this, false);
   },
 
   /*
    * Selection monocle start move event handler
    */
   _onSelectionMoveStart: function _onSelectionMoveStart(aMsg) {
     if (!this.targetIsEditable) {
       this._onFail("_onSelectionMoveStart with bad targetElement.");
@@ -137,16 +138,27 @@ var ChromeSelectionHandler = {
     
     // Clear any existing scroll timers
     this._clearTimers();
 
     // Update the position of our selection monocles
     this._updateSelectionUI("end", true, true);
   },
 
+  _onSelectionUpdate: function _onSelectionUpdate() {
+    if (!this._targetHasFocus()) {
+      this._closeSelection();
+      return;
+    }
+    this._updateSelectionUI("update",
+                            this._mode == kSelectionMode,
+                            this._mode == kSelectionMode,
+                            this._mode == kCaretMode);
+  },
+
   /*
    * Switch selection modes. Currently we only support switching
    * from "caret" to "selection".
    */
   _onSwitchMode: function _onSwitchMode(aMode, aMarker, aX, aY) {
     if (aMode != "selection") {
       this._onFail("unsupported mode switch");
       return;
@@ -260,47 +272,64 @@ var ChromeSelectionHandler = {
    * _closeSelection
    *
    * Shuts SelectionHandler down.
    */
   _closeSelection: function _closeSelection() {
     this._clearTimers();
     this._cache = null;
     this._contentWindow = null;
-    this._targetElement = null;
+    if (this._targetElement) {
+      this._targetElement.removeEventListener("blur", this, true);
+      this._targetElement = null;
+    }
     this._selectionMoveActive = false;
     this._domWinUtils = null;
     this._targetIsEditable = false;
     this.sendAsync("Content:HandlerShutdown", {});
   },
 
   get hasSelection() {
     if (!this._targetElement) {
       return false;
     }
     let selection = this._getSelection();
     return (selection && !selection.isCollapsed);
   },
 
+  _targetHasFocus: function() {
+    if (!this._targetElement || !document.commandDispatcher.focusedElement) {
+      return false;
+    }
+    let bindingParent = this._contentWindow.document.getBindingParent(document.commandDispatcher.focusedElement);
+    return (bindingParent && this._targetElement == bindingParent);
+  },
+
   /*************************************************
    * Events
    */
 
   /*
    * Scroll + selection advancement timer when the monocle is
    * outside the bounds of an input control.
    */
   scrollTimerCallback: function scrollTimerCallback() {
     let result = ChromeSelectionHandler.updateTextEditSelection();
     // Update monocle position and speed if we've dragged off to one side
     if (result.trigger) {
       ChromeSelectionHandler._updateSelectionUI("update", result.start, result.end);
     }
   },
 
+  handleEvent: function handleEvent(aEvent) {
+    if (aEvent.type == "blur" && !this._targetHasFocus()) {
+      this._closeSelection();
+    }
+  },
+
   msgHandler: function msgHandler(aMsg, aJson) {
     if (this._debugEvents && "Browser:SelectionMove" != aMsg) {
       Util.dumpLn("ChromeSelectionHandler:", aMsg);
     }
     switch(aMsg) {
       case "Browser:SelectionDebug":
         this._onSelectionDebug(aJson);
         break;
@@ -313,20 +342,17 @@ var ChromeSelectionHandler = {
         this._onSelectionAttach(aJson);
         break;
 
       case "Browser:SelectionClose":
         this._onSelectionClose(aJson.clearSelection);
       break;
 
       case "Browser:SelectionUpdate":
-        this._updateSelectionUI("update",
-                                this._mode == kSelectionMode,
-                                this._mode == kSelectionMode,
-                                this._mode == kCaretMode);
+        this._onSelectionUpdate();
       break;
 
       case "Browser:SelectionMoveStart":
         this._onSelectionMoveStart(aJson);
         break;
 
       case "Browser:SelectionMove":
         this._onSelectionMove(aJson);
--- a/browser/metro/base/content/helperui/FindHelperUI.js
+++ b/browser/metro/base/content/helperui/FindHelperUI.js
@@ -91,21 +91,23 @@ var FindHelperUI = {
       case "keydown":
         if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN) {
           if (aEvent.shiftKey) {
             this.goToPrevious();
           } else {
             this.goToNext();
           }
         }
+        break;
 
       case "MozAppbarShowing":
         if (aEvent.target != this._container) {
           this.hide();
         }
+        break;
     }
   },
 
   show: function findHelperShow() {
     if (StartUI.isVisible || this._open)
       return;
 
     // Hide any menus
--- a/browser/metro/base/content/helperui/SelectionHelperUI.js
+++ b/browser/metro/base/content/helperui/SelectionHelperUI.js
@@ -506,24 +506,28 @@ var SelectionHelperUI = {
     // SelectionHandler messages
     messageManager.addMessageListener("Content:SelectionRange", this);
     messageManager.addMessageListener("Content:SelectionCopied", this);
     messageManager.addMessageListener("Content:SelectionFail", this);
     messageManager.addMessageListener("Content:SelectionDebugRect", this);
     messageManager.addMessageListener("Content:HandlerShutdown", this);
     messageManager.addMessageListener("Content:SelectionHandlerPong", this);
 
+    // capture phase
     window.addEventListener("keypress", this, true);
-    window.addEventListener("click", this, false);
-    window.addEventListener("touchstart", this, true);
-    window.addEventListener("touchend", this, true);
-    window.addEventListener("touchmove", this, true);
     window.addEventListener("MozPrecisePointer", this, true);
     window.addEventListener("MozDeckOffsetChanging", this, true);
     window.addEventListener("MozDeckOffsetChanged", this, true);
+    window.addEventListener("KeyboardChanged", this, true);
+
+    // bubble phase
+    window.addEventListener("click", this, false);
+    window.addEventListener("touchstart", this, false);
+    window.addEventListener("touchend", this, false);
+    window.addEventListener("touchmove", this, false);
 
     Elements.browsers.addEventListener("URLChanged", this, true);
     Elements.browsers.addEventListener("SizeChanged", this, true);
     Elements.browsers.addEventListener("ZoomChanged", this, true);
 
     Elements.navbar.addEventListener("transitionend", this, true);
     Elements.navbar.addEventListener("MozAppbarDismissing", this, true);
 
@@ -535,22 +539,23 @@ var SelectionHelperUI = {
     messageManager.removeMessageListener("Content:SelectionCopied", this);
     messageManager.removeMessageListener("Content:SelectionFail", this);
     messageManager.removeMessageListener("Content:SelectionDebugRect", this);
     messageManager.removeMessageListener("Content:HandlerShutdown", this);
     messageManager.removeMessageListener("Content:SelectionHandlerPong", this);
 
     window.removeEventListener("keypress", this, true);
     window.removeEventListener("click", this, false);
-    window.removeEventListener("touchstart", this, true);
-    window.removeEventListener("touchend", this, true);
-    window.removeEventListener("touchmove", this, true);
+    window.removeEventListener("touchstart", this, false);
+    window.removeEventListener("touchend", this, false);
+    window.removeEventListener("touchmove", this, false);
     window.removeEventListener("MozPrecisePointer", this, true);
     window.removeEventListener("MozDeckOffsetChanging", this, true);
     window.removeEventListener("MozDeckOffsetChanged", this, true);
+    window.removeEventListener("KeyboardChanged", this, true);
 
     Elements.browsers.removeEventListener("URLChanged", this, true);
     Elements.browsers.removeEventListener("SizeChanged", this, true);
     Elements.browsers.removeEventListener("ZoomChanged", this, true);
 
     Elements.navbar.removeEventListener("transitionend", this, true);
     Elements.navbar.removeEventListener("MozAppbarDismissing", this, true);
 
@@ -913,16 +918,23 @@ var SelectionHelperUI = {
 
   _onNavBarDismissEvent: function _onNavBarDismissEvent() {
     if (!this.isActive || this.layerMode == kContentLayer) {
       return;
     }
     this._hideMonocles();
   },
 
+  _onKeyboardChangedEvent: function _onKeyboardChangedEvent() {
+    if (!this.isActive || this.layerMode == kContentLayer) {
+      return;
+    }
+    this._sendAsyncMessage("Browser:SelectionUpdate", {});
+  },
+
   /*
    * Event handlers for message manager
    */
 
   _onDebugRectRequest: function _onDebugRectRequest(aMsg) {
     this.overlay.addDebugRect(aMsg.left, aMsg.top, aMsg.right, aMsg.bottom,
                               aMsg.color, aMsg.fill, aMsg.id);
   },
@@ -1008,16 +1020,21 @@ var SelectionHelperUI = {
     switch (aEvent.type) {
       case "click":
         this._onTap(aEvent);
         break;
 
       case "touchstart": {
         if (aEvent.touches.length != 1)
           break;
+        // Only prevent default if we're dragging so that
+        // APZC doesn't scroll.
+        if (this._checkForActiveDrag()) {
+          aEvent.preventDefault();
+        }
         let touch = aEvent.touches[0];
         this._movement.x = touch.clientX;
         this._movement.y = touch.clientY;
         this._movement.active = true;
         break;
       }
 
       case "touchend":
@@ -1066,16 +1083,20 @@ var SelectionHelperUI = {
 
       case "transitionend":
         this._onNavBarTransitionEvent(aEvent);
         break;
 
       case "MozAppbarDismissing":
         this._onNavBarDismissEvent();
         break;
+
+      case "KeyboardChanged":
+        this._onKeyboardChangedEvent();
+        break;
     }
   },
 
   receiveMessage: function sh_receiveMessage(aMessage) {
     if (this._debugEvents) Util.dumpLn("SelectionHelperUI:", aMessage.name);
     let json = aMessage.json;
     switch (aMessage.name) {
       case "Content:SelectionFail":
--- a/browser/metro/base/content/input.js
+++ b/browser/metro/base/content/input.js
@@ -37,16 +37,17 @@ const kDebugMouseInputPref = "metro.debu
 // Display rects around selection ranges. Useful in debugging
 // selection problems.
 const kDebugSelectionDisplayPref = "metro.debug.selection.displayRanges";
 // Dump range rect data to the console. Very useful, but also slows
 // things down a lot.
 const kDebugSelectionDumpPref = "metro.debug.selection.dumpRanges";
 // Dump message manager event traffic for selection.
 const kDebugSelectionDumpEvents = "metro.debug.selection.dumpEvents";
+const kAsyncPanZoomEnabled = "layers.async-pan-zoom.enabled"
 
 /**
  * TouchModule
  *
  * Handles all touch-related input such as dragging and tapping.
  *
  * The Fennec chrome DOM tree has elements that are augmented dynamically with
  * custom JS properties that tell the TouchModule they have custom support for
@@ -230,16 +231,24 @@ var TouchModule = {
 
     this._targetScrollbox = targetScrollInterface ? targetScrollInterface.element : targetScrollbox;
     this._targetScrollInterface = targetScrollInterface;
 
     if (!this._targetScrollbox) {
       return;
     }
 
+    // Don't allow kinetic panning if APZC is enabled and the pan element is the deck
+    let deck = document.getElementById("browsers");
+    if (Services.prefs.getBoolPref(kAsyncPanZoomEnabled) &&
+        !StartUI.isStartPageVisible &&
+        this._targetScrollbox == deck) {
+      return;
+    }
+
     // XXX shouldn't dragger always be valid here?
     if (dragger) {
       let draggable = dragger.isDraggable(targetScrollbox, targetScrollInterface);
       dragData.locked = !draggable.x || !draggable.y;
       if (draggable.x || draggable.y) {
         this._dragger = dragger;
         if (dragger.freeDrag)
           dragData.alwaysFreeDrag = dragger.freeDrag();
@@ -349,18 +358,25 @@ var TouchModule = {
 
     // Note: it is possible for kinetic scrolling to be active from a
     // mousedown/mouseup event previous to this one. In this case, we
     // want the kinetic panner to tell our drag interface to stop.
 
     if (dragData.isPan()) {
       if (Date.now() - this._dragStartTime > kStopKineticPanOnDragTimeout)
         this._kinetic._velocity.set(0, 0);
-      // Start kinetic pan.
-      this._kinetic.start();
+
+      // Start kinetic pan if we i) aren't using async pan zoom or ii) if we
+      // are on the start page, iii) If the scroll element is not browsers
+      let deck = document.getElementById("browsers");
+      if (!Services.prefs.getBoolPref(kAsyncPanZoomEnabled) ||
+          StartUI.isStartPageVisible ||
+          this._targetScrollbox != deck) {
+        this._kinetic.start();
+      }
     } else {
       this._kinetic.end();
       if (this._dragger)
         this._dragger.dragStop(0, 0, this._targetScrollInterface);
       this._dragger = null;
     }
   },
 
--- a/browser/metro/base/jar.mn
+++ b/browser/metro/base/jar.mn
@@ -91,11 +91,12 @@ chrome.jar:
   content/AnimatedZoom.js                      (content/AnimatedZoom.js)
   content/dbg-metro-actors.js                  (content/dbg-metro-actors.js)
 #ifdef MOZ_SERVICES_SYNC
   content/flyouts/syncFlyout.js                (content/flyouts/syncFlyout.js)
   content/RemoteTabs.js                        (content/RemoteTabs.js)
 #endif
   content/NavButtonSlider.js                   (content/NavButtonSlider.js)
   content/ContextUI.js                         (content/ContextUI.js)
+  content/apzc.js                              (content/apzc.js)
 
 % override chrome://global/content/config.xul chrome://browser/content/config.xul
 % override chrome://global/content/netError.xhtml chrome://browser/content/netError.xhtml
--- a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
+++ b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
@@ -66,17 +66,17 @@ gTests.push({
 
     // taps on the urlbar-edit leak a ClientRect property on the window
     delete window.r;
   },
 });
 
 gTests.push({
   desc: "bug 887120 - tap & hold to paste into urlbar",
-  run: function bug887120_test() {
+  run: function() {
     gWindow = window;
 
     yield showNavBar();
     let edit = document.getElementById("urlbar-edit");
 
     SpecialPowers.clipboardCopyString("mozilla");
     sendContextMenuClickToElement(window, edit);
     yield waitForEvent(document, "popupshown");
@@ -90,17 +90,17 @@ gTests.push({
 
     clearSelection(edit);
     delete window.r;
   }
 });
 
 gTests.push({
   desc: "bug 895284 - tap selection",
-  run: function bug887120_test() {
+  run: function() {
     gWindow = window;
 
     yield showNavBar();
     let edit = document.getElementById("urlbar-edit");
     edit.value = "wikipedia.org";
     edit.select();
 
     let editCoords = logicalCoordsForElement(edit);
@@ -118,15 +118,32 @@ gTests.push({
 
     ok(SelectionHelperUI.isCaretUIVisible, "caret browsing enabled");
 
     clearSelection(edit);
     delete window.r;
   }
 });
 
+gTests.push({
+  desc: "bug 894713 - blur shuts down selection handling",
+  run: function() {
+    gWindow = window;
+    yield showNavBar();
+    let edit = document.getElementById("urlbar-edit");
+    edit.value = "wikipedia.org";
+    edit.select();
+    let editCoords = logicalCoordsForElement(edit);
+    SelectionHelperUI.attachEditSession(ChromeSelectionHandler, editCoords.x, editCoords.y);
+    edit.blur();
+    ok(!SelectionHelperUI.isSelectionUIVisible, "selection no longer enabled");
+    clearSelection(edit);
+    delete window.r;
+  }
+});
+
 function test() {
   if (!isLandscapeMode()) {
     todo(false, "browser_selection_tests need landscape mode to run.");
     return;
   }
   runTests();
 }
--- a/browser/metro/base/tests/mochitest/browser_urlbar.js
+++ b/browser/metro/base/tests/mochitest/browser_urlbar.js
@@ -1,232 +1,262 @@
-// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-var gEdit = null;
-
-/*=============================================================================
-  Search engine mocking utilities
-=============================================================================*/
-
-var gEngine = null;
-
-const kSearchEngineName = "Foo";
-const kSearchEngineURI = chromeRoot + "res/testEngine.xml";
-
-/*
- * addMockSearchDefault - adds a mock search engine to the top of the engine list.
- */
-function addMockSearchDefault(aTimeoutMs) {
-  let deferred = Promise.defer();
-  let timeoutMs = aTimeoutMs || kDefaultWait;
-  let timerID = 0;
-
-  function engineAddObserver(aSubject, aTopic, aData) {
-    if (aData != "engine-added")
-      return;
-
-    gEngine = Services.search.getEngineByName(kSearchEngineName);
-    Services.obs.removeObserver(engineAddObserver, "browser-search-engine-modified");
-    clearTimeout(timerID);
-    gEngine.hidden = false;
-    ok(gEngine, "mock engine was added");
-    deferred.resolve();
-  }
-
-  if (gEngine) {
-    deferred.resolve();
-    return deferred.promise;
-  }
-
-  timerID = setTimeout(function ids_canceller() {
-    Services.obs.removeObserver(engineAddObserver, "browser-search-engine-modified");
-    deferred.reject(new Error("search add timeout"));
-  }, timeoutMs);
-
-  Services.obs.addObserver(engineAddObserver, "browser-search-engine-modified", false);
-  Services.search.addEngine(kSearchEngineURI, Ci.nsISearchEngine.DATA_XML,
-                            "data:image/x-icon,%00", false);
-  return deferred.promise;
-}
-
-/*
- * removeMockSearchDefault - removes mock "Foo" search engine.
- */
-
-function removeMockSearchDefault(aTimeoutMs) {
-  let deferred = Promise.defer();
-  let timeoutMs = aTimeoutMs || kDefaultWait;
-  let timerID = 0;
-
-  function engineRemoveObserver(aSubject, aTopic, aData) {
-    if (aData != "engine-removed")
-      return;
-
-    clearTimeout(timerID);
-    gEngine = null;
-    Services.obs.removeObserver(engineRemoveObserver, "browser-search-engine-modified");
-    deferred.resolve();
-  }
-
-  if (!gEngine) {
-    deferred.resolve();
-    return deferred.promise;
-  }
-
-  timerID = setTimeout(function ids_canceller() {
-    Services.obs.removeObserver(engineRemoveObserver, "browser-search-engine-modified");
-    deferred.reject(new Error("search remove timeout"));
-  }, timeoutMs);
-
-  Services.obs.addObserver(engineRemoveObserver, "browser-search-engine-modified", false);
-  Services.search.removeEngine(gEngine);
-  return deferred.promise;
-}
-
-/*=============================================================================
-  Test cases
-=============================================================================*/
-
-function test() {
-  runTests();
-}
-
-
-function setUp() {
-  if (!gEdit)
-    gEdit = document.getElementById("urlbar-edit");
-
-  yield addTab("about:start");
-  yield showNavBar();
-  yield waitForCondition(function () {
-    return StartUI.isStartPageVisible;
-  });
-}
-
-function tearDown() {
-  yield removeMockSearchDefault();
-  Browser.closeTab(Browser.selectedTab, { forceClose: true });
-  delete window.r;
-}
-
-gTests.push({
-  desc: "search engines update",
-  setUp: setUp,
-  tearDown: tearDown,
-  run: function testSearchEngine() {
-    // If the XBL hasn't initialized yet, open the popup so that it will.
-    if (gEdit.popup._searches == undefined) {
-      gEdit.openPopup();
-      gEdit.closePopup();
-    }
-
-    let numSearches = gEdit.popup._searches.itemCount;
-    function getEngineItem() {
-      return gEdit.popup._searches.querySelector("richgriditem[value="+kSearchEngineName+"]");
-    }
-
-    yield addMockSearchDefault();
-    ok(gEdit.popup._searches.itemCount == numSearches + 1, "added search engine count");
-    ok(getEngineItem(), "added search engine item");
-
-    yield removeMockSearchDefault();
-    ok(gEdit.popup._searches.itemCount == numSearches, "normal search engine count");
-    ok(!getEngineItem(), "added search engine item");
-  }
-});
-
-gTests.push({
-  desc: "display autocomplete while typing, handle enter",
-  setUp: setUp,
-  tearDown: tearDown,
-  run: function testUrlbarTyping() {
-    sendElementTap(window, gEdit);
-    ok(gEdit.isEditing, "focus urlbar: in editing mode");
-    ok(!gEdit.popup.popupOpen, "focus urlbar: popup not open yet");
-
-    EventUtils.sendString("about:blank", window);
-    let opened = yield waitForCondition(() => gEdit.popup.popupOpen);
-    ok(opened, "type in urlbar: popup opens");
-
-    EventUtils.synthesizeKey("VK_RETURN", {}, window);
-    let closed = yield waitForCondition(() => !gEdit.popup.popupOpen);
-    ok(closed, "hit enter in urlbar: popup closes, page loads");
-    ok(!gEdit.isEditing, "hit enter in urlbar: not in editing mode");
-  }
-});
-
-gTests.push({
-  desc: "display and select a search with keyboard",
-  setUp: setUp,
-  tearDown: tearDown,
-  run: function testSearchKeyboard() {
-    yield addMockSearchDefault();
-
-    sendElementTap(window, gEdit);
-    ok(gEdit.isEditing, "focus urlbar: in editing mode");
-    ok(!gEdit.popup.popupOpen, "focus urlbar: popup not open yet");
-
-    let search = "mozilla";
-    EventUtils.sendString(search, window);
-    yield waitForCondition(() => gEdit.popup.popupOpen);
-
-    // XXX We should probably change the keyboard selection behavior entirely,
-    // given that it makes little to no sense, but that's a job for a later patch.
-
-    EventUtils.synthesizeKey("VK_DOWN", {}, window);
-    is(gEdit.popup.selectedIndex, -1, "key select search: no result selected");
-    is(gEdit.popup._searches.selectedIndex, 0, "key select search: first search selected");
-
-    let engines = Services.search.getVisibleEngines();
-    for (let i = 0, max = engines.length - 1; i < max; i++) {
-      is(gEdit.popup._searches.selectedIndex, i, "key select search: next index");
-      EventUtils.synthesizeKey("VK_DOWN", {}, window);
-    }
-
-    let existingValue = gEdit.value;
-    EventUtils.synthesizeKey("VK_RETURN", {}, window);
-
-    yield waitForCondition(() => gEdit.value != existingValue);
-
-    let closed = yield waitForCondition(() => !gEdit.popup.popupOpen);
-    ok(closed, "hit enter in urlbar: popup closes, page loads");
-    ok(!gEdit.isEditing, "hit enter in urlbar: not in editing mode");
-
-    let searchSubmission = gEngine.getSubmission(search, null);
-    let trimmedSubmission = gEdit.trimValue(searchSubmission.uri.spec);
-    is(gEdit.value, trimmedSubmission, "hit enter in urlbar: search conducted");
-
-    yield removeMockSearchDefault();
-  }
-});
-
-gTests.push({
-  desc: "display and select a search with touch",
-  setUp: setUp,
-  tearDown: tearDown,
-  run: function testUrlbarSearchesTouch() {
-    yield addMockSearchDefault();
-
-    sendElementTap(window, gEdit);
-    ok(gEdit.isEditing, "focus urlbar: in editing mode");
-    ok(!gEdit.popup.popupOpen, "focus urlbar: popup not open yet");
-
-    let search = "mozilla";
-    EventUtils.sendString(search, window);
-    yield waitForCondition(() => gEdit.popup.popupOpen);
-
-    sendElementTap(window, gEdit.popup._searches.lastChild);
-
-    let closed = yield waitForCondition(() => !gEdit.popup.popupOpen);
-    ok(closed, "tap search option: popup closes, page loads");
-    ok(!gEdit.isEditing, "tap search option: not in editing mode");
-
-    let searchSubmission = gEngine.getSubmission(search, null);
-    let trimmedSubmission = gEdit.trimValue(searchSubmission.uri.spec);
-    is(gEdit.value, trimmedSubmission, "tap search option: search conducted");
-  }
-});
-
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var gEdit = null;
+
+/*=============================================================================
+  Search engine mocking utilities
+=============================================================================*/
+
+var gEngine = null;
+
+const kSearchEngineName = "Foo";
+const kSearchEngineURI = chromeRoot + "res/testEngine.xml";
+
+/*
+ * addMockSearchDefault - adds a mock search engine to the top of the engine list.
+ */
+function addMockSearchDefault(aTimeoutMs) {
+  let deferred = Promise.defer();
+  let timeoutMs = aTimeoutMs || kDefaultWait;
+  let timerID = 0;
+
+  function engineAddObserver(aSubject, aTopic, aData) {
+    if (aData != "engine-added")
+      return;
+
+    gEngine = Services.search.getEngineByName(kSearchEngineName);
+    Services.obs.removeObserver(engineAddObserver, "browser-search-engine-modified");
+    clearTimeout(timerID);
+    gEngine.hidden = false;
+    ok(gEngine, "mock engine was added");
+    deferred.resolve();
+  }
+
+  if (gEngine) {
+    deferred.resolve();
+    return deferred.promise;
+  }
+
+  timerID = setTimeout(function ids_canceller() {
+    Services.obs.removeObserver(engineAddObserver, "browser-search-engine-modified");
+    deferred.reject(new Error("search add timeout"));
+  }, timeoutMs);
+
+  Services.obs.addObserver(engineAddObserver, "browser-search-engine-modified", false);
+  Services.search.addEngine(kSearchEngineURI, Ci.nsISearchEngine.DATA_XML,
+                            "data:image/x-icon,%00", false);
+  return deferred.promise;
+}
+
+/*
+ * removeMockSearchDefault - removes mock "Foo" search engine.
+ */
+
+function removeMockSearchDefault(aTimeoutMs) {
+  let deferred = Promise.defer();
+  let timeoutMs = aTimeoutMs || kDefaultWait;
+  let timerID = 0;
+
+  function engineRemoveObserver(aSubject, aTopic, aData) {
+    if (aData != "engine-removed")
+      return;
+
+    clearTimeout(timerID);
+    gEngine = null;
+    Services.obs.removeObserver(engineRemoveObserver, "browser-search-engine-modified");
+    deferred.resolve();
+  }
+
+  if (!gEngine) {
+    deferred.resolve();
+    return deferred.promise;
+  }
+
+  timerID = setTimeout(function ids_canceller() {
+    Services.obs.removeObserver(engineRemoveObserver, "browser-search-engine-modified");
+    deferred.reject(new Error("search remove timeout"));
+  }, timeoutMs);
+
+  Services.obs.addObserver(engineRemoveObserver, "browser-search-engine-modified", false);
+  Services.search.removeEngine(gEngine);
+  return deferred.promise;
+}
+
+/*=============================================================================
+  Test cases
+=============================================================================*/
+
+function test() {
+  runTests();
+}
+
+
+function setUp() {
+  if (!gEdit)
+    gEdit = document.getElementById("urlbar-edit");
+
+  yield addTab("about:start");
+  yield showNavBar();
+  yield waitForCondition(function () {
+    return StartUI.isStartPageVisible;
+  });
+}
+
+function tearDown() {
+  yield removeMockSearchDefault();
+  Browser.closeTab(Browser.selectedTab, { forceClose: true });
+  delete window.r;
+}
+
+gTests.push({
+  desc: "search engines update",
+  setUp: setUp,
+  tearDown: tearDown,
+  run: function testSearchEngine() {
+    // If the XBL hasn't initialized yet, open the popup so that it will.
+    if (gEdit.popup._searches == undefined) {
+      gEdit.openPopup();
+      gEdit.closePopup();
+    }
+
+    let numSearches = gEdit.popup._searches.itemCount;
+    function getEngineItem() {
+      return gEdit.popup._searches.querySelector("richgriditem[value="+kSearchEngineName+"]");
+    }
+
+    yield addMockSearchDefault();
+    ok(gEdit.popup._searches.itemCount == numSearches + 1, "added search engine count");
+    ok(getEngineItem(), "added search engine item");
+
+    yield removeMockSearchDefault();
+    ok(gEdit.popup._searches.itemCount == numSearches, "normal search engine count");
+    ok(!getEngineItem(), "added search engine item");
+  }
+});
+
+gTests.push({
+  desc: "display autocomplete while typing, handle enter",
+  setUp: setUp,
+  tearDown: tearDown,
+  run: function testUrlbarTyping() {
+    sendElementTap(window, gEdit);
+    ok(gEdit.isEditing, "focus urlbar: in editing mode");
+    ok(!gEdit.popup.popupOpen, "focus urlbar: popup not open yet");
+
+    EventUtils.sendString("about:blank", window);
+    let opened = yield waitForCondition(() => gEdit.popup.popupOpen);
+    ok(opened, "type in urlbar: popup opens");
+
+    EventUtils.synthesizeKey("VK_RETURN", {}, window);
+    let closed = yield waitForCondition(() => !gEdit.popup.popupOpen);
+    ok(closed, "hit enter in urlbar: popup closes, page loads");
+    ok(!gEdit.isEditing, "hit enter in urlbar: not in editing mode");
+  }
+});
+
+gTests.push({
+  desc: "display and select a search with keyboard",
+  setUp: setUp,
+  tearDown: tearDown,
+  run: function testSearchKeyboard() {
+    yield addMockSearchDefault();
+
+    sendElementTap(window, gEdit);
+    ok(gEdit.isEditing, "focus urlbar: in editing mode");
+    ok(!gEdit.popup.popupOpen, "focus urlbar: popup not open yet");
+
+    let search = "mozilla";
+    EventUtils.sendString(search, window);
+    yield waitForCondition(() => gEdit.popup.popupOpen);
+
+    // XXX We should probably change the keyboard selection behavior entirely,
+    // given that it makes little to no sense, but that's a job for a later patch.
+
+    EventUtils.synthesizeKey("VK_DOWN", {}, window);
+    is(gEdit.popup.selectedIndex, -1, "key select search: no result selected");
+    is(gEdit.popup._searches.selectedIndex, 0, "key select search: first search selected");
+
+    let engines = Services.search.getVisibleEngines();
+    for (let i = 0, max = engines.length - 1; i < max; i++) {
+      is(gEdit.popup._searches.selectedIndex, i, "key select search: next index");
+      EventUtils.synthesizeKey("VK_DOWN", {}, window);
+    }
+
+    let existingValue = gEdit.value;
+    EventUtils.synthesizeKey("VK_RETURN", {}, window);
+
+    yield waitForCondition(() => gEdit.value != existingValue);
+
+    let closed = yield waitForCondition(() => !gEdit.popup.popupOpen);
+    ok(closed, "hit enter in urlbar: popup closes, page loads");
+    ok(!gEdit.isEditing, "hit enter in urlbar: not in editing mode");
+
+    let searchSubmission = gEngine.getSubmission(search, null);
+    let trimmedSubmission = gEdit.trimValue(searchSubmission.uri.spec);
+    is(gEdit.value, trimmedSubmission, "hit enter in urlbar: search conducted");
+
+    yield removeMockSearchDefault();
+  }
+});
+
+gTests.push({
+  desc: "display and select a search with touch",
+  setUp: setUp,
+  tearDown: tearDown,
+  run: function testUrlbarSearchesTouch() {
+    yield addMockSearchDefault();
+
+    sendElementTap(window, gEdit);
+    ok(gEdit.isEditing, "focus urlbar: in editing mode");
+    ok(!gEdit.popup.popupOpen, "focus urlbar: popup not open yet");
+
+    let search = "mozilla";
+    EventUtils.sendString(search, window);
+    yield waitForCondition(() => gEdit.popup.popupOpen);
+
+    sendElementTap(window, gEdit.popup._searches.lastChild);
+
+    let closed = yield waitForCondition(() => !gEdit.popup.popupOpen);
+    ok(closed, "tap search option: popup closes, page loads");
+    ok(!gEdit.isEditing, "tap search option: not in editing mode");
+
+    let searchSubmission = gEngine.getSubmission(search, null);
+    let trimmedSubmission = gEdit.trimValue(searchSubmission.uri.spec);
+    is(gEdit.value, trimmedSubmission, "tap search option: search conducted");
+  }
+});
+
+gTests.push({
+  desc: "bug 897131 - url bar update after content tap + edge swipe",
+  setUp: setUp,
+  tearDown: tearDown,
+  run: function testUrlbarTyping() {
+    let tab = yield addTab("about:mozilla");
+
+    sendElementTap(window, gEdit);
+    ok(gEdit.isEditing, "focus urlbar: in editing mode");
+    ok(!gEdit.popup.popupOpen, "focus urlbar: popup not open yet");
+
+    EventUtils.sendString("about:blank", window);
+    let opened = yield waitForCondition(() => gEdit.popup.popupOpen);
+    ok(opened, "type in urlbar: popup opens");
+
+    sendElementTap(window, tab.browser);
+
+    let closed = yield waitForCondition(() => !gEdit.popup.popupOpen);
+    ok(closed, "autocomplete closed after tap on content");
+    ok(!ContextUI.navbarVisible, "navbar closed");
+
+    let event = document.createEvent("Events");
+    event.initEvent("MozEdgeUICompleted", true, false);
+    window.dispatchEvent(event);
+
+    ok(ContextUI.navbarVisible, "navbar visible");
+    is(gEdit.value, "about:mozilla", "url bar text refreshed");
+  }
+});
+
--- a/browser/metro/locales/en-US/chrome/browser.properties
+++ b/browser/metro/locales/en-US/chrome/browser.properties
@@ -56,44 +56,46 @@ browserForSaveLocation=Save Location
 browserForOpenLocation=Open Location
 
 # Download Manager
 downloadsUnknownSize=Unknown size
 downloadRun=Run
 downloadSave=Save
 downloadCancel=Cancel
 downloadTryAgain=Try Again
-# LOCALIZATION NOTE (downloadsShowInFiles): Semicolon-separated list of plural forms.
-# 'Files' refers to the Windows 8 file explorer
-downloadsShowInFiles=Show download in Files;Show downloads in Files
+# LOCALIZATION NOTE (downloadShowInFiles): 'Files' refers to the Windows 8 file explorer
+downloadShowInFiles=Show in Files
 
 # Alerts
 alertLinkBookmarked=Bookmark added
 alertDownloads=Downloads
 alertDownloadsStart=Downloading: %S
 alertDownloadsDone=%S has finished downloading
 # LOCALIZATION NOTE (alertDownloadsStart2): Used in a notification bar for download progress
 # #1 is the file name, #2 is the amount downloaded so far / total amount to download, and #3 is seconds remaining.
 alertDownloadsStart2=Downloading #1, #2, #3
 alertDownloadsDone2=%S has been downloaded
 alertTapToSave=Tap to save this file.
 alertDownloadsSize=Download too big
 alertDownloadsNoSpace=Not enough storage space
 # LOCALIZATION NOTE (alertDownloadSave): #1 is the file name, #2 is the file size, #3 is the file host
 alertDownloadSave=Do you want to run or save #1 (#2) from #3?
-# LOCALIZATION NOTE (alertDownloadMultiple): Semicolon-separated list of plural forms.
+# LOCALIZATION NOTE (alertDownloadMultiple): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 is the number of files, #2 is (amount downloaded so far / total amount to download) and #3 is seconds remaining
-alertDownloadMultiple=Downloading one file, #2, #3; Downloading #1 files, #2, #3
-# LOCALIZATION NOTE (alertMultipleDownloadsComplete): Semicolon-separated list of plural forms.
+alertDownloadMultiple=Downloading one file, #2, #3;Downloading #1 files, #2, #3
+# LOCALIZATION NOTE (alertMultipleDownloadsComplete): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 is the number of downloads completed
 alertMultipleDownloadsComplete=One download has been completed;#1 downloads have been completed
 alertDownloadFailed=Sorry, downloading %S failed.
 
 # Popup Blocker
-# LOCALIZATION NOTE (popupWarning.message): Semicolon-separated list of plural forms.
+# LOCALIZATION NOTE (popupWarning.message): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 is brandShortName and #2 is the number of pop-ups blocked.
 popupWarning.message=#1 prevented this site from opening a pop-up window.;#1 prevented this site from opening #2 pop-up windows.
 popupButtonAllowOnce2=Allow once
 popupButtonAlwaysAllow3=Always allow
 popupButtonNeverWarn3=Never allow
 
 # ContentPermissionsPrompt
 contentPermissions.alwaysForSite=Always for this site
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -20,16 +20,17 @@ pref("app.crashreporter.prompted", false
 pref("metro.debug.treatmouseastouch", false);
 pref("metro.debug.colorizeInputOverlay", false);
 pref("metro.debug.selection.displayRanges", false);
 pref("metro.debug.selection.dumpRanges", false);
 pref("metro.debug.selection.dumpEvents", false);
 
 // Enable off main thread compositing
 pref("layers.offmainthreadcomposition.enabled", true);
+pref("layers.async-pan-zoom.enabled", false);
 
 // Enable Microsoft TSF support by default for imes.
 pref("intl.enable_tsf_support", true);
 
 pref("general.autoScroll", true);
 pref("general.smoothScroll", true);
 pref("general.smoothScroll.durationToIntervalRatio", 200);
 pref("mousewheel.enable_pixel_scrolling", true);
@@ -352,16 +353,17 @@ pref("plugins.force.wmode", "opaque");
 // 3 - Last 4 Hours
 // 4 - Today
 pref("privacy.sanitize.timeSpan", 1);
 pref("privacy.sanitize.sanitizeOnShutdown", false);
 pref("privacy.sanitize.migrateFx3Prefs",    false);
 
 // enable geo
 pref("geo.enabled", true);
+pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_API_KEY%");
 
 // JS error console
 pref("devtools.errorconsole.enabled", false);
 
 // kinetic tweakables
 pref("browser.ui.kinetic.updateInterval", 16);
 pref("browser.ui.kinetic.exponentialC", 1400);
 pref("browser.ui.kinetic.polynomialC", 100);
--- a/browser/metro/theme/browser.css
+++ b/browser/metro/theme/browser.css
@@ -192,60 +192,70 @@ documenttab[selected] .documenttab-selec
 
 #start-container[startpage],
 #start-container[filtering] {
   display: -moz-box;
 }
 
 #start-scrollbox[input="precise"] {
   overflow-x: scroll;
-  /* Move scrollbar above toolbar,
-   *  discount padding added by .meta in #start-container */
-  margin-bottom: calc(@toolbar_height@ - @metro_spacing_normal@);
 }
 
 /* if autocomplete is set, hide both start pages,
  *  else hide the autocomplete screen */
 #start-container[filtering] > .start-page,
 #start-container:not([filtering]) > #start-autocomplete {
   visibility: collapse;
 }
-
 /* startUI sections, grids */
-#start-container .meta-section {
+#start-scrollbox > .meta-section {
   /* allot space for at least a single column */
   min-width: @grid_double_column_width@;
+  /* leave margin for horizontal scollbar */
+  margin-bottom: 30px;
 }
-
+#start-scrollbox[input="precise"] > .meta-section {
+  margin-bottom: 5px;
+}
 #start-topsites {
   /* allot space for 3 tile columns for the topsites grid */
   min-width: calc(3 * @grid_double_column_width@);
 }
+#start-scrollbox {
+  -moz-box-orient: horizontal;
+  /* Move scrollbar above toolbar,
+   *  discount padding added by .meta in #start-container */
+  margin-bottom: calc(@toolbar_height@ - @metro_spacing_normal@);
+}
 
-/* if snapped, hide the fullscreen awesome screen, if viewstate is anything
- *  other than snapped, hide the snapped awesome screen */
-#start[viewstate="snapped"],
-#snapped-start:not([viewstate="snapped"]) {
-  visibility: collapse;
+#start-container[viewstate="snapped"] #start-scrollbox {
+  -moz-box-orient: vertical;
 }
 
 /*Formatting for the limited horizontal space of snapped*/
 #start-autocomplete[viewstate="snapped"] .richgrid-item-content {
   -moz-box-orient: horizontal;
 }
-
 #start-container,
 #start-autocomplete {
   padding-left: 0;
   padding-right: 0;
 }
+#start-container[viewstate="snapped"] #start-scrollbox > .meta-section {
+  margin: 0;
+  min-width: @grid_double_column_width@;
+  -moz-box-flex: 1;
+  -moz-box-align: center;
+}
+#start-container[viewstate="snapped"] richgrid {
+  visibility: collapse;
+}
 
-#start-container[viewstate="snapped"] .meta-section {
-  margin: 0px;
-  min-width: @grid_double_column_width@;
+#start-container[viewstate="snapped"] richgrid[expanded] {
+  visibility: visible;
 }
 
 /* Browser Content Areas ==================================================== */
 
 /* Hide the browser while the start UI is visible */
 #content-viewport[startpage],
 #content-viewport[filtering] {
   visibility: collapse;
--- a/browser/metro/theme/flyoutpanel.css
+++ b/browser/metro/theme/flyoutpanel.css
@@ -62,16 +62,20 @@ flyoutpanel[visible] {
 .flyoutpanel-contents {
   border-width: 1px;
   -moz-border-start-style: solid;
   border-color: #c2c2c2;
   padding: 40px;
   width: 100%;
 }
 
+.flyoutpanel-contents[input="precise"] {
+  overflow: scroll;
+}
+
 .flyout-close-button {
   border: 0 none;
   -moz-appearance: none;
   list-style-image: url(chrome://browser/skin/images/flyout-back-button.png);
   -moz-image-region: rect(0 32px 32px 0);
 }
 
 .flyout-close-button:hover {
--- a/browser/metro/theme/platform.css
+++ b/browser/metro/theme/platform.css
@@ -648,34 +648,29 @@ arrowbox {
   background-image: url("chrome://browser/skin/images/firefox-watermark.png");
   background-repeat: no-repeat;
   background-position: center center;
   padding: @metro_spacing_normal@ @metro_spacing_xxnormal@;
   overflow: auto;
   max-width: 100%;
   width: 100%;
 }
-
 .meta-section {
   margin: 0 @metro_spacing_large@;
 }
-
 .meta-section-title {
   font-size: @metro_font_large@;
   font-weight: 100;
-  visibility: collapse;
+  display: none;
 }
-
 #start-container[viewstate="snapped"] .meta-section-title.narrow-title,
 #start-container:not([viewstate="snapped"]) .meta-section-title.wide-title {
-  visibility: visible;
+  display: block;
 }
-
 /* App bars ----------------------------------------------------------------- */
-
 appbar {
   display: block;
   position: fixed;
   bottom: 0;
   width: 100%;
   transform: translateY(100%);
   transition: transform @metro_animation_duration@ @metro_animation_easing@;
   font-size: 0;
--- a/browser/metro/theme/tiles.css
+++ b/browser/metro/theme/tiles.css
@@ -20,25 +20,23 @@ richgriditem {
 richgriditem[tiletype="thumbnail"] {
   width: @grid_double_column_width@;
   height: @grid_double_row_height@;
 }
 richgriditem[compact] {
   width: auto;
   height: @compactgrid_row_height@;
 }
-
 /*
  *****************************************************
  */
-
 richgrid {
   display: -moz-box;
+  overflow: hidden;
 }
-
 richgrid > .richgrid-grid {
   -moz-column-width: @grid_double_column_width@; /* tile width (2x unit + gutter) */
   min-width: @grid_double_column_width@; /* min 1 column */
   min-height: @grid_double_row_height@; /* 2 rows (or 1 double rows) minimum; multiple of tile_height */
   -moz-column-fill: auto; /* do not attempt to balance content between columns */
   -moz-column-gap: 0;
   -moz-column-count: auto;
   display: block;
@@ -79,16 +77,20 @@ richgriditem {
   top: 0;
   bottom: 0;
   right: 0;
   left: 20px;
   background: hsla(0,2%,98%,.95);
   padding: 8px;
 }
 
+richgriditem:not([tiletype="thumbnail"]) .tile-start-container {
+  background-image: none!important;
+}
+
 .tile-icon-box {
   display: inline-block;
   padding: 4px;
   background: #fff;
   opacity: 1.0;
 }
 
 .tile-icon-box > image {
--- a/content/base/public/nsINode.h
+++ b/content/base/public/nsINode.h
@@ -1117,22 +1117,16 @@ public:
 
   nsresult GetUserData(const nsAString& aKey, nsIVariant** aResult)
   {
     NS_IF_ADDREF(*aResult = GetUserData(aKey));
   
     return NS_OK;
   }
 
-  /**
-   * Control if GetUserData and SetUserData methods will be exposed to
-   * unprivileged content.
-   */
-  static bool IsChromeOrXBL(JSContext* aCx, JSObject* /* unused */);
-
   void LookupPrefix(const nsAString& aNamespace, nsAString& aResult);
   bool IsDefaultNamespace(const nsAString& aNamespaceURI)
   {
     nsAutoString defaultNamespace;
     LookupNamespaceURI(EmptyString(), defaultNamespace);
     return aNamespaceURI.Equals(defaultNamespace);
   }
   void LookupNamespaceURI(const nsAString& aNamespacePrefix,
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -6100,17 +6100,18 @@ nsContentUtils::SetUpChannelOwner(nsIPri
       (inherit || (aSetUpForAboutBlank && NS_IsAboutBlank(aURI)))))) {
 #ifdef DEBUG
     // Assert that aForceOwner is only set for null principals for non-srcdoc
     // loads.  (Strictly speaking not all uses of about:srcdoc would be 
     // srcdoc loads, but the URI is non-resolvable in cases where it is not).
     if (aForceOwner) {
       nsAutoCString uriStr;
       aURI->GetSpec(uriStr);
-      if(!uriStr.EqualsLiteral("about:srcdoc")) {
+      if(!uriStr.EqualsLiteral("about:srcdoc") &&
+         !uriStr.EqualsLiteral("view-source:about:srcdoc")) {
         nsCOMPtr<nsIURI> ownerURI;
         nsresult rv = aLoadingPrincipal->GetURI(getter_AddRefs(ownerURI));
         MOZ_ASSERT(NS_SUCCEEDED(rv) && SchemeIs(ownerURI, NS_NULLPRINCIPAL_SCHEME));
       }
     }
 #endif
     aChannel->SetOwner(aLoadingPrincipal);
     return true;
--- a/content/base/src/nsINode.cpp
+++ b/content/base/src/nsINode.cpp
@@ -730,25 +730,16 @@ nsINode::GetUserData(JSContext* aCx, con
 
   JS::Rooted<JS::Value> result(aCx);
   JSAutoCompartment ac(aCx, GetWrapper());
   aError = nsContentUtils::XPConnect()->VariantToJS(aCx, GetWrapper(), data,
                                                     result.address());
   return result;
 }
 
-//static
-bool
-nsINode::IsChromeOrXBL(JSContext* aCx, JSObject* /* unused */)
-{
-  JSCompartment* compartment = js::GetContextCompartment(aCx);
-  return xpc::AccessCheck::isChrome(compartment) ||
-         xpc::IsXBLScope(compartment);
-}
-
 uint16_t
 nsINode::CompareDocumentPosition(nsINode& aOtherNode) const
 {
   if (this == &aOtherNode) {
     return 0;
   }
 
   nsAutoTArray<const nsINode*, 32> parents1, parents2;
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -128,17 +128,17 @@ private:
 
 NS_IMETHODIMP
 nsAsyncInstantiateEvent::Run()
 {
   nsObjectLoadingContent *objLC =
     static_cast<nsObjectLoadingContent *>(mContent.get());
 
   // If objLC is no longer tracking this event, we've been canceled or
-  // superceded
+  // superseded
   if (objLC->mPendingInstantiateEvent != this) {
     return NS_OK;
   }
   objLC->mPendingInstantiateEvent = nullptr;
 
   return objLC->SyncStartPluginInstance();
 }
 
@@ -161,38 +161,52 @@ private:
 
 NS_IMETHODIMP
 CheckPluginStopEvent::Run()
 {
   nsObjectLoadingContent *objLC =
     static_cast<nsObjectLoadingContent *>(mContent.get());
 
   // If objLC is no longer tracking this event, we've been canceled or
-  // superceded
+  // superseded. We clear this before we finish - either by calling
+  // UnloadObject/StopPluginInstance, or directly if we took no action.
   if (objLC->mPendingCheckPluginStopEvent != this) {
     return NS_OK;
   }
-  objLC->mPendingCheckPluginStopEvent = nullptr;
 
   nsCOMPtr<nsIContent> content =
     do_QueryInterface(static_cast<nsIImageLoadingContent *>(objLC));
   if (!InActiveDocument(content)) {
     // Unload the object entirely
     LOG(("OBJLC [%p]: Unloading plugin outside of document", this));
     objLC->UnloadObject();
     return NS_OK;
   }
 
   if (!content->GetPrimaryFrame()) {
+    LOG(("OBJLC [%p]: CheckPluginStopEvent - No frame, flushing layout", this));
+    nsIDocument* currentDoc = content->GetCurrentDoc();
+    if (currentDoc) {
+      currentDoc->FlushPendingNotifications(Flush_Layout);
+      if (objLC->mPendingCheckPluginStopEvent != this ||
+          content->GetPrimaryFrame()) {
+        LOG(("OBJLC [%p]: Event superseded or frame gained during layout flush",
+             this));
+        objLC->mPendingCheckPluginStopEvent = nullptr;
+        return NS_OK;
+      }
+    }
     // Still no frame, suspend plugin. HasNewFrame will restart us when we
     // become rendered again
     LOG(("OBJLC [%p]: Stopping plugin that lost frame", this));
     // Okay to leave loaded as a plugin, but stop the unrendered instance
     objLC->StopPluginInstance();
   }
+
+  objLC->mPendingCheckPluginStopEvent = nullptr;
   return NS_OK;
 }
 
 /**
  * Helper task for firing simple events
  */
 class nsSimplePluginEvent : public nsRunnable {
 public:
@@ -885,17 +899,16 @@ nsObjectLoadingContent::OnStartRequest(n
 
   LOG(("OBJLC [%p]: Channel OnStartRequest", this));
 
   if (aRequest != mChannel || !aRequest) {
     // happens when a new load starts before the previous one got here
     return NS_BINDING_ABORTED;
   }
 
-  NS_ASSERTION(!mChannelLoaded, "mChannelLoaded set already?");
   // If we already switched to type plugin, this channel can just be passed to
   // the final listener.
   if (mType == eType_Plugin) {
     if (!mInstanceOwner) {
       // We drop mChannel when stopping plugins, so something is wrong
       NS_NOTREACHED("Opened a channel in plugin mode, but don't have a plugin");
       return NS_BINDING_ABORTED;
     }
@@ -907,16 +920,17 @@ nsObjectLoadingContent::OnStartRequest(n
     }
   }
 
   // Otherwise we should be state loading, and call LoadObject with the channel
   if (mType != eType_Loading) {
     NS_NOTREACHED("Should be type loading at this point");
     return NS_BINDING_ABORTED;
   }
+  NS_ASSERTION(!mChannelLoaded, "mChannelLoaded set already?");
   NS_ASSERTION(!mFinalListener, "mFinalListener exists already?");
 
   mChannelLoaded = true;
 
   nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
   NS_ASSERTION(chan, "Why is our request not a channel?");
 
   nsCOMPtr<nsIURI> uri;
--- a/content/base/test/test_object.html
+++ b/content/base/test/test_object.html
@@ -88,21 +88,21 @@
         return document.body.constructor.prototype.setAttribute.call(obj, attr, val);
       }
       function hasAttr(obj, attr) {
         return document.body.constructor.prototype.hasAttribute.call(obj, attr);
       }
       function removeAttr(obj, attr) {
         return document.body.constructor.prototype.removeAttribute.call(obj, attr);
       }
-      function setDisplay(obj, val) {
-        if (val)
+      function setDisplayed(obj, display) {
+        if (display)
           removeAttr(obj, 'style');
         else
-          setAttr(obj, 'style', "display none;");
+          setAttr(obj, 'style', "display: none;");
       }
       function displayed(obj) {
         // Hacky, but that's all we use style for.
         return !hasAttr(obj, 'style');
       }
       function actualType(obj, state) {
         return state.getActualType.call(obj);
       }
@@ -229,20 +229,20 @@
           forceReload(obj, state);
         },
         noChannel: function(obj, state) {
           src(obj, state, null);
           state.noChannel = true;
           state.pluginExtension = false;
         },
         displayNone: function(obj, state) {
-          setDisplay(obj, "none");
+          setDisplayed(obj, false);
         },
         displayInherit: function(obj, state) {
-          setDisplay(obj, "inherit");
+          setDisplayed(obj, true);
         }
       };
 
 
       function testObject(obj, state) {
         // If our test combination both sets noChannel but no explicit type
         // it shouldn't load ever.
         let expectedMode = state.expectedMode;
@@ -295,32 +295,33 @@
           // Images are handled by nsIImageLoadingContent so we dont track
           // their state change as they're detached and reattached. All other
           // types switch to state "loading", and are completely unloaded
           expectedMode = "loading";
         }
 
         is(actualMode, expectedMode, "check loaded mode");
 
-        // If we're a plugin, check that we spawned successfully
-        // Except for _loading...
-        let shouldSpawn = expectedMode == "plugin" && (!state.loading || state.initialPlugin);
+        // If we're a plugin, check that we spawned successfully. state.loading
+        // is set if we haven't had an event loop since applying state, in which
+        // case the plugin would not have stopped yet if it was initially a
+        // plugin.
+        let shouldBeSpawnable = expectedMode == "plugin" && displayed(obj);
+        let shouldSpawn = shouldBeSpawnable && (!state.loading || state.initialPlugin);
         let didSpawn = runningPlugin(obj, state);
         is(didSpawn, !!shouldSpawn, "check plugin spawned is " + !!shouldSpawn);
 
         // If we are a plugin, scripting should work. If we're not spawned we
         // should spawn synchronously.
-        if (expectedMode == "plugin") {
-          let scripted = false;
-          try {
-            let x = obj.getObjectValue();
-            scripted = true;
-          } catch(e) {}
-          ok(scripted, "check plugin scriptability");
-        }
+        let scripted = false;
+        try {
+          let x = obj.getObjectValue();
+          scripted = true;
+        } catch(e) {}
+        is(scripted, shouldBeSpawnable, "check plugin scriptability");
 
         // If this tag previously had other spawned plugins, make sure it
         // respawned between then and now
         if (state.oldPlugins && didSpawn) {
           let didRespawn = false;
           for (let oldp of state.oldPlugins) {
             // If this returns false or throws, it's not the same plugin
             try {
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -382,19 +382,16 @@ public:
     void DeleteVertexArray(WebGLVertexArray *vao);
     void DeleteTexture(WebGLTexture *tex);
     void DepthFunc(WebGLenum func);
     void DepthMask(WebGLboolean b);
     void DepthRange(WebGLclampf zNear, WebGLclampf zFar);
     void DetachShader(WebGLProgram *program, WebGLShader *shader);
     void Disable(WebGLenum cap);
     void DisableVertexAttribArray(WebGLuint index);
-    void DrawArrays(GLenum mode, WebGLint first, WebGLsizei count);
-    void DrawElements(WebGLenum mode, WebGLsizei count, WebGLenum type,
-                      WebGLintptr byteOffset);
     void DrawBuffers(const dom::Sequence<GLenum>& buffers);
     void Enable(WebGLenum cap);
     void EnableVertexAttribArray(WebGLuint index);
     void Flush() {
         if (!IsContextStable())
             return;
         MakeContextCurrent();
         gl->fFlush();
@@ -777,16 +774,33 @@ public:
     void VertexAttrib4fv_base(WebGLuint idx, uint32_t arrayLength,
                               const WebGLfloat* ptr);
     
     void VertexAttribPointer(WebGLuint index, WebGLint size, WebGLenum type,
                              WebGLboolean normalized, WebGLsizei stride,
                              WebGLintptr byteOffset);
     void Viewport(WebGLint x, WebGLint y, WebGLsizei width, WebGLsizei height);
 
+// -----------------------------------------------------------------------------
+// Vertices Feature (WebGLContextVertices.cpp)
+public:
+    void DrawArrays(GLenum mode, WebGLint first, WebGLsizei count);
+    void DrawArraysInstanced(GLenum mode, WebGLint first, WebGLsizei count, WebGLsizei primcount);
+    void DrawElements(WebGLenum mode, WebGLsizei count, WebGLenum type, WebGLintptr byteOffset);
+    void DrawElementsInstanced(WebGLenum mode, WebGLsizei count, WebGLenum type,
+                               WebGLintptr byteOffset, WebGLsizei primcount);
+
+private:
+    bool DrawArrays_check(WebGLint first, WebGLsizei count, WebGLsizei primcount, const char* info);
+    bool DrawElements_check(WebGLsizei count, WebGLenum type, WebGLintptr byteOffset,
+                            WebGLsizei primcount, const char* info);
+    void Draw_cleanup();
+
+// -----------------------------------------------------------------------------
+// PROTECTED
 protected:
     void SetDontKnowIfNeedFakeBlack() {
         mFakeBlackStatus = DontKnowIfNeedFakeBlack;
     }
 
     bool NeedFakeBlack();
     void BindFakeBlackTextures();
     void UnbindFakeBlackTextures();
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -45,19 +45,16 @@
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gl;
 
 static bool BaseTypeAndSizeFromUniformType(WebGLenum uType, WebGLenum *baseType, WebGLint *unitSize);
 static WebGLenum InternalFormatForFormatAndType(WebGLenum format, WebGLenum type, bool isGLES2);
 
-// For a Tegra workaround.
-static const int MAX_DRAW_CALLS_SINCE_FLUSH = 100;
-
 //
 //  WebGL API
 //
 
 inline const WebGLRectangleObject *WebGLContext::FramebufferRectangleObject() const {
     return mBoundFramebuffer ? mBoundFramebuffer->RectangleObject()
                              : static_cast<const WebGLRectangleObject*>(this);
 }
@@ -1342,197 +1339,16 @@ WebGLContext::UnbindFakeBlackTextures()
             gl->fBindTexture(LOCAL_GL_TEXTURE_CUBE_MAP, mBoundCubeMapTextures[i]->GLName());
         }
     }
 
     gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture);
 }
 
 void
-WebGLContext::DrawArrays(GLenum mode, WebGLint first, WebGLsizei count)
-{
-    if (!IsContextStable())
-        return;
-
-    if (!ValidateDrawModeEnum(mode, "drawArrays: mode"))
-        return;
-
-    if (first < 0 || count < 0)
-        return ErrorInvalidValue("drawArrays: negative first or count");
-
-    if (!ValidateStencilParamsForDrawCall())
-        return;
-
-    // If count is 0, there's nothing to do.
-    if (count == 0)
-        return;
-
-    // If there is no current program, this is silently ignored.
-    // Any checks below this depend on a program being available.
-    if (!mCurrentProgram)
-        return;
-
-    uint32_t maxAllowedCount = 0;
-    if (!ValidateBuffers(&maxAllowedCount, "drawArrays"))
-        return;
-
-    CheckedUint32 checked_firstPlusCount = CheckedUint32(first) + count;
-
-    if (!checked_firstPlusCount.isValid())
-        return ErrorInvalidOperation("drawArrays: overflow in first+count");
-
-    if (checked_firstPlusCount.value() > maxAllowedCount)
-        return ErrorInvalidOperation("drawArrays: bound vertex attribute buffers do not have sufficient size for given first and count");
-
-    MakeContextCurrent();
-
-    if (mBoundFramebuffer) {
-        if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers())
-            return ErrorInvalidFramebufferOperation("drawArrays: incomplete framebuffer");
-    }
-
-    if (!DoFakeVertexAttrib0(checked_firstPlusCount.value()))
-        return;
-    BindFakeBlackTextures();
-
-    SetupContextLossTimer();
-    gl->fDrawArrays(mode, first, count);
-
-    UndoFakeVertexAttrib0();
-    UnbindFakeBlackTextures();
-
-    if (!mBoundFramebuffer) {
-        Invalidate();
-        mShouldPresent = true;
-        mIsScreenCleared = false;
-    }
-
-    if (gl->WorkAroundDriverBugs()) {
-        if (gl->Renderer() == gl::GLContext::RendererTegra) {
-            mDrawCallsSinceLastFlush++;
-
-            if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
-                gl->fFlush();
-                mDrawCallsSinceLastFlush = 0;
-            }
-        }
-    }
-}
-
-void
-WebGLContext::DrawElements(WebGLenum mode, WebGLsizei count, WebGLenum type,
-                           WebGLintptr byteOffset)
-{
-    if (!IsContextStable())
-        return;
-
-    if (!ValidateDrawModeEnum(mode, "drawElements: mode"))
-        return;
-
-    if (count < 0 || byteOffset < 0)
-        return ErrorInvalidValue("drawElements: negative count or offset");
-
-    if (!ValidateStencilParamsForDrawCall())
-        return;
-
-    // If count is 0, there's nothing to do.
-    if (count == 0)
-        return;
-
-    CheckedUint32 checked_byteCount;
-
-    WebGLsizei first = 0;
-
-    if (type == LOCAL_GL_UNSIGNED_SHORT) {
-        checked_byteCount = 2 * CheckedUint32(count);
-        if (byteOffset % 2 != 0)
-            return ErrorInvalidOperation("drawElements: invalid byteOffset for UNSIGNED_SHORT (must be a multiple of 2)");
-        first = byteOffset / 2;
-    } else if (type == LOCAL_GL_UNSIGNED_BYTE) {
-        checked_byteCount = count;
-        first = byteOffset;
-    } else if (type == LOCAL_GL_UNSIGNED_INT && IsExtensionEnabled(OES_element_index_uint)) {
-        checked_byteCount = 4 * CheckedUint32(count);
-        if (byteOffset % 4 != 0)
-            return ErrorInvalidOperation("drawElements: invalid byteOffset for UNSIGNED_INT (must be a multiple of 4)");
-        first = byteOffset / 4;
-    } else {
-        return ErrorInvalidEnum("drawElements: type must be UNSIGNED_SHORT or UNSIGNED_BYTE");
-    }
-
-    if (!checked_byteCount.isValid())
-        return ErrorInvalidValue("drawElements: overflow in byteCount");
-
-    // If there is no current program, this is silently ignored.
-    // Any checks below this depend on a program being available.
-    if (!mCurrentProgram)
-        return;
-
-    if (!mBoundVertexArray->mBoundElementArrayBuffer)
-        return ErrorInvalidOperation("drawElements: must have element array buffer binding");
-
-    if (!mBoundVertexArray->mBoundElementArrayBuffer->ByteLength())
-        return ErrorInvalidOperation("drawElements: bound element array buffer doesn't have any data");
-
-    CheckedUint32 checked_neededByteCount = checked_byteCount + byteOffset;
-
-    if (!checked_neededByteCount.isValid())
-        return ErrorInvalidOperation("drawElements: overflow in byteOffset+byteCount");
-
-    if (checked_neededByteCount.value() > mBoundVertexArray->mBoundElementArrayBuffer->ByteLength())
-        return ErrorInvalidOperation("drawElements: bound element array buffer is too small for given count and offset");
-
-    uint32_t maxAllowedCount = 0;
-    if (!ValidateBuffers(&maxAllowedCount, "drawElements"))
-        return;
-
-    if (!maxAllowedCount ||
-        !mBoundVertexArray->mBoundElementArrayBuffer->Validate(type, maxAllowedCount - 1, first, count))
-    {
-        return ErrorInvalidOperation(
-            "DrawElements: bound vertex attribute buffers do not have sufficient "
-            "size for given indices from the bound element array");
-    }
-
-    MakeContextCurrent();
-
-    if (mBoundFramebuffer) {
-        if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers())
-            return ErrorInvalidFramebufferOperation("drawElements: incomplete framebuffer");
-    }
-
-    if (!DoFakeVertexAttrib0(maxAllowedCount))
-        return;
-    BindFakeBlackTextures();
-
-    SetupContextLossTimer();
-    gl->fDrawElements(mode, count, type, reinterpret_cast<GLvoid*>(byteOffset));
-
-    UndoFakeVertexAttrib0();
-    UnbindFakeBlackTextures();
-
-    if (!mBoundFramebuffer) {
-        Invalidate();
-        mShouldPresent = true;
-        mIsScreenCleared = false;
-    }
-
-    if (gl->WorkAroundDriverBugs()) {
-        if (gl->Renderer() == gl::GLContext::RendererTegra) {
-            mDrawCallsSinceLastFlush++;
-
-            if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
-                gl->fFlush();
-                mDrawCallsSinceLastFlush = 0;
-            }
-        }
-    }
-}
-
-void
 WebGLContext::Enable(WebGLenum cap)
 {
     if (!IsContextStable())
         return;
 
     if (!ValidateCapabilityEnum(cap, "enable"))
         return;
 
--- a/content/canvas/src/WebGLContextValidate.cpp
+++ b/content/canvas/src/WebGLContextValidate.cpp
@@ -1057,17 +1057,18 @@ WebGLContext::InitAndValidateGL()
         GenerateWarning("GL error 0x%x occurred during WebGL context initialization!", error);
         return false;
     }
 
     if (IsWebGL2() &&
         (!IsExtensionSupported(OES_vertex_array_object) ||
          !IsExtensionSupported(WEBGL_draw_buffers) ||
          !gl->IsExtensionSupported(gl::GLContext::EXT_gpu_shader4) ||
-         !gl->IsExtensionSupported(gl::GLContext::EXT_blend_minmax)
+         !gl->IsExtensionSupported(gl::GLContext::EXT_blend_minmax) ||
+         !gl->IsExtensionSupported(gl::GLContext::XXX_draw_instanced)
         ))
     {
         return false;
     }
 
     mMemoryPressureObserver
         = new WebGLMemoryPressureObserver(this);
     nsCOMPtr<nsIObserverService> observerService
new file mode 100644
--- /dev/null
+++ b/content/canvas/src/WebGLContextVertices.cpp
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "WebGLContext.h"
+#include "WebGLBuffer.h"
+#include "WebGLVertexAttribData.h"
+#include "WebGLVertexArray.h"
+#include "WebGLTexture.h"
+#include "WebGLRenderbuffer.h"
+#include "WebGLFramebuffer.h"
+
+using namespace mozilla;
+
+// For a Tegra workaround.
+static const int MAX_DRAW_CALLS_SINCE_FLUSH = 100;
+
+
+bool WebGLContext::DrawArrays_check(WebGLint first, WebGLsizei count, WebGLsizei primcount, const char* info)
+{
+    if (first < 0 || count < 0) {
+        ErrorInvalidValue("%s: negative first or count", info);
+        return false;
+    }
+
+    if (!ValidateStencilParamsForDrawCall()) {
+        return false;
+    }
+
+    // If count is 0, there's nothing to do.
+    if (count == 0 || primcount == 0) {
+        return false;
+    }
+
+    // If there is no current program, this is silently ignored.
+    // Any checks below this depend on a program being available.
+    if (!mCurrentProgram) {
+        return false;
+    }
+
+    uint32_t maxAllowedCount = 0;
+    if (!ValidateBuffers(&maxAllowedCount, info)) {
+        return false;
+    }
+
+    CheckedInt<GLsizei> checked_firstPlusCount = CheckedInt<GLsizei>(first) + count;
+
+    if (!checked_firstPlusCount.isValid()) {
+        ErrorInvalidOperation("%s: overflow in first+count", info);
+        return false;
+    }
+
+    if (uint32_t(checked_firstPlusCount.value()) > maxAllowedCount) {
+        ErrorInvalidOperation("%s: bound vertex attribute buffers do not have sufficient size for given first and count", info);
+        return false;
+    }
+
+    MakeContextCurrent();
+
+    if (mBoundFramebuffer) {
+        if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers()) {
+            ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info);
+            return false;
+        }
+    }
+
+    if (!DoFakeVertexAttrib0(checked_firstPlusCount.value())) {
+        return false;
+    }
+    BindFakeBlackTextures();
+
+    return true;
+}
+
+void
+WebGLContext::DrawArrays(GLenum mode, WebGLint first, WebGLsizei count)
+{
+    if (!IsContextStable())
+        return;
+
+    if (!ValidateDrawModeEnum(mode, "drawArrays: mode"))
+        return;
+
+    if (!DrawArrays_check(first, count, 1, "drawArrays"))
+        return;
+
+    SetupContextLossTimer();
+    gl->fDrawArrays(mode, first, count);
+
+    Draw_cleanup();
+}
+
+void
+WebGLContext::DrawArraysInstanced(GLenum mode, WebGLint first, WebGLsizei count, WebGLsizei primcount)
+{
+    if (!IsContextStable())
+        return;
+
+    if (!ValidateDrawModeEnum(mode, "drawArraysInstanced: mode"))
+        return;
+
+    if (!DrawArrays_check(first, count, primcount, "drawArraysInstanced"))
+        return;
+
+    SetupContextLossTimer();
+    gl->fDrawArraysInstanced(mode, first, count, primcount);
+
+    Draw_cleanup();
+}
+
+bool
+WebGLContext::DrawElements_check(WebGLsizei count, WebGLenum type, WebGLintptr byteOffset, WebGLsizei primcount, const char* info)
+{
+    if (count < 0 || byteOffset < 0) {
+        ErrorInvalidValue("%s: negative count or offset", info);
+        return false;
+    }
+
+    if (!ValidateStencilParamsForDrawCall()) {
+        return false;
+    }
+
+    // If count is 0, there's nothing to do.
+    if (count == 0 || primcount == 0) {
+        return false;
+    }
+
+    CheckedUint32 checked_byteCount;
+
+    WebGLsizei first = 0;
+
+    if (type == LOCAL_GL_UNSIGNED_SHORT) {
+        checked_byteCount = 2 * CheckedUint32(count);
+        if (byteOffset % 2 != 0) {
+            ErrorInvalidOperation("%s: invalid byteOffset for UNSIGNED_SHORT (must be a multiple of 2)", info);
+            return false;
+        }
+        first = byteOffset / 2;
+    }
+    else if (type == LOCAL_GL_UNSIGNED_BYTE) {
+        checked_byteCount = count;
+        first = byteOffset;
+    }
+    else if (type == LOCAL_GL_UNSIGNED_INT && IsExtensionEnabled(OES_element_index_uint)) {
+        checked_byteCount = 4 * CheckedUint32(count);
+        if (byteOffset % 4 != 0) {
+            ErrorInvalidOperation("%s: invalid byteOffset for UNSIGNED_INT (must be a multiple of 4)", info);
+            return false;
+        }
+        first = byteOffset / 4;
+    }
+    else {
+        ErrorInvalidEnum("%s: type must be UNSIGNED_SHORT or UNSIGNED_BYTE", info);
+        return false;
+    }
+
+    if (!checked_byteCount.isValid()) {
+        ErrorInvalidValue("%s: overflow in byteCount", info);
+        return false;
+    }
+
+    // If there is no current program, this is silently ignored.
+    // Any checks below this depend on a program being available.
+    if (!mCurrentProgram) {
+        return false;
+    }
+
+    if (!mBoundVertexArray->mBoundElementArrayBuffer) {
+        ErrorInvalidOperation("%s: must have element array buffer binding", info);
+        return false;
+    }
+
+    if (!mBoundVertexArray->mBoundElementArrayBuffer->ByteLength()) {
+        ErrorInvalidOperation("%s: bound element array buffer doesn't have any data", info);
+        return false;
+    }
+
+    CheckedInt<GLsizei> checked_neededByteCount = checked_byteCount.toChecked<GLsizei>() + byteOffset;
+
+    if (!checked_neededByteCount.isValid()) {
+        ErrorInvalidOperation("%s: overflow in byteOffset+byteCount", info);
+        return false;
+    }
+
+    if (uint32_t(checked_neededByteCount.value()) > mBoundVertexArray->mBoundElementArrayBuffer->ByteLength()) {
+        ErrorInvalidOperation("%s: bound element array buffer is too small for given count and offset", info);
+        return false;
+    }
+
+    uint32_t maxAllowedCount = 0;
+    if (!ValidateBuffers(&maxAllowedCount, info))
+        return false;
+
+    if (!maxAllowedCount ||
+        !mBoundVertexArray->mBoundElementArrayBuffer->Validate(type, maxAllowedCount - 1, first, count))
+    {
+        ErrorInvalidOperation(
+                              "%s: bound vertex attribute buffers do not have sufficient "
+                              "size for given indices from the bound element array", info);
+        return false;
+    }
+
+    MakeContextCurrent();
+
+    if (mBoundFramebuffer) {
+        if (!mBoundFramebuffer->CheckAndInitializeRenderbuffers()) {
+            ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info);
+            return false;
+        }
+    }
+
+    if (!DoFakeVertexAttrib0(maxAllowedCount)) {
+        return false;
+    }
+    BindFakeBlackTextures();
+
+    return true;
+}
+
+void
+WebGLContext::DrawElements(WebGLenum mode, WebGLsizei count, WebGLenum type,
+                               WebGLintptr byteOffset)
+{
+    if (!IsContextStable())
+        return;
+
+    if (!ValidateDrawModeEnum(mode, "drawElements: mode"))
+        return;
+
+    if (!DrawElements_check(count, type, byteOffset, 1, "drawElements"))
+        return;
+
+    SetupContextLossTimer();
+    gl->fDrawElements(mode, count, type, reinterpret_cast<GLvoid*>(byteOffset));
+
+    Draw_cleanup();
+}
+
+void
+WebGLContext::DrawElementsInstanced(WebGLenum mode, WebGLsizei count, WebGLenum type,
+                                        WebGLintptr byteOffset, WebGLsizei primcount)
+{
+    if (!IsContextStable())
+        return;
+
+    if (!ValidateDrawModeEnum(mode, "drawElementsInstanced: mode"))
+        return;
+
+    if (!DrawElements_check(count, type, byteOffset, 1, "drawElementsInstanced"))
+        return;
+
+    SetupContextLossTimer();
+    gl->fDrawElements(mode, count, type, reinterpret_cast<GLvoid*>(byteOffset));
+
+    Draw_cleanup();
+}
+
+void WebGLContext::Draw_cleanup()
+{
+    UndoFakeVertexAttrib0();
+    UnbindFakeBlackTextures();
+
+    if (!mBoundFramebuffer) {
+        Invalidate();
+        mShouldPresent = true;
+        mIsScreenCleared = false;
+    }
+
+    if (gl->WorkAroundDriverBugs()) {
+        if (gl->Renderer() == gl::GLContext::RendererTegra) {
+            mDrawCallsSinceLastFlush++;
+            
+            if (mDrawCallsSinceLastFlush >= MAX_DRAW_CALLS_SINCE_FLUSH) {
+                gl->fFlush();
+                mDrawCallsSinceLastFlush = 0;
+            }
+        }
+    }
+}
+
--- a/content/canvas/src/moz.build
+++ b/content/canvas/src/moz.build
@@ -32,16 +32,17 @@ if CONFIG['MOZ_WEBGL']:
         'WebGL2Context.cpp',
         'WebGLContext.cpp',
         'WebGLContextGL.cpp',
         'WebGLContextUtils.cpp',
         'WebGLContextReporter.cpp',
         'WebGLContextValidate.cpp',
         'WebGLContextFramebufferOperations.cpp',
         'WebGLContextVertexArray.cpp',
+        'WebGLContextVertices.cpp',
         'WebGLElementArrayCache.cpp',
         'WebGLExtensionBase.cpp',
         'WebGLExtensionCompressedTextureATC.cpp',
         'WebGLExtensionCompressedTexturePVRTC.cpp',
         'WebGLExtensionCompressedTextureS3TC.cpp',
         'WebGLExtensionDebugRendererInfo.cpp',
         'WebGLExtensionDepthTexture.cpp',
         'WebGLExtensionDrawBuffers.cpp',
--- a/content/xbl/src/nsXBLProtoImplField.cpp
+++ b/content/xbl/src/nsXBLProtoImplField.cpp
@@ -277,17 +277,17 @@ FieldSetterImpl(JSContext *cx, JS::CallA
   JS::Rooted<jsid> id(cx);
   if (!InstallXBLField(cx, callee, thisObj, &id, &installed)) {
     return false;
   }
 
   if (installed) {
     JS::Rooted<JS::Value> v(cx,
                             args.length() > 0 ? args[0] : JS::UndefinedValue());
-    if (!::JS_SetPropertyById(cx, thisObj, id, v.address())) {
+    if (!::JS_SetPropertyById(cx, thisObj, id, &v)) {
       return false;
     }
   }
   args.rval().setUndefined();
   return true;
 }
 
 static JSBool
--- a/content/xul/templates/src/nsXULTemplateBuilder.cpp
+++ b/content/xul/templates/src/nsXULTemplateBuilder.cpp
@@ -1399,34 +1399,34 @@ nsXULTemplateBuilder::InitHTMLTemplateRo
         // database
         JS::Rooted<JS::Value> jsdatabase(jscontext);
         rv = nsContentUtils::WrapNative(jscontext, scope, mDB,
                                         &NS_GET_IID(nsIRDFCompositeDataSource),
                                         jsdatabase.address(), getter_AddRefs(wrapper));
         NS_ENSURE_SUCCESS(rv, rv);
 
         bool ok;
-        ok = JS_SetProperty(jscontext, jselement, "database", jsdatabase.address());
+        ok = JS_SetProperty(jscontext, jselement, "database", &jsdatabase);
         NS_ASSERTION(ok, "unable to set database property");
         if (! ok)
             return NS_ERROR_FAILURE;
     }
 
     {
         // builder
         JS::Rooted<JS::Value> jsbuilder(jscontext);
         nsCOMPtr<nsIXPConnectJSObjectHolder> wrapper;
         rv = nsContentUtils::WrapNative(jscontext, jselement,
                                         static_cast<nsIXULTemplateBuilder*>(this),
                                         &NS_GET_IID(nsIXULTemplateBuilder),
                                         jsbuilder.address(), getter_AddRefs(wrapper));
         NS_ENSURE_SUCCESS(rv, rv);
 
         bool ok;
-        ok = JS_SetProperty(jscontext, jselement, "builder", jsbuilder.address());
+        ok = JS_SetProperty(jscontext, jselement, "builder", &jsbuilder);
         if (! ok)
             return NS_ERROR_FAILURE;
     }
 
     return NS_OK;
 }
 
 nsresult
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -2,27 +2,30 @@
 /* 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/. */
 
 #ifndef mozilla_dom_BindingUtils_h__
 #define mozilla_dom_BindingUtils_h__
 
+#include <algorithm>
+
 #include "jsfriendapi.h"
 #include "jswrapper.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/CallbackObject.h"
 #include "mozilla/dom/DOMJSClass.h"
 #include "mozilla/dom/DOMJSProxyHandler.h"
 #include "mozilla/dom/NonRefcountedDOMObject.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/workers/Workers.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Likely.h"
+#include "nsCycleCollector.h"
 #include "nsIXPConnect.h"
 #include "nsTraceRefcnt.h"
 #include "qsObjectHelper.h"
 #include "xpcpublic.h"
 
 #include "nsWrapperCacheInlines.h"
 
 class nsPIDOMWindow;
@@ -406,34 +409,16 @@ class Has##_name##Member {              
   typedef char no[2];                                                     \
   HAS_MEMBER_CHECK(_name);                                                \
   template<typename V> static no& Check(...);                             \
                                                                           \
 public:                                                                   \
   static bool const Value = sizeof(Check<T>(nullptr)) == sizeof(yes);     \
 };
 
-HAS_MEMBER(AddRef)
-HAS_MEMBER(Release)
-HAS_MEMBER(QueryInterface)
-
-template<typename T>
-struct IsRefCounted
-{
-  static bool const Value = HasAddRefMember<T>::Value &&
-                            HasReleaseMember<T>::Value;
-};
-
-template<typename T>
-struct IsISupports
-{
-  static bool const Value = IsRefCounted<T>::Value &&
-                            HasQueryInterfaceMember<T>::Value;
-};
-
 HAS_MEMBER(WrapObject)
 
 // HasWrapObject<T>::Value will be true if T has a WrapObject member but it's
 // not nsWrapperCache::WrapObject.
 template<typename T>
 struct HasWrapObject
 {
 private:
@@ -446,17 +431,17 @@ private:
   template <typename V> static yes& Check(...);
 
 public:
   static bool const Value = HasWrapObjectMember<T>::Value &&
                             sizeof(Check<T>(nullptr)) == sizeof(yes);
 };
 
 #ifdef DEBUG
-template <class T, bool isISupports=IsISupports<T>::Value>
+template <class T, bool isISupports=IsBaseOf<nsISupports, T>::value>
 struct
 CheckWrapperCacheCast
 {
   static bool Check()
   {
     return reinterpret_cast<uintptr_t>(
       static_cast<nsWrapperCache*>(
         reinterpret_cast<T*>(1))) == 1;
@@ -682,17 +667,17 @@ WrapNewBindingObject(JSContext* cx, JS::
   if (clasp) {
     // Some sanity asserts about our object.  Specifically:
     // 1)  If our class claims we're nsISupports, we better be nsISupports
     //     XXXbz ideally, we could assert that reinterpret_cast to nsISupports
     //     does the right thing, but I don't see a way to do it.  :(
     // 2)  If our class doesn't claim we're nsISupports we better be
     //     reinterpret_castable to nsWrapperCache.
     MOZ_ASSERT(clasp, "What happened here?");
-    MOZ_ASSERT_IF(clasp->mDOMObjectIsISupports, IsISupports<T>::Value);
+    MOZ_ASSERT_IF(clasp->mDOMObjectIsISupports, (IsBaseOf<nsISupports, T>::value));
     MOZ_ASSERT(CheckWrapperCacheCast<T>::Check());
   }
 
   // When called via XrayWrapper, we end up here while running in the
   // chrome compartment.  But the obj we have would be created in
   // whatever the content compartment is.  So at this point we need to
   // make sure it's correctly wrapped for the compartment of |scope|.
   // cx should already be in the compartment of |scope| here.
@@ -1127,17 +1112,17 @@ WrapNativeISupportsParent(JSContext* cx,
   JS::Rooted<JS::Value> v(cx);
   return XPCOMObjectToJsval(cx, scope, helper, nullptr, false, v.address()) ?
          JSVAL_TO_OBJECT(v) :
          nullptr;
 }
 
 
 // Fallback for when our parent is not a WebIDL binding object.
-template<typename T, bool isISupports=IsISupports<T>::Value >
+template<typename T, bool isISupports=IsBaseOf<nsISupports, T>::value>
 struct WrapNativeParentFallback
 {
   static inline JSObject* Wrap(JSContext* cx, JS::Handle<JSObject*> scope,
                                T* parent, nsWrapperCache* cache)
   {
     return nullptr;
   }
 };
@@ -1398,16 +1383,36 @@ InitIds(JSContext* cx, const Prefable<Sp
     ++ids;
   } while ((++prefableSpecs)->specs);
 
   return true;
 }
 
 JSBool
 QueryInterface(JSContext* cx, unsigned argc, JS::Value* vp);
+
+template <class T, bool isISupports=IsBaseOf<nsISupports, T>::value>
+struct
+WantsQueryInterface
+{
+  static bool Enabled(JSContext* aCx, JSObject* aGlobal)
+  {
+    return false;
+  }
+};
+template <class T>
+struct
+WantsQueryInterface<T, true>
+{
+  static bool Enabled(JSContext* aCx, JSObject* aGlobal)
+  {
+    return IsChromeOrXBL(aCx, aGlobal);
+  }
+};
+
 JSBool
 ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp);
 
 bool
 GetPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
                        JS::Handle<jsid> id, bool* found,
                        JS::Value* vp);
 
@@ -2110,12 +2115,185 @@ inline bool ByteStringToJsval(JSContext 
 {
     if (str.IsVoid()) {
         rval.setNull();
         return true;
     }
     return NonVoidByteStringToJsval(cx, str, rval);
 }
 
+template<class T, bool isISupports=IsBaseOf<nsISupports, T>::value>
+struct PreserveWrapperHelper
+{
+  static void PreserveWrapper(T* aObject)
+  {
+    aObject->PreserveWrapper(aObject, NS_CYCLE_COLLECTION_PARTICIPANT(T));
+  }
+};
+
+template<class T>
+struct PreserveWrapperHelper<T, true>
+{
+  static void PreserveWrapper(T* aObject)
+  {
+    aObject->PreserveWrapper(reinterpret_cast<nsISupports*>(aObject));
+  }
+};
+
+template<class T>
+void PreserveWrapper(T* aObject)
+{
+  PreserveWrapperHelper<T>::PreserveWrapper(aObject);
+}
+
+template<class T, bool isISupports=IsBaseOf<nsISupports, T>::value>
+struct CastingAssertions
+{
+  static bool ToSupportsIsCorrect(T*)
+  {
+    return true;
+  }
+  static bool ToSupportsIsOnPrimaryInheritanceChain(T*, nsWrapperCache*)
+  {
+    return true;
+  }
+};
+
+template<class T>
+struct CastingAssertions<T, true>
+{
+  static bool ToSupportsIsCorrect(T* aObject)
+  {
+    return ToSupports(aObject) ==  reinterpret_cast<nsISupports*>(aObject);
+  }
+  static bool ToSupportsIsOnPrimaryInheritanceChain(T* aObject,
+                                                    nsWrapperCache* aCache)
+  {
+    return reinterpret_cast<void*>(aObject) != aCache;
+  }
+};
+
+template<class T>
+bool
+ToSupportsIsCorrect(T* aObject)
+{
+  return CastingAssertions<T>::ToSupportsIsCorrect(aObject);
+}
+
+template<class T>
+bool
+ToSupportsIsOnPrimaryInheritanceChain(T* aObject, nsWrapperCache* aCache)
+{
+  return CastingAssertions<T>::ToSupportsIsOnPrimaryInheritanceChain(aObject,
+                                                                     aCache);
+}
+
+template<class T, template <typename> class SmartPtr,
+         bool isISupports=IsBaseOf<nsISupports, T>::value>
+class DeferredFinalizer
+{
+  typedef nsTArray<SmartPtr<T> > SmartPtrArray;
+
+  static void*
+  AppendDeferredFinalizePointer(void* aData, void* aObject)
+  {
+    SmartPtrArray* pointers = static_cast<SmartPtrArray*>(aData);
+    if (!pointers) {
+      pointers = new SmartPtrArray();
+    }
+
+    T* self = static_cast<T*>(aObject);
+
+    SmartPtr<T>* defer = pointers->AppendElement();
+    Take(*defer, self);
+    return pointers;
+  }
+  static bool
+  DeferredFinalize(uint32_t aSlice, void* aData)
+  {
+    MOZ_ASSERT(aSlice > 0, "nonsensical/useless call with aSlice == 0");
+    SmartPtrArray* pointers = static_cast<SmartPtrArray*>(aData);
+    uint32_t oldLen = pointers->Length();
+    aSlice = std::min(oldLen, aSlice);
+    uint32_t newLen = oldLen - aSlice;
+    pointers->RemoveElementsAt(newLen, aSlice);
+    if (newLen == 0) {
+      delete pointers;
+      return true;
+    }
+    return false;
+  }
+
+public:
+  static void
+  AddForDeferredFinalization(T* aObject)
+  {
+    cyclecollector::DeferredFinalize(AppendDeferredFinalizePointer,
+                                     DeferredFinalize, aObject);
+  }
+};
+
+template<class T, template <typename> class SmartPtr>
+class DeferredFinalizer<T, SmartPtr, true>
+{
+public:
+  static void
+  AddForDeferredFinalization(T* aObject)
+  {
+    cyclecollector::DeferredFinalize(reinterpret_cast<nsISupports*>(aObject));
+  }
+};
+
+template<class T, template <typename> class SmartPtr>
+static void
+AddForDeferredFinalization(T* aObject)
+{
+  DeferredFinalizer<T, SmartPtr>::AddForDeferredFinalization(aObject);
+}
+
+// This returns T's CC participant if it participates in CC or null if it
+// doesn't. This also returns null for classes that don't inherit from
+// nsISupports (QI should be used to get the participant for those).
+template<class T, bool isISupports=IsBaseOf<nsISupports, T>::value>
+class GetCCParticipant
+{
+  // Helper for GetCCParticipant for classes that participate in CC.
+  template<class U>
+  static nsCycleCollectionParticipant*
+  GetHelper(int, typename U::NS_CYCLE_COLLECTION_INNERCLASS* dummy=nullptr)
+  {
+    return T::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant();
+  }
+  // Helper for GetCCParticipant for classes that don't participate in CC.
+  template<class U>
+  static nsCycleCollectionParticipant*
+  GetHelper(double)
+  {
+    return nullptr;
+  }
+
+public:
+  static nsCycleCollectionParticipant*
+  Get()
+  {
+    // Passing int() here will try to call the GetHelper that takes an int as
+    // its firt argument. If T doesn't participate in CC then substitution for
+    // the second argument (with a default value) will fail and because of
+    // SFINAE the next best match (the variant taking a double) will be called.
+    return GetHelper<T>(int());
+  }
+};
+
+template<class T>
+class GetCCParticipant<T, true>
+{
+public:
+  static nsCycleCollectionParticipant*
+  Get()
+  {
+    return nullptr;
+  }
+};
+
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_BindingUtils_h__ */
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -39,33 +39,32 @@
 #                   in the WebIDL.
 #   * wrapperCache: True if this object is a wrapper cache.  Objects that are
 #                   not can only be returned from a limited set of methods,
 #                   cannot be prefable, and must ensure that they disallow
 #                   XPConnect wrapping.  Always false for callback interfaces.
 #                   Always true for worker descriptors for non-callback
 #                   interfaces.  Defaults to true for non-worker non-callback
 #                   descriptors.
-#   * nativeOwnership: Describes how the native object is held. 4 possible
+#   * nativeOwnership: Describes how the native object is held. 3 possible
 #                      types: worker object ('worker'), non-refcounted object
-#                      ('owned'), refcounted non-nsISupports object
-#                      ('refcounted') or nsISupports ('nsisupports').
+#                      ('owned'), refcounted object ('refcounted').
 #                      Non-refcounted objects need to inherit from
 #                      mozilla::dom::NonRefcountedDOMObject and preferably use
 #                      MOZ_COUNT_CTOR/MOZ_COUNT_DTOR in their
 #                      constructor/destructor so they participate in leak
 #                      logging.
 #                      This mostly determines how the finalizer releases the
 #                      binding's hold on the native object. For a worker object
 #                      it'll call Release, for a non-refcounted object it'll
 #                      call delete through XPConnect's deferred finalization
 #                      mechanism, for a refcounted object it'll call Release
 #                      through XPConnect's deferred finalization mechanism.
 #                      Always 'worker' for worker descriptors. Defaults to
-#                      'nsisupports'.
+#                      'refcounted'.
 #
 #   The following fields are either a string, an array (defaults to an empty
 #   array) or a dictionary with three possible keys (all, getterOnly and
 #   setterOnly) each having such an array as the value
 #
 #   * implicitJSContext - attributes and methods specified in the .webidl file
 #                         that require a JSContext as the first argument
 #   * resultNotAddRefed - attributes and methods specified in the .webidl file
@@ -100,41 +99,32 @@ DOMInterfaces = {
 
 'AudioChannelManager': {
     'nativeType': 'mozilla::dom::system::AudioChannelManager',
     'headerFile': 'AudioChannelManager.h'
 },
 
 'AudioContext': {
     'implicitJSContext': [ 'createBuffer' ],
-    'nativeOwnership': 'refcounted',
     'resultNotAddRefed': [ 'destination', 'listener' ],
 },
 
 'AudioBufferSourceNode': {
     'implicitJSContext': [ 'buffer' ],
     'resultNotAddRefed': [ 'playbackRate' ],
 },
 
-'AudioListener' : {
-    'nativeOwnership': 'refcounted'
-},
-
 'AudioNode' : {
     'concrete': False,
     'binaryNames': {
         'channelCountMode': 'channelCountModeValue',
         'channelInterpretation': 'channelInterpretationValue',
     },
 },
 
-'AudioParam' : {
-    'nativeOwnership': 'refcounted'
-},
-
 'AudioProcessingEvent' : {
     'resultNotAddRefed': [ 'inputBuffer', 'outputBuffer' ],
 },
 
 'BeforeUnloadEvent': {
     'nativeType': 'nsDOMBeforeUnloadEvent',
 },
 
@@ -741,17 +731,16 @@ DOMInterfaces = {
 
 'OfflineAudioCompletionEvent': {
     'resultNotAddRefed': [ 'renderedBuffer' ],
 },
 
 'OfflineAudioContext': {
     'nativeType': 'mozilla::dom::AudioContext',
     'implicitJSContext': [ 'createBuffer' ],
-    'nativeOwnership': 'refcounted',
     'resultNotAddRefed': [ 'destination', 'listener' ],
 },
 
 'OfflineResourceList': {
     'nativeType': 'nsDOMOfflineResourceList',
 },
 
 'PaintRequest': {
@@ -771,24 +760,22 @@ DOMInterfaces = {
 
 'Performance': {
     'nativeType': 'nsPerformance',
     'resultNotAddRefed': [ 'timing', 'navigation' ]
 },
 
 'PerformanceTiming': {
     'nativeType': 'nsPerformanceTiming',
-    'headerFile': 'nsPerformance.h',
-    'nativeOwnership': 'refcounted'
+    'headerFile': 'nsPerformance.h'
 },
 
 'PerformanceNavigation': {
     'nativeType': 'nsPerformanceNavigation',
-    'headerFile': 'nsPerformance.h',
-    'nativeOwnership': 'refcounted'
+    'headerFile': 'nsPerformance.h'
 },
 
 'PeriodicWave' : {
     'nativeOwnership': 'refcounted'
 },
 
 'Plugin': {
     'headerFile' : 'nsPluginArray.h',
@@ -827,17 +814,16 @@ DOMInterfaces = {
 },
 
 'Rect': {
     'nativeType': 'nsDOMCSSRect',
     'resultNotAddRefed': [ 'top', 'right', 'bottom', 'left' ]
 },
 
 'RGBColor': {
-    'nativeOwnership': 'refcounted',
     'nativeType': 'nsDOMCSSRGBColor',
     'resultNotAddRefed': [ 'alpha', 'blue', 'green', 'red' ]
 },
 
 'Screen': {
     'nativeType': 'nsScreen',
 },
 
@@ -884,24 +870,16 @@ DOMInterfaces = {
     'headerFile': 'DOMSVGAnimatedNumberList.h'
 },
 
 'SVGAnimatedPreserveAspectRatio': {
     'nativeType': 'mozilla::dom::DOMSVGAnimatedPreserveAspectRatio',
     'headerFile': 'SVGAnimatedPreserveAspectRatio.h'
 },
 
-'SVGAnimatedRect' : {
-    'nativeOwnership': 'refcounted'
-},
-
-'SVGAnimatedTransformList': {
-    'nativeOwnership': 'refcounted',
-},
-
 'SVGAnimationElement': {
     'resultNotAddRefed': ['targetElement'],
     'concrete': False
 },
 
 'SVGComponentTransferFunctionElement': {
     'concrete': False,
 },
@@ -940,20 +918,16 @@ DOMInterfaces = {
     'nativeType': 'mozilla::DOMSVGLengthList',
     'headerFile': 'DOMSVGLengthList.h'
 },
 
 'SVGLinearGradientElement': {
     'headerFile': 'mozilla/dom/SVGGradientElement.h',
 },
 
-'SVGMatrix' : {
-    'nativeOwnership': 'refcounted'
-},
-
 'SVGNumberList': {
     'nativeType': 'mozilla::DOMSVGNumberList',
     'headerFile': 'DOMSVGNumberList.h'
 },
 
 'SVGPathSeg': {
     'nativeType': 'mozilla::DOMSVGPathSeg',
     'headerFile': 'DOMSVGPathSeg.h',
@@ -1087,17 +1061,16 @@ DOMInterfaces = {
     'concrete': False
 },
 
 'SVGTextPositioningElement': {
     'concrete': False
 },
 
 'SVGTransform': {
-    'nativeOwnership': 'refcounted',
     'resultNotAddRefed': [ 'matrix' ]
 },
 
 'SVGTransformList': {
     'nativeType': 'mozilla::DOMSVGTransformList',
     'headerFile': 'DOMSVGTransformList.h'
 },
 
@@ -1121,25 +1094,21 @@ DOMInterfaces = {
 'Text': {
     # Total hack to allow binding code to realize that nsTextNode can
     # in fact be cast to Text.
     'headerFile': 'nsTextNode.h',
 },
 
 'TextDecoder': [
 {
-    'nativeOwnership': 'refcounted',
-},
-{
     'workers': True,
 }],
 
 'TextEncoder': [
 {
-    'nativeOwnership': 'refcounted',
     'implicitJSContext': [ 'encode' ],
 },
 {
     'workers': True,
     'implicitJSContext': [ 'encode' ],
 }],
 
 'TextMetrics': {
@@ -1188,23 +1157,16 @@ DOMInterfaces = {
 'URL' : [{
     'concrete': False,
 },
 {
     'implicitJSContext': [ 'createObjectURL', 'revokeObjectURL' ],
     'workers': True,
 }],
 
-'VideoPlaybackQuality': {
-   'nativeOwnership': 'refcounted',
-},
-
-'VideoStreamTrack': {
-},
-
 'WebGLActiveInfo': {
    'nativeType': 'mozilla::WebGLActiveInfo',
    'headerFile': 'WebGLContext.h',
    'wrapperCache': False
 },
 
 'WebGLBuffer': {
    'nativeType': 'mozilla::WebGLBuffer',
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -132,29 +132,29 @@ def NativePropertyHooks(descriptor):
 def DOMClass(descriptor):
         protoList = ['prototypes::id::' + proto for proto in descriptor.prototypeChain]
         # Pad out the list to the right length with _ID_Count so we
         # guarantee that all the lists are the same length.  _ID_Count
         # is never the ID of any prototype, so it's safe to use as
         # padding.
         protoList.extend(['prototypes::id::_ID_Count'] * (descriptor.config.maxProtoChainLength - len(protoList)))
         prototypeChainString = ', '.join(protoList)
-        if descriptor.workers or descriptor.nativeOwnership != 'refcounted':
+        if descriptor.workers:
             participant = "nullptr"
         else:
-            participant = "NS_CYCLE_COLLECTION_PARTICIPANT(%s)" % descriptor.nativeType
+            participant = "GetCCParticipant<%s>::Get()" % descriptor.nativeType
         getParentObject = "GetParentObject<%s>::Get" % descriptor.nativeType
         return """{
   { %s },
-  %s,
+  IsBaseOf<nsISupports, %s >::value,
   %s,
   %s,
   GetProtoObject,
   %s
-}""" % (prototypeChainString, toStringBool(descriptor.nativeOwnership == 'nsisupports'),
+}""" % (prototypeChainString, descriptor.nativeType,
         NativePropertyHooks(descriptor),
         getParentObject,
         participant)
 
 class CGDOMJSClass(CGThing):
     """
     Generate a DOMJSClass for a given descriptor
     """
@@ -916,24 +916,19 @@ class CGAbstractClassHook(CGAbstractStat
     Meant for implementing JSClass hooks, like Finalize or Trace. Does very raw
     'this' unwrapping as it assumes that the unwrapped type is always known.
     """
     def __init__(self, descriptor, name, returnType, args):
         CGAbstractStaticMethod.__init__(self, descriptor, name, returnType,
                                         args)
 
     def definition_body_prologue(self):
-        if self.descriptor.nativeOwnership == 'nsisupports':
-            assertion = ('  MOZ_STATIC_ASSERT((IsBaseOf<nsISupports, %s>::value), '
-                         '"Must be an nsISupports class");') % self.descriptor.nativeType
-        else:
-            assertion = ''
-        return """%s
+        return """
   %s* self = UnwrapDOMObject<%s>(obj);
-""" % (assertion, self.descriptor.nativeType, self.descriptor.nativeType)
+""" % (self.descriptor.nativeType, self.descriptor.nativeType)
 
     def definition_body(self):
         return self.definition_body_prologue() + self.generate_code()
 
     def generate_code(self):
         # Override me
         assert(False)
 
@@ -944,87 +939,43 @@ class CGAddPropertyHook(CGAbstractClassH
     def __init__(self, descriptor):
         args = [Argument('JSContext*', 'cx'), Argument('JS::Handle<JSObject*>', 'obj'),
                 Argument('JS::Handle<jsid>', 'id'), Argument('JS::MutableHandle<JS::Value>', 'vp')]
         CGAbstractClassHook.__init__(self, descriptor, ADDPROPERTY_HOOK_NAME,
                                      'JSBool', args)
 
     def generate_code(self):
         assert not self.descriptor.workers and self.descriptor.wrapperCache
-        if self.descriptor.nativeOwnership == 'nsisupports':
-            preserveArgs = "reinterpret_cast<nsISupports*>(self)"
-        else:
-            preserveArgs = "self, NS_CYCLE_COLLECTION_PARTICIPANT(%s)" % self.descriptor.nativeType
         return ("  // We don't want to preserve if we don't have a wrapper.\n"
                 "  if (self->GetWrapperPreserveColor()) {\n"
-                "    self->PreserveWrapper(%s);\n"
+                "    PreserveWrapper(self);\n"
                 "  }\n"
-                "  return true;" % preserveArgs)
+                "  return true;")
 
 def DeferredFinalizeSmartPtr(descriptor):
     if descriptor.nativeOwnership == 'owned':
-        smartPtr = 'nsAutoPtr<%s>'
+        smartPtr = 'nsAutoPtr'
     else:
-        assert descriptor.nativeOwnership == 'refcounted'
-        smartPtr = 'nsRefPtr<%s>'
-    return smartPtr % descriptor.nativeType
-
-class CGAppendDeferredFinalizePointer(CGAbstractStaticMethod):
-    def __init__(self, descriptor):
-        CGAbstractStaticMethod.__init__(self, descriptor, "AppendDeferredFinalizePointer", "void*", [Argument('void*', 'data'), Argument('void*', 'thing')])
-
-    def definition_body(self):
-        smartPtr = DeferredFinalizeSmartPtr(self.descriptor)
-        return """  nsTArray<%(smartPtr)s >* pointers = static_cast<nsTArray<%(smartPtr)s >*>(data);
-  if (!pointers) {
-    pointers = new nsTArray<%(smartPtr)s >();
-  }
-
-  %(nativeType)s* self = static_cast<%(nativeType)s*>(thing);
-
-  %(smartPtr)s* defer = pointers->AppendElement();
-  Take(*defer, self);
-  return pointers;""" % { 'smartPtr': smartPtr, 'nativeType': self.descriptor.nativeType }
-
-class CGDeferredFinalize(CGAbstractStaticMethod):
-    def __init__(self, descriptor):
-        CGAbstractStaticMethod.__init__(self, descriptor, "DeferredFinalize", "bool", [Argument('uint32_t', 'slice'), Argument('void*', 'data')])
-
-    def definition_body(self):
-        smartPtr = DeferredFinalizeSmartPtr(self.descriptor)
-        return """  MOZ_ASSERT(slice > 0, "nonsensical/useless call with slice == 0");
-  nsTArray<%(smartPtr)s >* pointers = static_cast<nsTArray<%(smartPtr)s >*>(data);
-  uint32_t oldLen = pointers->Length();
-  slice = std::min(oldLen, slice);
-  uint32_t newLen = oldLen - slice;
-  pointers->RemoveElementsAt(newLen, slice);
-  if (newLen == 0) {
-    delete pointers;
-    return true;
-  }
-  return false;""" % { 'smartPtr': smartPtr }
+        smartPtr = 'nsRefPtr'
+    return smartPtr
 
 def finalizeHook(descriptor, hookName, context):
     if descriptor.customFinalize:
         finalize = "self->%s(%s);" % (hookName, context)
     else:
         finalize = "JSBindingFinalized<%s>::Finalized(self);\n" % descriptor.nativeType
         if descriptor.wrapperCache:
             finalize += "ClearWrapper(self, self);\n"
         if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
             finalize += "self->mExpandoAndGeneration.expando = JS::UndefinedValue();\n"
         if descriptor.workers:
             finalize += "self->Release();"
-        elif descriptor.nativeOwnership == 'nsisupports':
-            finalize += "nsContentUtils::DeferredFinalize(reinterpret_cast<nsISupports*>(self));"
-        else:
-            finalize += """nsContentUtils::DeferredFinalize(AppendDeferredFinalizePointer,
-                                 DeferredFinalize,
-                                 self);
-"""
+        else:
+            finalize += ("AddForDeferredFinalization<%s, %s >(self);" %
+                (descriptor.nativeType, DeferredFinalizeSmartPtr(descriptor)))
     return CGIfWrapper(CGGeneric(finalize), "self")
 
 class CGClassFinalizeHook(CGAbstractClassHook):
     """
     A hook for finalize, used to release our native object.
     """
     def __init__(self, descriptor):
         args = [Argument('JSFreeOp*', 'fop'), Argument('JSObject*', 'obj')]
@@ -1187,40 +1138,43 @@ class CGClassHasInstanceHook(CGAbstractS
         if not NeedsGeneratedHasInstance(self.descriptor):
             return ""
         return CGAbstractStaticMethod.define(self)
 
     def definition_body(self):
         return self.generate_code()
 
     def generate_code(self):
-        assert self.descriptor.nativeOwnership == 'nsisupports'
         header = """
   if (!vp.isObject()) {
     *bp = false;
     return true;
   }
 
   JS::Rooted<JSObject*> instance(cx, &vp.toObject());
   """
         if self.descriptor.interface.hasInterfacePrototypeObject():
             return header + """
+  MOZ_STATIC_ASSERT((IsBaseOf<nsISupports, %s>::value),
+                    "HasInstance only works for nsISupports-based classes.");
+
   bool ok = InterfaceHasInstance(cx, obj, instance, bp);
   if (!ok || *bp) {
     return ok;
   }
 
   // FIXME Limit this to chrome by checking xpc::AccessCheck::isChrome(obj).
   nsISupports* native =
     nsContentUtils::XPConnect()->GetNativeOfWrapper(cx,
                                                     js::UncheckedUnwrap(instance));
   nsCOMPtr<nsIDOM%s> qiResult = do_QueryInterface(native);
   *bp = !!qiResult;
   return true;
-         """ % self.descriptor.interface.identifier.name
+         """ % (self.descriptor.nativeType,
+                self.descriptor.interface.identifier.name)
 
         hasInstanceCode = """
   const DOMClass* domClass = GetDOMClass(js::UncheckedUnwrap(instance));
   *bp = false;
   if (!domClass) {
     // Not a DOM object, so certainly not an instance of this interface
     return true;
   }
@@ -1414,17 +1368,17 @@ def requiresQueryInterfaceMethod(descrip
     # interfaces that are concrete and all of whose ancestors are abstract.
     def allAncestorsAbstract(iface):
         if not iface.parent:
             return True
         desc = descriptor.getDescriptor(iface.parent.identifier.name)
         if desc.concrete:
             return False
         return allAncestorsAbstract(iface.parent)
-    return (descriptor.nativeOwnership == 'nsisupports' and
+    return (not descriptor.workers and
             descriptor.interface.hasInterfacePrototypeObject() and
             descriptor.concrete and
             allAncestorsAbstract(descriptor.interface))
 
 class MethodDefiner(PropertyDefiner):
     """
     A class for defining methods on a prototype object.
     """
@@ -1460,23 +1414,22 @@ class MethodDefiner(PropertyDefiner):
             self.regular.append({"name": 'iterator',
                                  "methodInfo": False,
                                  "nativeName": "JS_ArrayIterator",
                                  "length": 0,
                                  "flags": "JSPROP_ENUMERATE",
                                  "condition": MemberCondition(None, None) })
 
         if not static and requiresQueryInterfaceMethod(descriptor):
+            condition = "WantsQueryInterface<%s>::Enabled" % descriptor.nativeType
             self.regular.append({"name": 'QueryInterface',
                                  "methodInfo": False,
                                  "length": 1,
                                  "flags": "0",
-                                 "condition":
-                                     MemberCondition(None,
-                                                     "nsINode::IsChromeOrXBL") })
+                                 "condition": MemberCondition(None, condition) })
 
         if not static:
             stringifier = descriptor.operations['Stringifier']
             if stringifier:
                 toStringDesc = { "name": "toString",
                                  "nativeName": stringifier.identifier.name,
                                  "length": 0,
                                  "flags": "JSPROP_ENUMERATE",
@@ -2067,21 +2020,20 @@ def CreateBindingJSObject(descriptor, pr
   if (!obj) {
     return nullptr;
   }
 
   js::SetReservedSlot(obj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL(aObject));
 """
     create = objDecl + create
 
-    if descriptor.nativeOwnership in ['refcounted', 'nsisupports']:
+    if descriptor.nativeOwnership == 'refcounted':
         create += """  NS_ADDREF(aObject);
 """
     else:
-        assert descriptor.nativeOwnership == 'owned'
         create += """  // Make sure the native objects inherit from NonRefcountedDOMObject so that we
   // log their ctor and dtor.
   MustInheritFromNonRefcountedDOMObject(aObject);
   *aTookOwnership = true;
 """
     return create % parent
 
 def GetAccessCheck(descriptor, object):
@@ -2150,20 +2102,18 @@ def AssertInheritanceChain(descriptor):
     iface = descriptor.interface
     while iface:
         desc = descriptor.getDescriptor(iface.identifier.name)
         asserts += (
             "  MOZ_ASSERT(static_cast<%s*>(aObject) == \n"
             "             reinterpret_cast<%s*>(aObject));\n" %
             (desc.nativeType, desc.nativeType))
         iface = iface.parent
-    if descriptor.nativeOwnership == 'nsisupports':
-        asserts += (
-            "  MOZ_ASSERT(ToSupports(aObject) == \n"
-            "             reinterpret_cast<nsISupports*>(aObject));\n")
+    asserts += (
+        "  MOZ_ASSERT(ToSupportsIsCorrect(aObject));\n")
     return asserts
 
 class CGWrapWithCacheMethod(CGAbstractMethod):
     """
     Create a wrapper JSObject for a given native that implements nsWrapperCache.
 
     properties should be a PropertyArrays instance.
     """
@@ -2175,22 +2125,19 @@ class CGWrapWithCacheMethod(CGAbstractMe
                 Argument('nsWrapperCache*', 'aCache')]
         CGAbstractMethod.__init__(self, descriptor, 'Wrap', 'JSObject*', args)
         self.properties = properties
 
     def definition_body(self):
         if self.descriptor.workers:
             return """  return aObject->GetJSObject();"""
 
-        if self.descriptor.nativeOwnership == 'nsisupports':
-            assertISupportsInheritance = (
-                '  MOZ_ASSERT(reinterpret_cast<void*>(aObject) != aCache,\n'
-                '             "nsISupports must be on our primary inheritance chain");\n')
-        else:
-            assertISupportsInheritance = ""
+        assertISupportsInheritance = (
+            '  MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache),\n'
+            '             "nsISupports must be on our primary inheritance chain");\n')
         return """%s
 %s
   JS::Rooted<JSObject*> parent(aCx,
     GetRealParentObject(aObject,
                         WrapNativeParent(aCx, aScope, aObject->GetParentObject())));
   if (!parent) {
     return nullptr;
   }
@@ -3777,17 +3724,17 @@ class CGArgumentConverter(CGThing):
             "argc" : "args.length()"
             }
         self.replacementVariables = {
             "declName" : "arg%d" % index,
             "holderName" : ("arg%d" % index) + "_holder",
             "obj" : "obj"
             }
         self.replacementVariables["val"] = string.Template(
-            "args.handleAt(${index})"
+            "args[${index}]"
             ).substitute(replacer)
         self.replacementVariables["mutableVal"] = self.replacementVariables["val"]
         if argument.treatUndefinedAs == "Missing":
             haveValueCheck = "args.hasDefined(${index})"
         else:
             haveValueCheck = "${index} < args.length()"
         haveValueCheck = string.Template(haveValueCheck).substitute(replacer)
         self.replacementVariables["haveValue"] = haveValueCheck
@@ -3846,17 +3793,17 @@ class CGArgumentConverter(CGThing):
   if (!${declName}.SetCapacity(${argc} - ${index})) {
     JS_ReportOutOfMemory(cx);
     return false;
   }
   for (uint32_t variadicArg = ${index}; variadicArg < ${argc}; ++variadicArg) {
     ${elemType}& slot = *${declName}.AppendElement();
 """).substitute(replacer)
 
-        val = string.Template("args.handleAt(variadicArg)").substitute(replacer)
+        val = string.Template("args[variadicArg]").substitute(replacer)
         variadicConversion += CGIndenter(CGGeneric(
                 string.Template(typeConversion.template).substitute(
                     {
                         "val" : val,
                         "mutableVal" : val,
                         "declName" : "slot",
                         # We only need holderName here to handle isExternal()
                         # interfaces, which use an internal holder for the
@@ -4915,17 +4862,17 @@ class CGMethodCall(CGThing):
             # they all have the same types up to that point; just use
             # possibleSignatures[0]
             caseBody = [ CGArgumentConverter(possibleSignatures[0][1][i],
                                              i, descriptor,
                                              argDesc % (i + 1)) for i in
                          range(0, distinguishingIndex) ]
 
             # Select the right overload from our set.
-            distinguishingArg = "args.handleAt(%d)" % distinguishingIndex
+            distinguishingArg = "args[%d]" % distinguishingIndex
 
             def pickFirstSignature(condition, filterLambda):
                 sigs = filter(filterLambda, possibleSignatures)
                 assert len(sigs) < 2
                 if len(sigs) > 0:
                     if condition is None:
                         caseBody.append(
                             getPerSignatureCall(sigs[0], distinguishingIndex))
@@ -5534,17 +5481,17 @@ class CGSpecializedForwardingSetter(CGSp
 if (!JS_GetProperty(cx, obj, "%s", v.address())) {
   return false;
 }
 
 if (!v.isObject()) {
   return ThrowErrorMessage(cx, MSG_NOT_OBJECT, "%s.%s");
 }
 
-return JS_SetProperty(cx, &v.toObject(), "%s", args.handleAt(0).address());""" % (attrName, self.descriptor.interface.identifier.name, attrName, forwardToAttrName))).define()
+return JS_SetProperty(cx, &v.toObject(), "%s", args[0]);""" % (attrName, self.descriptor.interface.identifier.name, attrName, forwardToAttrName))).define()
 
 def memberIsCreator(member):
     return member.getExtendedAttribute("Creator") is not None
 
 class CGMemberJITInfo(CGThing):
     """
     A class for generating the JITInfo for a property that points to
     our specialized getter and setter.
@@ -7646,32 +7593,27 @@ class CGDescriptor(CGThing):
         if hasSetter: cgThings.append(CGGenericSetter(descriptor))
         if hasLenientSetter: cgThings.append(CGGenericSetter(descriptor,
                                                              lenientThis=True))
 
         if descriptor.interface.getNavigatorProperty():
             cgThings.append(CGConstructNavigatorObjectHelper(descriptor))
             cgThings.append(CGConstructNavigatorObject(descriptor))
 
-        if descriptor.concrete:
-            if descriptor.nativeOwnership == 'owned' or descriptor.nativeOwnership == 'refcounted':
-                cgThings.append(CGAppendDeferredFinalizePointer(descriptor))
-                cgThings.append(CGDeferredFinalize(descriptor))
-
-            if not descriptor.proxy:
-                if not descriptor.workers and descriptor.wrapperCache:
-                    cgThings.append(CGAddPropertyHook(descriptor))
-
-                # Always have a finalize hook, regardless of whether the class
-                # wants a custom hook.
-                cgThings.append(CGClassFinalizeHook(descriptor))
-
-                # Only generate a trace hook if the class wants a custom hook.
-                if (descriptor.customTrace):
-                    cgThings.append(CGClassTraceHook(descriptor))
+        if descriptor.concrete and not descriptor.proxy:
+            if not descriptor.workers and descriptor.wrapperCache:
+                cgThings.append(CGAddPropertyHook(descriptor))
+
+            # Always have a finalize hook, regardless of whether the class
+            # wants a custom hook.
+            cgThings.append(CGClassFinalizeHook(descriptor))
+
+            # Only generate a trace hook if the class wants a custom hook.
+            if (descriptor.customTrace):
+                cgThings.append(CGClassTraceHook(descriptor))
 
         properties = PropertyArrays(descriptor)
         cgThings.append(CGGeneric(define=str(properties)))
         cgThings.append(CGNativeProperties(descriptor, properties))
 
         # Set up our Xray callbacks as needed.  Note that we don't need to do
         # it in workers.
         if not descriptor.workers and descriptor.concrete and descriptor.proxy:
@@ -7724,19 +7666,21 @@ class CGDescriptor(CGThing):
                 cgThings.append(CGPrefEnabledNative(descriptor))
             elif havePref is not None:
                 cgThings.append(CGPrefEnabled(descriptor))
             elif haveChromeOnly is not None:
                 cgThings.append(CGConstructorEnabledChromeOnly(descriptor))
 
         if descriptor.concrete:
             if descriptor.proxy:
-                if descriptor.nativeOwnership != 'nsisupports':
-                    raise TypeError("We don't support non-nsISupports native classes for "
-                                    "proxy-based bindings yet (" + descriptor.name + ")")
+                cgThings.append(CGGeneric("""MOZ_STATIC_ASSERT((IsBaseOf<nsISupports, %s >::value),
+                  "We don't support non-nsISupports native classes for "
+                  "proxy-based bindings yet");
+
+""" % descriptor.nativeType))
                 if not descriptor.wrapperCache:
                     raise TypeError("We need a wrappercache to support expandos for proxy-based "
                                     "bindings (" + descriptor.name + ")")
                 cgThings.append(CGProxyIsProxy(descriptor))
                 cgThings.append(CGProxyUnwrap(descriptor))
                 cgThings.append(CGDOMJSProxyHandlerDOMClass(descriptor))
                 cgThings.append(CGDOMJSProxyHandler(descriptor))
                 cgThings.append(CGIsMethod(descriptor))
@@ -9982,17 +9926,17 @@ class CallbackSetter(CallbackMember):
     def getRvalDecl(self):
         # We don't need an rval
         return ""
 
     def getCall(self):
         replacements = {
             "errorReturn" : self.getDefaultRetval(),
             "attrName": self.attrName,
-            "argv": "argv.begin()",
+            "argv": "argv.handleAt(0)",
             }
         return string.Template(
             'MOZ_ASSERT(argv.length() == 1);\n'
             'if (!JS_SetProperty(cx, mCallback, "${attrName}", ${argv})) {\n'
             '  aRv.Throw(NS_ERROR_UNEXPECTED);\n'
             '  return${errorReturn};\n'
             '}\n').substitute(replacements)
 
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -357,18 +357,18 @@ class Descriptor(DescriptorProvider):
 
         if self.workers:
             if desc.get('nativeOwnership', 'worker') != 'worker':
                 raise TypeError("Worker descriptor for %s should have 'worker' "
                                 "as value for nativeOwnership" %
                                 self.interface.identifier.name)
             self.nativeOwnership = "worker"
         else:
-            self.nativeOwnership = desc.get('nativeOwnership', 'nsisupports')
-            if not self.nativeOwnership in ['owned', 'refcounted', 'nsisupports']:
+            self.nativeOwnership = desc.get('nativeOwnership', 'refcounted')
+            if not self.nativeOwnership in ['owned', 'refcounted']:
                 raise TypeError("Descriptor for %s has unrecognized value (%s) "
                                 "for nativeOwnership" %
                                 (self.interface.identifier.name, self.nativeOwnership))
         self.customTrace = desc.get('customTrace', self.workers)
         self.customFinalize = desc.get('customFinalize', self.workers)
         self.wrapperCache = (not self.interface.isCallback() and
                              (self.workers or
                               (self.nativeOwnership != 'owned' and
--- a/dom/bluetooth/BluetoothUtils.cpp
+++ b/dom/bluetooth/BluetoothUtils.cpp
@@ -18,41 +18,41 @@
 #include "nsString.h"
 #include "nsTArray.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 bool
 SetJsObject(JSContext* aContext,
             const BluetoothValue& aValue,
-            JSObject* aObj)
+            JS::Handle<JSObject*> aObj)
 {
   MOZ_ASSERT(aContext && aObj);
 
   if (aValue.type() != BluetoothValue::TArrayOfBluetoothNamedValue) {
     NS_WARNING("SetJsObject: Invalid parameter type");
     return false;
   }
 
   const nsTArray<BluetoothNamedValue>& arr =
     aValue.get_ArrayOfBluetoothNamedValue();
 
   for (uint32_t i = 0; i < arr.Length(); i++) {
-    JS::Value val;
+    JS::Rooted<JS::Value> val(aContext);
     const BluetoothValue& v = arr[i].value();
-    JSString* jsData;
 
     switch(v.type()) {
-      case BluetoothValue::TnsString:
-        jsData = JS_NewUCStringCopyN(aContext,
+       case BluetoothValue::TnsString: {
+        JSString* jsData = JS_NewUCStringCopyN(aContext,
                                      v.get_nsString().BeginReading(),
                                      v.get_nsString().Length());
         NS_ENSURE_TRUE(jsData, false);
         val = STRING_TO_JSVAL(jsData);
         break;
+      }
       case BluetoothValue::Tuint32_t:
         val = INT_TO_JSVAL(v.get_uint32_t());
         break;
       case BluetoothValue::Tbool:
         val = BOOLEAN_TO_JSVAL(v.get_bool());
         break;
       default:
         NS_WARNING("SetJsObject: Parameter is not handled");
@@ -104,17 +104,17 @@ GetAddressFromObjectPath(const nsAString
 bool
 BroadcastSystemMessage(const nsAString& aType,
                        const InfallibleTArray<BluetoothNamedValue>& aData)
 {
   mozilla::AutoSafeJSContext cx;
   NS_ASSERTION(!::JS_IsExceptionPending(cx),
       "Shouldn't get here when an exception is pending!");
 
-  JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL);
+  JS::RootedObject obj(cx, JS_NewObject(cx, NULL, NULL, NULL));
   if (!obj) {
     NS_WARNING("Failed to new JSObject for system message!");
     return false;
   }
 
   if (!SetJsObject(cx, aData, obj)) {
     NS_WARNING("Failed to set properties of system message!");
     return false;
--- a/dom/bluetooth/BluetoothUtils.h
+++ b/dom/bluetooth/BluetoothUtils.h
@@ -16,17 +16,17 @@ BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothNamedValue;
 class BluetoothValue;
 class BluetoothReplyRunnable;
 
 bool
 SetJsObject(JSContext* aContext,
             const BluetoothValue& aValue,
-            JSObject* aObj);
+            JS::Handle<JSObject*> aObj);
 
 nsString
 GetObjectPathFromAddress(const nsAString& aAdapterPath,
                          const nsAString& aDeviceAddress);
 
 nsString
 GetAddressFromObjectPath(const nsAString& aObjectPath);
 
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -156,37 +156,37 @@ CameraControlImpl::Get(JSContext* aCx, u
 
     JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, nullptr, nullptr));
     if (!o) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     DOM_CAMERA_LOGI("top=%d\n", r->top);
     v = INT_TO_JSVAL(r->top);
-    if (!JS_SetProperty(aCx, o, "top", v.address())) {
+    if (!JS_SetProperty(aCx, o, "top", &v)) {
       return NS_ERROR_FAILURE;
     }
     DOM_CAMERA_LOGI("left=%d\n", r->left);
     v = INT_TO_JSVAL(r->left);
-    if (!JS_SetProperty(aCx, o, "left", v.address())) {
+    if (!JS_SetProperty(aCx, o, "left", &v)) {
       return NS_ERROR_FAILURE;
     }
     DOM_CAMERA_LOGI("bottom=%d\n", r->bottom);
     v = INT_TO_JSVAL(r->bottom);
-    if (!JS_SetProperty(aCx, o, "bottom", v.address())) {
+    if (!JS_SetProperty(aCx, o, "bottom", &v)) {
       return NS_ERROR_FAILURE;
     }
     DOM_CAMERA_LOGI("right=%d\n", r->right);
     v = INT_TO_JSVAL(r->right);
-    if (!JS_SetProperty(aCx, o, "right", v.address())) {
+    if (!JS_SetProperty(aCx, o, "right", &v)) {
       return NS_ERROR_FAILURE;
     }
     DOM_CAMERA_LOGI("weight=%d\n", r->weight);
     v = INT_TO_JSVAL(r->weight);
-    if (!JS_SetProperty(aCx, o, "weight", v.address())) {
+    if (!JS_SetProperty(aCx, o, "weight", &v)) {
       return NS_ERROR_FAILURE;
     }
 
     v = OBJECT_TO_JSVAL(o);
     if (!JS_SetElement(aCx, array, i, v.address())) {
       return NS_ERROR_FAILURE;
     }
   }
--- a/dom/camera/CameraRecorderProfiles.cpp
+++ b/dom/camera/CameraRecorderProfiles.cpp
@@ -31,41 +31,41 @@ RecorderVideoProfile::GetJsObject(JSCont
   JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, nullptr, nullptr));
   NS_ENSURE_TRUE(o, NS_ERROR_OUT_OF_MEMORY);
 
   const char* codec = GetCodecName();
   NS_ENSURE_TRUE(codec, NS_ERROR_FAILURE);
 
   JS::Rooted<JSString*> s(aCx, JS_NewStringCopyZ(aCx, codec));
   JS::Rooted<JS::Value> v(aCx, STRING_TO_JSVAL(s));
-  if (!JS_SetProperty(aCx, o, "codec", v.address())) {
+  if (!JS_SetProperty(aCx, o, "codec", &v)) {
     return NS_ERROR_FAILURE;
   }
 
   if (mBitrate != -1) {
     v = INT_TO_JSVAL(mBitrate);
-    if (!JS_SetProperty(aCx, o, "bitrate", v.address())) {
+    if (!JS_SetProperty(aCx, o, "bitrate", &v)) {
       return NS_ERROR_FAILURE;
     }
   }
   if (mFramerate != -1) {
     v = INT_TO_JSVAL(mFramerate);
-    if (!JS_SetProperty(aCx, o, "framerate", v.address())) {
+    if (!JS_SetProperty(aCx, o, "framerate", &v)) {
       return NS_ERROR_FAILURE;
     }
   }
   if (mWidth != -1) {
     v = INT_TO_JSVAL(mWidth);
-    if (!JS_SetProperty(aCx, o, "width", v.address())) {
+    if (!JS_SetProperty(aCx, o, "width", &v)) {
       return NS_ERROR_FAILURE;
     }
   }
   if (mHeight != -1) {
     v = INT_TO_JSVAL(mHeight);
-    if (!JS_SetProperty(aCx, o, "height", v.address())) {
+    if (!JS_SetProperty(aCx, o, "height", &v)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   *aObject = o;
   return NS_OK;
 }
 
@@ -92,35 +92,35 @@ RecorderAudioProfile::GetJsObject(JSCont
   JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, nullptr, nullptr));
   NS_ENSURE_TRUE(o, NS_ERROR_OUT_OF_MEMORY);
 
   const char* codec = GetCodecName();
   NS_ENSURE_TRUE(codec, NS_ERROR_FAILURE);
 
   JS::Rooted<JSString*> s(aCx, JS_NewStringCopyZ(aCx, codec));
   JS::Rooted<JS::Value> v(aCx, STRING_TO_JSVAL(s));
-  if (!JS_SetProperty(aCx, o, "codec", v.address())) {
+  if (!JS_SetProperty(aCx, o, "codec", &v)) {
     return NS_ERROR_FAILURE;
   }
 
   if (mBitrate != -1) {
     v = INT_TO_JSVAL(mBitrate);
-    if (!JS_SetProperty(aCx, o, "bitrate", v.address())) {
+    if (!JS_SetProperty(aCx, o, "bitrate", &v)) {
       return NS_ERROR_FAILURE;
     }
   }
   if (mSamplerate != -1) {
     v = INT_TO_JSVAL(mSamplerate);
-    if (!JS_SetProperty(aCx, o, "samplerate", v.address())) {
+    if (!JS_SetProperty(aCx, o, "samplerate", &v)) {
       return NS_ERROR_FAILURE;
     }
   }
   if (mChannels != -1) {
     v = INT_TO_JSVAL(mChannels);
-    if (!JS_SetProperty(aCx, o, "channels", v.address())) {
+    if (!JS_SetProperty(aCx, o, "channels", &v)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   *aObject = o;
   return NS_OK;
 }
 
@@ -180,16 +180,16 @@ RecorderProfileManager::GetJsObject(JSCo
       continue;
     }
 
     JS::Rooted<JSObject*> p(aCx);
     nsresult rv = profile->GetJsObject(aCx, p.address());
     NS_ENSURE_SUCCESS(rv, rv);
     JS::Rooted<JS::Value> v(aCx, OBJECT_TO_JSVAL(p));
 
-    if (!JS_SetProperty(aCx, o, profileName, v.address())) {
+    if (!JS_SetProperty(aCx, o, profileName, &v)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   *aObject = o;
   return NS_OK;
 }
--- a/dom/camera/CameraRecorderProfiles.h
+++ b/dom/camera/CameraRecorderProfiles.h
@@ -168,37 +168,37 @@ public:
     NS_ENSURE_TRUE(aObject, NS_ERROR_INVALID_ARG);
 
     const char* format = GetFileFormatName();
     if (!format) {
       // the profile must have a file format
       return NS_ERROR_FAILURE;
     }
 
-    JSObject* o = JS_NewObject(aCx, nullptr, nullptr, nullptr);
+    JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, nullptr, nullptr));
     if (!o) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
-    JSString* s = JS_NewStringCopyZ(aCx, format);
-    JS::Value v = STRING_TO_JSVAL(s);
+    JS::Rooted<JSString*> s(aCx, JS_NewStringCopyZ(aCx, format));
+    JS::Rooted<JS::Value> v(aCx, STRING_TO_JSVAL(s));
     if (!JS_SetProperty(aCx, o, "format", &v)) {
       return NS_ERROR_FAILURE;
     }
 
-    JSObject* video;
-    nsresult rv = mVideo.GetJsObject(aCx, &video);
+    JS::Rooted<JSObject*> video(aCx);
+    nsresult rv = mVideo.GetJsObject(aCx, video.address());
     NS_ENSURE_SUCCESS(rv, rv);
     v = OBJECT_TO_JSVAL(video);
     if (!JS_SetProperty(aCx, o, "video", &v)) {
       return NS_ERROR_FAILURE;
     }
 
-    JSObject* audio;
-    rv = mAudio.GetJsObject(aCx, &audio);
+    JS::Rooted<JSObject*> audio(aCx);
+    rv = mAudio.GetJsObject(aCx, audio.address());
     NS_ENSURE_SUCCESS(rv, rv);
     v = OBJECT_TO_JSVAL(audio);
     if (!JS_SetProperty(aCx, o, "audio", &v)) {
       return NS_ERROR_FAILURE;
     }
 
     *aObject = o;
     return NS_OK;
--- a/dom/camera/DOMCameraCapabilities.cpp
+++ b/dom/camera/DOMCameraCapabilities.cpp
@@ -90,20 +90,20 @@ ParseDimensionItemAndAdd(JSContext* aCx,
   JS::Rooted<JS::Value> w(aCx, INT_TO_JSVAL(strtol(aStart, &x, 10)));
   JS::Rooted<JS::Value> h(aCx, INT_TO_JSVAL(strtol(x + 1, aEnd, 10)));
 
   JS::Rooted<JSObject*> o(aCx, JS_NewObject(aCx, nullptr, nullptr, nullptr));
   if (!o) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  if (!JS_SetProperty(aCx, o, "width", w.address())) {
+  if (!JS_SetProperty(aCx, o, "width", &w)) {
     return NS_ERROR_FAILURE;
   }
-  if (!JS_SetProperty(aCx, o, "height", h.address())) {
+  if (!JS_SetProperty(aCx, o, "height", &h)) {
     return NS_ERROR_FAILURE;
   }
 
   JS::Rooted<JS::Value> v(aCx, OBJECT_TO_JSVAL(o));
   if (!JS_SetElement(aCx, aArray, aIndex, v.address())) {
     return NS_ERROR_FAILURE;
   }
 
@@ -366,21 +366,21 @@ DOMCameraCapabilities::GetVideoSizes(JSC
   JS::Rooted<JSObject*> array(cx, JS_NewArrayObject(cx, 0, nullptr));
   if (!array) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   for (uint32_t i = 0; i < sizes.Length(); ++i) {
     JS::Rooted<JSObject*> o(cx, JS_NewObject(cx, nullptr, nullptr, nullptr));
     JS::Rooted<JS::Value> v(cx, INT_TO_JSVAL(sizes[i].width));
-    if (!JS_SetProperty(cx, o, "width", v.address())) {
+    if (!JS_SetProperty(cx, o, "width", &v)) {
       return NS_ERROR_FAILURE;
     }
     v = INT_TO_JSVAL(sizes[i].height);
-    if (!JS_SetProperty(cx, o, "height", v.address())) {
+    if (!JS_SetProperty(cx, o, "height", &v)) {
       return NS_ERROR_FAILURE;
     }
 
     v = OBJECT_TO_JSVAL(o);
     if (!JS_SetElement(cx, array, i, v.address())) {
       return NS_ERROR_FAILURE;
     }
   }
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1537,17 +1537,19 @@ TabChild::ProcessUpdateFrame(const Frame
       cssCompositedRect.width, cssCompositedRect.height);
     ScrollWindowTo(window, aFrameMetrics.mScrollOffset);
     LayoutDeviceToLayerScale resolution =
       aFrameMetrics.CalculateResolution()
       / aFrameMetrics.mDevPixelsPerCSSPixel
       * ScreenToLayerScale(1);
     utils->SetResolution(resolution.scale, resolution.scale);
 
-    SetDisplayPort(aFrameMetrics);
+    if (aFrameMetrics.mScrollId != FrameMetrics::NULL_SCROLL_ID) {
+      SetDisplayPort(aFrameMetrics);
+    }
 
     mLastMetrics = aFrameMetrics;
 
     return true;
 }
 
 void
 TabChild::SetDisplayPort(const FrameMetrics& aFrameMetrics)
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -783,17 +783,17 @@ nsJSObjWrapper::NP_SetProperty(NPObject 
   AutoJSExceptionReporter reporter(cx);
   JSAutoCompartment ac(cx, npjsobj->mJSObj);
 
   JS::Rooted<JS::Value> v(cx, NPVariantToJSVal(npp, cx, value));
 
   NS_ASSERTION(NPIdentifierIsInt(id) || NPIdentifierIsString(id),
                "id must be either string or int!\n");
   ok = ::JS_SetPropertyById(cx, npjsobj->mJSObj, NPIdentifierToJSId(id),
-                            v.address());
+                            &v);
 
   // return ok == JS_TRUE to quiet down compiler warning, even if
   // return ok is what we really want.
   return ok == JS_TRUE;
 }
 
 // static
 bool
--- a/dom/plugins/test/mochitest/Makefile.in
+++ b/dom/plugins/test/mochitest/Makefile.in
@@ -109,17 +109,16 @@ MOCHITEST_CHROME_FILES = \
   test_refresh_navigator_plugins.html \
   test_plugin_tag_clicktoplay.html \
   privatemode_perwindowpb.xul \
   test_privatemode_perwindowpb.xul \
   $(NULL)
 
 ifneq ($(MOZ_WIDGET_TOOLKIT),cocoa)
 MOCHITEST_FILES += \
-  test_instance_re-parent-windowed.html \
   test_visibility.html \
   $(NULL)
 
 ifneq ($(OS_ARCH),WINNT)
 MOCHITEST_CHROME_FILES += \
   test_xulbrowser_plugin_visibility.xul \
   xulbrowser_plugin_visibility.xul \
   plugin_visibility_loader.html \
deleted file mode 100644
--- a/dom/plugins/test/mochitest/test_instance_re-parent-windowed.html
+++ /dev/null
@@ -1,59 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>Test re-parentinging an instance's DOM node</title>
-  <script type="text/javascript" src="/MochiKit/packed.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body onload="startTest()">
-  <p id="display"></p>
-
-  <div id="div1">
-    <embed id="plugin1" type="application/x-test" width="200" height="200" wmode="window"></embed>
-  </div>
-  <div id="div2">
-  </div>
-
-  <script type="application/javascript;version=1.8">
-  SimpleTest.waitForExplicitFinish();
-
-  var destroyed = false;
-  function onDestroy() {
-    destroyed = true;
-  }
-
-  function startTest() {
-    var exceptionThrown = false;
-
-    var p = document.getElementById('plugin1');
-    var d1 = document.getElementById('div1');
-    var d2 = document.getElementById('div2');
-
-    p.startWatchingInstanceCount();
-    p.callOnDestroy(onDestroy);
-
-    try {
-      d1.removeChild(p);
-    } catch (e) {
-      exceptionThrown = true;
-    }
-    is(exceptionThrown, false, "Testing for exception after removeChild.");
-
-    try {
-      d2.appendChild(p);
-    } catch (e) {
-      exceptionThrown = true;
-    }
-    is(exceptionThrown, false, "Testing for exception after appendChild.");
-
-    is(destroyed, false, "No instances should have been destroyed at this point.");
-    is(p.getInstanceCount(), 0, "No new instances should have been created at this point.");
-
-    p.stopWatchingInstanceCount();
-
-    SimpleTest.finish();
-  }
-  </script>
-</body>
-</html>
--- a/dom/plugins/test/mochitest/test_instance_re-parent.html
+++ b/dom/plugins/test/mochitest/test_instance_re-parent.html
@@ -1,17 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
   <title>Test re-parentinging an instance's DOM node</title>
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
-<body onload="runTests()">
+<body onload="begin()">
   <p id="display"></p>
 
   <div id="div1">
     <!-- This embed has to have a "src" attribute. Part of this test involves seeing if we
          properly restart plugins that have been added back to a document without a change
          in URL. Not re-loading an object when the URL hasn't changed is a shortcut used for
          some object types. Without a URL, this won't be tested. -->
     <embed id="plugin1" src="loremipsum.txt" type="application/x-test" width="200" height="200"></embed>
@@ -23,47 +23,42 @@
   SimpleTest.waitForExplicitFinish();
 
   var exceptionThrown = false;
   var p = document.getElementById('plugin1');
   var d1 = document.getElementById('div1');
   var d2 = document.getElementById('div2');
 
   var destroyed = false;
-  function onDestroy() {
-    destroyed = true;
-  }
+
+  function begin() {
+    runTests(function() {
+      // Callback when finished - set plugin to windowed and repeat the tests
+
+      info("Repeating tests with wmode=window");
+      p.setAttribute("wmode", "window");
+      d1.appendChild(p);
 
-  function testsFinished() {
-    p.stopWatchingInstanceCount();
-    SimpleTest.finish();
+      // Forces the plugin to be respawned
+      p.src = p.src;
+
+      destroyed = false;
+      exceptionThrown = false;
+      runTests(function () {
+        SimpleTest.finish();
+      });
+    });
   }
 
-  function continueTests2() {
-    try {
-      is(p.getInstanceCount(), 1, "One new instance should have been created at this point.");
-    } catch (e) {
-      exceptionThrown = true;
-    }
-    is(exceptionThrown, false, "Testing for exception getting instance count from plugin.");
-
-    testsFinished();
-  }
-
-  function continueTests1() {
-    // Adding to the document will kick off the plugin load. Need to have a timeout
-    // before we can safely call into it.
-    d2.appendChild(p);
-    setTimeout('continueTests2();', 0);
-  }
-
-  function runTests() {
+  function runTests(callback) {
     // First tests involve moving the instance from one div to another.
     p.startWatchingInstanceCount();
-    p.callOnDestroy(onDestroy);
+    p.callOnDestroy(function() {
+      destroyed = true;
+    });
 
     try {
       d1.removeChild(p);
     } catch (e) {
       exceptionThrown = true;
     }
     is(exceptionThrown, false, "Testing for exception after removeChild.");
 
@@ -72,15 +67,34 @@
     } catch (e) {
       exceptionThrown = true;
     }
     is(exceptionThrown, false, "Testing for exception after appendChild.");
 
     is(destroyed, false, "No instances should have been destroyed at this point.");
     is(p.getInstanceCount(), 0, "No new instances should have been created at this point.");
 
-    // Now remove the instance from the document and let it die.
-    d2.removeChild(p);
-    setTimeout('continueTests1();', 0);
+    // Wait for the event loop to spin and ensure the plugin still wasn't touched
+    SimpleTest.executeSoon(function() {
+      is(destroyed, false, "No instances should have been destroyed at this point.");
+      is(p.getInstanceCount(), 0, "No new instances should have been created at this point.");
+
+      // Removing the instance for a full event loop *should* respawn
+      d2.removeChild(p);
+      SimpleTest.executeSoon(function() {
+        d2.appendChild(p);
+        SimpleTest.executeSoon(function() {
+          try {
+            is(p.getInstanceCount(), 1, "One new instance should have been created at this point.");
+          } catch (e) {
+            exceptionThrown = true;
+          }
+          is(exceptionThrown, false, "Testing for exception getting instance count from plugin.");
+
+          p.stopWatchingInstanceCount();
+          callback.apply(null);
+        });
+      });
+    });
   }
   </script>
 </body>
 </html>
--- a/dom/system/OSFileConstants.cpp
+++ b/dom/system/OSFileConstants.cpp
@@ -629,17 +629,17 @@ bool SetStringProperty(JSContext *cx, JS
                        const nsString aValue)
 {
   if (aValue.IsVoid()) {
     return true;
   }
   JSString* strValue = JS_NewUCStringCopyZ(cx, aValue.get());
   NS_ENSURE_TRUE(strValue, false);
   JS::Rooted<JS::Value> valValue(cx, STRING_TO_JSVAL(strValue));
-  return JS_SetProperty(cx, aObject, aProperty, valValue.address());
+  return JS_SetProperty(cx, aObject, aProperty, &valValue);
 }
 
 /**
  * Define OS-specific constants.
  *
  * This function creates or uses JS object |OS.Constants| to store
  * all its constants.
  */
@@ -702,24 +702,24 @@ bool DefineOSFileConstants(JSContext *cx
     MOZ_ASSERT(NS_SUCCEEDED(rv));
 
     JSString* strVersion = JS_NewStringCopyZ(cx, os.get());
     if (!strVersion) {
       return false;
     }
 
     JS::Rooted<JS::Value> valVersion(cx, STRING_TO_JSVAL(strVersion));
-    if (!JS_SetProperty(cx, objSys, "Name", valVersion.address())) {
+    if (!JS_SetProperty(cx, objSys, "Name", &valVersion)) {
       return false;
     }
   }
 
 #if defined(DEBUG)
   JS::Rooted<JS::Value> valDebug(cx, JSVAL_TRUE);
-  if (!JS_SetProperty(cx, objSys, "DEBUG", valDebug.address())) {
+  if (!JS_SetProperty(cx, objSys, "DEBUG", &valDebug)) {
     return false;
   }
 #endif
 
   // Build OS.Constants.Path
 
   JS::Rooted<JSObject*> objPath(cx);
   if (!(objPath = GetOrCreateObjectProperty(cx, objConstants, "Path"))) {
--- a/dom/webidl/Node.webidl
+++ b/dom/webidl/Node.webidl
@@ -91,17 +91,17 @@ interface Node : EventTarget {
   [Constant]
   readonly attribute DOMString? namespaceURI;
   [Constant]
   readonly attribute DOMString? prefix;
   [Constant]
   readonly attribute DOMString? localName;
 
   boolean hasAttributes();
-  [Throws, Func="nsINode::IsChromeOrXBL"]
+  [Throws, Func="IsChromeOrXBL"]
   any setUserData(DOMString key, any data, UserDataHandler? handler);
-  [Throws, Func="nsINode::IsChromeOrXBL"]
+  [Throws, Func="IsChromeOrXBL"]
   any getUserData(DOMString key);
   [ChromeOnly]
   readonly attribute Principal nodePrincipal;
   [ChromeOnly]
   readonly attribute URI? baseURIObject;
 };
--- a/dom/webidl/WebGL2RenderingContext.webidl
+++ b/dom/webidl/WebGL2RenderingContext.webidl
@@ -50,14 +50,16 @@ interface WebGL2RenderingContext : WebGL
 
     /* blend equations */
     const GLenum MIN                         = 0x8007;
     const GLenum MAX                         = 0x8008;
 
 
     void bindVertexArray(WebGLVertexArray? arrayObject);
     WebGLVertexArray? createVertexArray();
+    void drawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount);
     void drawBuffers(sequence<GLenum> buffers);
+    void drawElementsInstanced(GLenum mode, GLsizei count, GLenum type, GLintptr offset, GLsizei primcount);
     void deleteVertexArray(WebGLVertexArray? arrayObject);
     [WebGLHandlesContextLoss] GLboolean isVertexArray(WebGLVertexArray? arrayObject);
 
 };
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4257,31 +4257,28 @@ WorkerPrivate::UpdateJITHardeningInterna
 }
 
 void
 WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking,
                                       bool aCollectChildren)
 {
   AssertIsOnWorkerThread();
 
-  if (aCollectChildren) {
+  if (aShrinking || aCollectChildren) {
     JSRuntime* rt = JS_GetRuntime(aCx);
     JS::PrepareForFullGC(rt);
+
     if (aShrinking) {
       JS::ShrinkingGC(rt, JS::gcreason::DOM_WORKER);
     }
     else {
       JS::GCForReason(rt, JS::gcreason::DOM_WORKER);
     }
   }
   else {
-    // If aCollectChildren is false then it means this collection request was
-    // not generated by the main thread. At the moment only the periodic GC
-    // timer can end up here, so rather than force a collection let the JS
-    // engine decide if we need one.
     JS_MaybeGC(aCx);
   }
 
   if (aCollectChildren) {
     for (uint32_t index = 0; index < mChildWorkers.Length(); index++) {
       mChildWorkers[index]->GarbageCollect(aCx, aShrinking);
     }
   }
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -88,16 +88,20 @@ static const char *sExtensionNames[] = {
     "GL_OES_element_index_uint",
     "GL_OES_vertex_array_object",
     "GL_ARB_vertex_array_object",
     "GL_APPLE_vertex_array_object",
     "GL_ARB_draw_buffers",
     "GL_EXT_draw_buffers",
     "GL_EXT_gpu_shader4",
     "GL_EXT_blend_minmax",
+    "GL_ARB_draw_instanced",
+    "GL_EXT_draw_instanced",
+    "GL_NV_draw_instanced",
+    "GL_ANGLE_instanced_array",
     nullptr
 };
 
 static int64_t sTextureMemoryUsage = 0;
 
 static int64_t
 GetTextureMemoryUsage()
 {
@@ -596,16 +600,51 @@ GLContext::InitWithPrefix(const char *pr
                 MarkExtensionUnsupported(APPLE_vertex_array_object);
                 mSymbols.fIsVertexArray = nullptr;
                 mSymbols.fGenVertexArrays = nullptr;
                 mSymbols.fBindVertexArray = nullptr;
                 mSymbols.fDeleteVertexArrays = nullptr;
             }
         }
 
+        if (IsExtensionSupported(XXX_draw_instanced)) {
+            SymLoadStruct drawInstancedSymbols[] = {
+                { (PRFuncPtr*) &mSymbols.fDrawArraysInstanced,
+                  { "DrawArraysInstanced",
+                    "DrawArraysInstancedARB",
+                    "DrawArraysInstancedEXT",
+                    "DrawArraysInstancedNV",
+                    "DrawArraysInstancedANGLE",
+                    nullptr
+                  }
+                },
+                { (PRFuncPtr*) &mSymbols.fDrawElementsInstanced,
+                  { "DrawElementsInstanced",
+                    "DrawElementsInstancedARB",
+                    "DrawElementsInstancedEXT",
+                    "DrawElementsInstancedNV",
+                    "DrawElementsInstancedANGLE",
+                    nullptr
+                  }
+                },
+                { nullptr, { nullptr } },
+            };
+
+            if (!LoadSymbols(drawInstancedSymbols, trygl, prefix)) {
+                NS_ERROR("GL supports instanced draws without supplying its functions.");
+
+                MarkExtensionUnsupported(ARB_draw_instanced);
+                MarkExtensionUnsupported(EXT_draw_instanced);
+                MarkExtensionUnsupported(NV_draw_instanced);
+                MarkExtensionUnsupported(ANGLE_instanced_array);
+                mSymbols.fDrawArraysInstanced = nullptr;
+                mSymbols.fDrawElementsInstanced = nullptr;
+            }
+        }
+
         // Load developer symbols, don't fail if we can't find them.
         SymLoadStruct auxSymbols[] = {
                 { (PRFuncPtr*) &mSymbols.fGetTexImage, { "GetTexImage", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fGetTexLevelParameteriv, { "GetTexLevelParameteriv", nullptr } },
                 { nullptr, { nullptr } },
         };
         bool warnOnFailures = DebugMode();
         LoadSymbols(&auxSymbols[0], trygl, prefix, warnOnFailures);
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -1076,27 +1076,32 @@ public:
         OES_element_index_uint,
         OES_vertex_array_object,
         ARB_vertex_array_object,
         APPLE_vertex_array_object,
         ARB_draw_buffers,
         EXT_draw_buffers,
         EXT_gpu_shader4,
         EXT_blend_minmax,
+        ARB_draw_instanced,
+        EXT_draw_instanced,
+        NV_draw_instanced,
+        ANGLE_instanced_array,
         Extensions_Max
     };
 
     /**
      * This enum introduce the possibility to check if a extension is
      * supported, regardless it is a ARB, EXT, OES, NV, AMD, APPLE, ANGLE ...
      * Be sure that extensions specifications are exactly same !
      * This enum should be sorted by name.
      */
     enum GLExtensionPackages {
         XXX_draw_buffers,
+        XXX_draw_instanced,
         XXX_framebuffer_blit,
         XXX_framebuffer_multisample,
         XXX_framebuffer_object,
         XXX_texture_float,
         XXX_texture_non_power_of_two,
         XXX_robustness,
         XXX_vertex_array_object,
         ExtensionPackages_Max
@@ -1109,16 +1114,22 @@ public:
     bool IsExtensionSupported(GLExtensionPackages aKnownExtensionPackage) const
     {
         switch (aKnownExtensionPackage)
         {
         case XXX_draw_buffers:
             return IsExtensionSupported(ARB_draw_buffers) ||
                    IsExtensionSupported(EXT_draw_buffers);
 
+        case XXX_draw_instanced:
+            return IsExtensionSupported(ARB_draw_instanced) ||
+                   IsExtensionSupported(EXT_draw_instanced) ||
+                   IsExtensionSupported(NV_draw_instanced) ||
+                   IsExtensionSupported(ANGLE_instanced_array);
+
         case XXX_framebuffer_blit:
             return IsExtensionSupported(EXT_framebuffer_blit) ||
                    IsExtensionSupported(ANGLE_framebuffer_blit);
 
         case XXX_framebuffer_multisample:
             return IsExtensionSupported(EXT_framebuffer_multisample) ||
                    IsExtensionSupported(ANGLE_framebuffer_multisample);
 
@@ -3020,16 +3031,33 @@ public:
     {
         BEFORE_GL_CALL;
         ASSERT_SYMBOL_PRESENT(fIsVertexArray);
         realGLboolean ret = mSymbols.fIsVertexArray(array);
         AFTER_GL_CALL;
         return ret;
     }
 
+    // ARB_draw_instanced
+    void fDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount)
+    {
+        BEFORE_GL_CALL;
+        ASSERT_SYMBOL_PRESENT(fDrawArraysInstanced);
+        mSymbols.fDrawArraysInstanced(mode, first, count, primcount);
+        AFTER_GL_CALL;
+    }
+
+    void fDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLsizei primcount)
+    {
+        BEFORE_GL_CALL;
+        ASSERT_SYMBOL_PRESENT(fDrawElementsInstanced);
+        mSymbols.fDrawElementsInstanced(mode, count, type, indices, primcount);
+        AFTER_GL_CALL;
+    }
+
 #undef ASSERT_SYMBOL_PRESENT
 
 #ifdef DEBUG
     void CreatedProgram(GLContext *aOrigin, GLuint aName);
     void CreatedShader(GLContext *aOrigin, GLuint aName);
     void CreatedBuffers(GLContext *aOrigin, GLsizei aCount, GLuint *aNames);
     void CreatedQueries(GLContext *aOrigin, GLsizei aCount, GLuint *aNames);
     void CreatedTextures(GLContext *aOrigin, GLsizei aCount, GLuint *aNames);
--- a/gfx/gl/GLContextSymbols.h
+++ b/gfx/gl/GLContextSymbols.h
@@ -402,14 +402,20 @@ struct GLContextSymbols
     typedef void (GLAPIENTRY * PFNGLGETSYNCIV) (GLsync sync, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values);
     PFNGLGETSYNCIV fGetSynciv;
 
     // OES_egl_image
     typedef void (GLAPIENTRY * PFNGLEGLIMAGETARGETTEXTURE2D)(GLenum target, GLeglImage image);
     PFNGLEGLIMAGETARGETTEXTURE2D fEGLImageTargetTexture2D;
     typedef void (GLAPIENTRY * PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGE)(GLenum target, GLeglImage image);
     PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGE fEGLImageTargetRenderbufferStorage;
+
+    // ARB_draw_instanced
+    typedef void (GLAPIENTRY * PFNGLDRAWARRAYSINSTANCED) (GLenum mode, GLint first, GLsizei count, GLsizei primcount);
+    PFNGLDRAWARRAYSINSTANCED fDrawArraysInstanced;
+    typedef void (GLAPIENTRY * PFNGLDRAWELEMENTSINSTANCED) (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLsizei primcount);
+    PFNGLDRAWELEMENTSINSTANCED fDrawElementsInstanced;
 };
 
 }
 }
 
 #endif /* GLCONTEXTSYMBOLS_H_ */
--- a/gfx/gl/GLLibraryLoader.h
+++ b/gfx/gl/GLLibraryLoader.h
@@ -22,17 +22,17 @@ namespace gl {
 class GLLibraryLoader
 {
 public:
     bool OpenLibrary(const char *library);
 
     typedef PRFuncPtr (GLAPIENTRY * PlatformLookupFunction) (const char *);
 
     enum {
-        MAX_SYMBOL_NAMES = 5,
+        MAX_SYMBOL_NAMES = 6,
         MAX_SYMBOL_LENGTH = 128
     };
 
     typedef struct {
         PRFuncPtr *symPointer;
         const char *symNames[MAX_SYMBOL_NAMES];
     } SymLoadStruct;
 
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -1375,19 +1375,34 @@ void AsyncPanZoomController::ContentRece
 
       mTouchQueue.RemoveElementAt(0);
     }
 
     mHandlingTouchQueue = false;
   }
 }
 
-void AsyncPanZoomController::SetState(PanZoomState aState) {
-  MonitorAutoLock monitor(mMonitor);
-  mState = aState;
+void AsyncPanZoomController::SetState(PanZoomState aNewState) {
+
+  PanZoomState oldState;
+
+  // Intentional scoping for mutex
+  {
+    MonitorAutoLock monitor(mMonitor);
+    oldState = mState;
+    mState = aNewState;
+  }
+
+  if (mGeckoContentController) {
+    if (oldState == PANNING && aNewState != PANNING) {
+      mGeckoContentController->HandlePanEnd();
+    } else if (oldState != PANNING && aNewState == PANNING) {
+      mGeckoContentController->HandlePanBegin();
+    }
+  }
 }
 
 void AsyncPanZoomController::TimeoutTouchListeners() {
   mTouchListenerTimeoutTask = nullptr;
   ContentReceivedTouch(false);
 }
 
 void AsyncPanZoomController::SetZoomAndResolution(const ScreenToScreenScale& aZoom) {
--- a/gfx/layers/ipc/Axis.cpp
+++ b/gfx/layers/ipc/Axis.cpp
@@ -312,17 +312,17 @@ float Axis::GetPageLength() {
   return GetRectLength(pageRect);
 }
 
 bool Axis::ScaleWillOverscrollBothSides(float aScale) {
   const FrameMetrics& metrics = mAsyncPanZoomController->GetFrameMetrics();
 
   CSSRect cssContentRect = metrics.mScrollableRect;
 
-  CSSToScreenScale scale(metrics.mZoom.scale * aScale);
+  CSSToScreenScale scale(metrics.CalculateResolution().scale * aScale);
   CSSIntRect cssCompositionBounds = RoundedIn(metrics.mCompositionBounds / scale);
 
   return GetRectLength(cssContentRect) < GetRectLength(CSSRect(cssCompositionBounds));
 }
 
 AxisX::AxisX(AsyncPanZoomController* aAsyncPanZoomController)
   : Axis(aAsyncPanZoomController)
 {
--- a/gfx/layers/ipc/GeckoContentController.h
+++ b/gfx/layers/ipc/GeckoContentController.h
@@ -55,16 +55,27 @@ public:
                                        const CSSSize &aScrollableSize) = 0;
 
   /**
    * Schedules a runnable to run on the controller/UI thread at some time
    * in the future.
    */
   virtual void PostDelayedTask(Task* aTask, int aDelayMs) = 0;
 
+
+  /**
+   * Request any special actions be performed when panning starts
+   */
+  virtual void HandlePanBegin() {}
+
+  /**
+   * Request any special actions be performed when panning ends
+   */
+  virtual void HandlePanEnd() {}
+
   GeckoContentController() {}
   virtual ~GeckoContentController() {}
 };
 
 }
 }
 
 #endif // mozilla_layers_GeckoContentController_h
--- a/gfx/src/nsColor.cpp
+++ b/gfx/src/nsColor.cpp
@@ -86,20 +86,20 @@ static int ComponentValue(const PRUnicha
     }
     else {  // not a hex digit, treat it like 0
       component = (component * 16);
     }
   }
   return component;
 }
 
-NS_GFX_(bool) NS_HexToRGB(const nsString& aColorSpec,
+NS_GFX_(bool) NS_HexToRGB(const nsAString& aColorSpec,
                                        nscolor* aResult)
 {
-  const PRUnichar* buffer = aColorSpec.get();
+  const PRUnichar* buffer = aColorSpec.BeginReading();
 
   int nameLen = aColorSpec.Length();
   if ((nameLen == 3) || (nameLen == 6)) {
     // Make sure the digits are legal
     for (int i = 0; i < nameLen; i++) {
       PRUnichar ch = buffer[i];
       if (((ch >= '0') && (ch <= '9')) ||
           ((ch >= 'a') && (ch <= 'f')) ||
--- a/gfx/src/nsColor.h
+++ b/gfx/src/nsColor.h
@@ -43,17 +43,17 @@ typedef uint32_t nscolor;
   PR_BEGIN_MACRO                                   \
     unsigned tmp_ = v;                             \
     target = ((tmp_ << 8) + tmp_ + 255) >> 16;     \
   PR_END_MACRO
 
 // Translate a hex string to a color. Return true if it parses ok,
 // otherwise return false.
 // This accepts only 3 or 6 digits
-NS_GFX_(bool) NS_HexToRGB(const nsString& aBuf, nscolor* aResult);
+NS_GFX_(bool) NS_HexToRGB(const nsAString& aBuf, nscolor* aResult);
 
 // Compose one NS_RGB color onto another. The result is what
 // you get if you draw aFG on top of aBG with operator OVER.
 NS_GFX_(nscolor) NS_ComposeColors(nscolor aBG, nscolor aFG);
 
 // Translate a hex string to a color. Return true if it parses ok,
 // otherwise return false.
 // This version accepts 1 to 9 digits (missing digits are 0)
--- a/js/ipc/JavaScriptChild.cpp
+++ b/js/ipc/JavaScriptChild.cpp
@@ -409,17 +409,17 @@ JavaScriptChild::AnswerSet(const ObjectI
         return fail(cx, rs);
 
     MOZ_ASSERT(obj == receiver);
 
     RootedValue val(cx);
     if (!toValue(cx, value, &val))
         return fail(cx, rs);
 
-    if (!JS_SetPropertyById(cx, obj, internedId, val.address()))
+    if (!JS_SetPropertyById(cx, obj, internedId, &val))
         return fail(cx, rs);
 
     if (!toVariant(cx, val, result))
         return fail(cx, rs);
 
     return ok(rs);
 }
 
--- a/js/ipc/JavaScriptParent.cpp
+++ b/js/ipc/JavaScriptParent.cpp
@@ -431,17 +431,17 @@ JavaScriptParent::call(JSContext *cx, Ha
             continue;
 
         // Take the value the child process returned, and set it on the XPC
         // object.
         if (!toValue(cx, outparams[i], &v))
             return false;
 
         JSObject *obj = &outobjects[i].toObject();
-        if (!JS_SetProperty(cx, obj, "value", v.address()))
+        if (!JS_SetProperty(cx, obj, "value", &v))
             return false;
     }
 
     if (!toValue(cx, result, args.rval()))
         return false;
 
     return true;
 }
--- a/js/public/CallArgs.h
+++ b/js/public/CallArgs.h
@@ -312,40 +312,26 @@ class MOZ_STACK_CLASS CallArgsBase :
   protected:
     unsigned argc_;
 
   public:
     /* Returns the number of arguments. */
     unsigned length() const { return argc_; }
 
     /* Returns the i-th zero-indexed argument. */
-    Value &operator[](unsigned i) const {
-        MOZ_ASSERT(i < argc_);
-        return this->argv_[i];
-    }
-
-    /* Returns a mutable handle for the i-th zero-indexed argument. */
-    MutableHandleValue handleAt(unsigned i) const {
+    MutableHandleValue operator[](unsigned i) const {
         MOZ_ASSERT(i < argc_);
         return MutableHandleValue::fromMarkedLocation(&this->argv_[i]);
     }
 
     /*
      * Returns the i-th zero-indexed argument, or |undefined| if there's no
      * such argument.
      */
-    Value get(unsigned i) const {
-        return i < length() ? this->argv_[i] : UndefinedValue();
-    }
-
-    /*
-     * Returns the i-th zero-indexed argument as a handle, or |undefined| if
-     * there's no such argument.
-     */
-    HandleValue handleOrUndefinedAt(unsigned i) const {
+    HandleValue get(unsigned i) const {
         return i < length()
                ? HandleValue::fromMarkedLocation(&this->argv_[i])
                : UndefinedHandleValue;
     }
 
     /*
      * Returns true if the i-th zero-indexed argument is present and is not
      * |undefined|.
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -176,16 +176,19 @@ JS_FRIEND_API(bool)
 IsIncrementalGCInProgress(JSRuntime *rt);
 
 extern JS_FRIEND_API(void)
 DisableIncrementalGC(JSRuntime *rt);
 
 extern JS_FRIEND_API(void)
 DisableGenerationalGC(JSRuntime *rt);
 
+extern JS_FRIEND_API(void)
+EnableGenerationalGC(JSRuntime *rt);
+
 extern JS_FRIEND_API(bool)
 IsIncrementalBarrierNeeded(JSRuntime *rt);
 
 extern JS_FRIEND_API(bool)
 IsIncrementalBarrierNeeded(JSContext *cx);
 
 extern JS_FRIEND_API(void)
 IncrementalReferenceBarrier(void *ptr, JSGCTraceKind kind);
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -513,18 +513,18 @@ class MOZ_STACK_CLASS MutableHandle : pu
     operator T() const { return get(); }
     T operator->() const { return get(); }
 
   private:
     MutableHandle() {}
 
     T *ptr;
 
-    template <typename S>
-    void operator=(S v) MOZ_DELETE;
+    template <typename S> void operator=(S v) MOZ_DELETE;
+    void operator=(MutableHandle other) MOZ_DELETE;
 };
 
 typedef MutableHandle<JSObject*>   MutableHandleObject;
 typedef MutableHandle<JSFunction*> MutableHandleFunction;
 typedef MutableHandle<JSScript*>   MutableHandleScript;
 typedef MutableHandle<JSString*>   MutableHandleString;
 typedef MutableHandle<jsid>        MutableHandleId;
 typedef MutableHandle<Value>       MutableHandleValue;
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -1462,16 +1462,17 @@ class ValueOperations
     int32_t toInt32() const { return value()->toInt32(); }
     double toDouble() const { return value()->toDouble(); }
     JSString *toString() const { return value()->toString(); }
     JSObject &toObject() const { return value()->toObject(); }
     JSObject *toObjectOrNull() const { return value()->toObjectOrNull(); }
     void *toGCThing() const { return value()->toGCThing(); }
 
     JSValueType extractNonDoubleType() const { return value()->extractNonDoubleType(); }
+    uint32_t toPrivateUint32() const { return value()->toPrivateUint32(); }
 
     JSWhyMagic whyMagic() const { return value()->whyMagic(); }
 };
 
 /*
  * A class designed for CRTP use in implementing the mutating parts of the Value
  * interface in Value-like classes that don't need post barriers.  Outer must be
  * a class inheriting UnbarrieredMutableValueOperations<Outer> with visible
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -67,16 +67,20 @@ VPATH		+= \
 
 ###############################################
 # BEGIN enable non-releasable features
 #
 ifdef NIGHTLY_BUILD
 DEFINES += -DENABLE_PARALLEL_JS
 endif
 
+ifdef NIGHTLY_BUILD
+DEFINES += -DENABLE_BINARYDATA
+endif
+
 # Ion
 ifdef ENABLE_ION
 VPATH +=	$(srcdir)/ion
 VPATH +=	$(srcdir)/ion/shared
 
 ifeq (86, $(findstring 86,$(TARGET_CPU)))
 ifeq (x86_64, $(TARGET_CPU))
 VPATH +=	$(srcdir)/ion/x64
--- a/js/src/assembler/assembler/X86Assembler.h
+++ b/js/src/assembler/assembler/X86Assembler.h
@@ -39,16 +39,17 @@
 #include "assembler/assembler/AssemblerBuffer.h"
 #include "assembler/wtf/Assertions.h"
 #include "js/Vector.h"
 
 namespace JSC {
 
 inline bool CAN_SIGN_EXTEND_8_32(int32_t value) { return value == (int32_t)(signed char)value; }
 inline bool CAN_ZERO_EXTEND_8_32(int32_t value) { return value == (int32_t)(unsigned char)value; }
+inline bool CAN_ZERO_EXTEND_8H_32(int32_t value) { return value == (value & 0xff00); }
 inline bool CAN_ZERO_EXTEND_32_64(int32_t value) { return value >= 0; }
 
 namespace X86Registers {
     typedef enum {
         eax,
         ecx,
         edx,
         ebx,
@@ -131,16 +132,41 @@ namespace X86Registers {
     {
 #       if WTF_CPU_X86_64
         return nameIReg(8, reg);
 #       else
         return nameIReg(4, reg);
 #       endif
     }
 
+    inline bool hasSubregL(RegisterID reg)
+    {
+#       if WTF_CPU_X86_64
+        // In 64-bit mode, all registers have an 8-bit lo subreg.
+        return true;
+#       else
+        // In 32-bit mode, only the first four registers do.
+        return reg <= ebx;
+#       endif
+    }
+
+    inline bool hasSubregH(RegisterID reg)
+    {
+        // The first four registers always have h registers. However, note that
+        // on x64, h registers may not be used in instructions using REX
+        // prefixes. Also note that this may depend on what other registers are
+        // used!
+        return reg <= ebx;
+    }
+
+    inline RegisterID getSubregH(RegisterID reg) {
+        JS_ASSERT(hasSubregH(reg));
+        return RegisterID(reg + 4);
+    }
+
 } /* namespace X86Registers */
 
 
 class X86Assembler : public GenericAssembler {
 public:
     typedef X86Registers::RegisterID RegisterID;
     typedef X86Registers::XMMRegisterID XMMRegisterID;
     typedef XMMRegisterID FPRegisterID;
@@ -1279,25 +1305,28 @@ public:
     {
         spew("testb      %s, %s",
              nameIReg(1,src), nameIReg(1,dst));
         m_formatter.oneByteOp(OP_TEST_EbGb, src, dst);
     }
 
     void testl_i32r(int imm, RegisterID dst)
     {
-#if WTF_CPU_X86_64
         // If the mask fits in an 8-bit immediate, we can use testb with an
-        // 8-bit subreg. This could be extended to handle x86-32 too, but it
-        // would require a check to see if the register supports 8-bit subregs.
-        if (CAN_ZERO_EXTEND_8_32(imm)) {
+        // 8-bit subreg.
+        if (CAN_ZERO_EXTEND_8_32(imm) && X86Registers::hasSubregL(dst)) {
             testb_i8r(imm, dst);
             return;
         }
-#endif
+        // If the mask is a subset of 0xff00, we can use testb with an h reg, if
+        // one happens to be available.
+        if (CAN_ZERO_EXTEND_8H_32(imm) && X86Registers::hasSubregH(dst)) {
+            testb_i8r_norex(imm >> 8, X86Registers::getSubregH(dst));
+            return;
+        }
         spew("testl      $0x%x, %s",
              imm, nameIReg(dst));
         m_formatter.oneByteOp(OP_GROUP3_EvIz, GROUP3_OP_TEST, dst);
         m_formatter.immediate32(imm);
     }
 
     void testl_i32m(int imm, int offset, RegisterID base)
     {
@@ -1333,24 +1362,26 @@ public:
     {
         spew("testq      %s, %s",
              nameIReg(8,src), nameIReg(8,dst));
         m_formatter.oneByteOp64(OP_TEST_EvGv, src, dst);
     }
 
     void testq_i32r(int imm, RegisterID dst)
     {
+        // If the mask fits in a 32-bit immediate, we can use testl with a
+        // 32-bit subreg.
         if (CAN_ZERO_EXTEND_32_64(imm)) {
             testl_i32r(imm, dst);
-        } else {
-            spew("testq      $0x%x, %s",
-                 imm, nameIReg(dst));
-            m_formatter.oneByteOp64(OP_GROUP3_EvIz, GROUP3_OP_TEST, dst);
-            m_formatter.immediate32(imm);
+            return;
         }
+        spew("testq      $0x%x, %s",
+             imm, nameIReg(dst));
+        m_formatter.oneByteOp64(OP_GROUP3_EvIz, GROUP3_OP_TEST, dst);
+        m_formatter.immediate32(imm);
     }
 
     void testq_i32m(int imm, int offset, RegisterID base)
     {
         spew("testq      $0x%x, %s0x%x(%s)",
              imm, PRETTY_PRINT_OFFSET(offset), nameIReg(base));
         m_formatter.oneByteOp64(OP_GROUP3_EvIz, GROUP3_OP_TEST, base, offset);
         m_formatter.immediate32(imm);
@@ -1374,16 +1405,26 @@ public:
     void testb_i8r(int imm, RegisterID dst)
     {
         spew("testb      $0x%x, %s",
              imm, nameIReg(1,dst));
         m_formatter.oneByteOp8(OP_GROUP3_EbIb, GROUP3_OP_TEST, dst);
         m_formatter.immediate8(imm);
     }
 
+    // Like testb_i8r, but never emits a REX prefix. This may be used to
+    // reference ah..bh.
+    void testb_i8r_norex(int imm, RegisterID dst)
+    {
+        spew("testb      $0x%x, %s",
+             imm, nameIReg(1,dst));
+        m_formatter.oneByteOp8_norex(OP_GROUP3_EbIb, GROUP3_OP_TEST, dst);
+        m_formatter.immediate8(imm);
+    }
+
     void setCC_r(Condition cond, RegisterID dst)
     {
         spew("set%s      %s",
              nameCC(cond), nameIReg(1,dst));
         m_formatter.twoByteOp8(setccOpcode(cond), (GroupOpcodeID)0, dst);
     }
 
     void sete_r(RegisterID dst)
@@ -1857,22 +1898,19 @@ public:
     {
         spew("movswl     %d(%s,%s,%d), %s",
              offset, nameIReg(base), nameIReg(index), 1<<scale, nameIReg(dst));
         m_formatter.twoByteOp(OP2_MOVSX_GvEw, dst, base, index, scale, offset);
     }
 
     void movzbl_rr(RegisterID src, RegisterID dst)
     {
-        // In 64-bit, this may cause an unnecessary REX to be planted (if the dst register
-        // is in the range ESP-EDI, and the src would not have required a REX).  Unneeded
-        // REX prefixes are defined to be silently ignored by the processor.
         spew("movzbl     %s, %s",
              nameIReg(1,src), nameIReg(4,dst));
-        m_formatter.twoByteOp8(OP2_MOVZX_GvEb, dst, src);
+        m_formatter.twoByteOp8_movx(OP2_MOVZX_GvEb, dst, src);
     }
 
     void leal_mr(int offset, RegisterID base, RegisterID index, int scale, RegisterID dst)
     {
         spew("leal       %d(%s,%s,%d), %s",
              offset, nameIReg(base), nameIReg(index), 1<<scale, nameIReg(dst));
         m_formatter.oneByteOp(OP_LEA, dst, base, index, scale, offset);
     }
@@ -3139,42 +3177,39 @@ private:
         // These methods format byte operations.  Byte operations differ from the normal
         // formatters in the circumstances under which they will decide to emit REX prefixes.
         // These should be used where any register operand signifies a byte register.
         //
         // The disctinction is due to the handling of register numbers in the range 4..7 on
         // x86-64.  These register numbers may either represent the second byte of the first
         // four registers (ah..bh) or the first byte of the second four registers (spl..dil).
         //
-        // Since ah..bh cannot be used in all permutations of operands (specifically cannot
-        // be accessed where a REX prefix is present), these are likely best treated as
-        // deprecated.  In order to ensure the correct registers spl..dil are selected a
-        // REX prefix will be emitted for any byte register operand in the range 4..15.
-        //
-        // These formatters may be used in instructions where a mix of operand sizes, in which
-        // case an unnecessary REX will be emitted, for example:
-        //     movzbl %al, %edi
-        // In this case a REX will be planted since edi is 7 (and were this a byte operand
-        // a REX would be required to specify dil instead of bh).  Unneeded REX prefixes will
-        // be silently ignored by the processor.
-        //
         // Address operands should still be checked using regRequiresRex(), while byteRegRequiresRex()
         // is provided to check byte register operands.
 
         void oneByteOp8(OneByteOpcodeID opcode, GroupOpcodeID groupOp, RegisterID rm)
         {
 #if !WTF_CPU_X86_64
             ASSERT(!byteRegRequiresRex(rm));
 #endif
             m_buffer.ensureSpace(maxInstructionSize);
             emitRexIf(byteRegRequiresRex(rm), 0, 0, rm);
             m_buffer.putByteUnchecked(opcode);
             registerModRM(groupOp, rm);
         }
 
+        // Like oneByteOp8, but never emits a REX prefix.
+        void oneByteOp8_norex(OneByteOpcodeID opcode, GroupOpcodeID groupOp, RegisterID rm)
+        {
+            ASSERT(!regRequiresRex(rm));
+            m_buffer.ensureSpace(maxInstructionSize);
+            m_buffer.putByteUnchecked(opcode);
+            registerModRM(groupOp, rm);
+        }
+
         void oneByteOp8(OneByteOpcodeID opcode, int reg, RegisterID base, int offset)
         {
 #if !WTF_CPU_X86_64
             ASSERT(!byteRegRequiresRex(reg));
 #endif
             m_buffer.ensureSpace(maxInstructionSize);
             emitRexIf(byteRegRequiresRex(reg), reg, 0, base);
             m_buffer.putByteUnchecked(opcode);
@@ -3207,16 +3242,29 @@ private:
         {
             m_buffer.ensureSpace(maxInstructionSize);
             emitRexIf(byteRegRequiresRex(reg)|byteRegRequiresRex(rm), reg, 0, rm);
             m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE);
             m_buffer.putByteUnchecked(opcode);
             registerModRM(reg, rm);
         }
 
+        // Like twoByteOp8 but doesn't add a REX prefix if the destination reg
+        // is in esp..edi. This may be used when the destination is not an 8-bit
+        // register (as in a movzbl instruction), so it doesn't need a REX
+        // prefix to disambiguate it from ah..bh.
+        void twoByteOp8_movx(TwoByteOpcodeID opcode, RegisterID reg, RegisterID rm)
+        {
+            m_buffer.ensureSpace(maxInstructionSize);
+            emitRexIf(regRequiresRex(reg)|byteRegRequiresRex(rm), reg, 0, rm);
+            m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE);
+            m_buffer.putByteUnchecked(opcode);
+            registerModRM(reg, rm);
+        }
+
         void twoByteOp8(TwoByteOpcodeID opcode, GroupOpcodeID groupOp, RegisterID rm)
         {
             m_buffer.ensureSpace(maxInstructionSize);
             emitRexIf(byteRegRequiresRex(rm), 0, 0, rm);
             m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE);
             m_buffer.putByteUnchecked(opcode);
             registerModRM(groupOp, rm);
         }
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/BinaryData.cpp
@@ -0,0 +1,2181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ */
+/* 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 "builtin/BinaryData.h"
+
+#include <vector>
+
+#include "mozilla/FloatingPoint.h"
+
+#include "jscompartment.h"
+#include "jsfun.h"
+#include "jsobj.h"
+#include "jsutil.h"
+
+#include "vm/TypedArrayObject.h"
+#include "vm/String.h"
+#include "vm/StringBuffer.h"
+#include "vm/GlobalObject.h"
+
+#include "jsatominlines.h"
+#include "jsobjinlines.h"
+
+using namespace js;
+
+/*
+ * Reify() takes a complex binary data object `owner` and an offset and tries to
+ * convert the value of type `type` at that offset to a JS Value stored in
+ * `vp`.
+ *
+ * NOTE: `type` is NOT the type of `owner`, but the type of `owner.elementType` in
+ * case of BinaryArray or `owner[field].type` in case of BinaryStruct.
+ */
+static bool Reify(JSContext *cx, HandleObject type, HandleObject owner,
+                  size_t offset, MutableHandleValue vp);
+
+/*
+ * ConvertAndCopyTo() converts `from` to type `type` and stores the result in
+ * `mem`, which MUST be pre-allocated to the appropriate size for instances of
+ * `type`.
+ */
+static bool ConvertAndCopyTo(JSContext *cx, HandleObject type,
+                             HandleValue from, uint8_t *mem);
+JSBool TypeThrowError(JSContext *cx, unsigned argc, Value *vp)
+{
+    return ReportIsNotFunction(cx, *vp);
+}
+
+JSBool DataThrowError(JSContext *cx, unsigned argc, Value *vp)
+{
+    return ReportIsNotFunction(cx, *vp);
+}
+
+static void
+ReportTypeError(JSContext *cx, Value fromValue, const char *toType)
+{
+    char *valueStr = JS_EncodeString(cx, JS_ValueToString(cx, fromValue));
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CONVERT_TO,
+                         valueStr, toType);
+    JS_free(cx, (void *) valueStr);
+}
+
+static void
+ReportTypeError(JSContext *cx, Value fromValue, JSString *toType)
+{
+    const char *fnName = JS_EncodeString(cx, toType);
+    ReportTypeError(cx, fromValue, fnName);
+    JS_free(cx, (void *) fnName);
+}
+
+// The false return value allows callers to just return as soon as this is
+// called.
+// So yes this call is with side effects.
+static bool
+ReportTypeError(JSContext *cx, Value fromValue, HandleObject exemplar)
+{
+    RootedValue v(cx, ObjectValue(*exemplar));
+    ReportTypeError(cx, fromValue, ToString<CanGC>(cx, v));
+    return false;
+}
+
+static int32_t
+Clamp(int32_t value, int32_t min, int32_t max)
+{
+    JS_ASSERT(min < max);
+    if (value < min)
+        return min;
+    if (value > max)
+        return max;
+    return value;
+}
+
+static inline bool
+IsNumericType(HandleObject type)
+{
+    return type && &NumericTypeClasses[NUMERICTYPE_UINT8] <= type->getClass() &&
+                   type->getClass() <= &NumericTypeClasses[NUMERICTYPE_FLOAT64];
+}
+
+static inline bool
+IsArrayType(HandleObject type)
+{
+    return type && type->hasClass(&ArrayType::class_);
+}
+
+static inline bool
+IsStructType(HandleObject type)
+{
+    return type && type->hasClass(&StructType::class_);
+}
+
+static inline bool
+IsComplexType(HandleObject type)
+{
+    return IsArrayType(type) || IsStructType(type);
+}
+
+static inline bool
+IsBinaryType(HandleObject type)
+{
+    return IsNumericType(type) || IsComplexType(type);
+}
+
+static inline bool
+IsBinaryArray(HandleObject type)
+{
+    return type && type->hasClass(&BinaryArray::class_);
+}
+
+static inline bool
+IsBinaryStruct(HandleObject type)
+{
+    return type && type->hasClass(&BinaryStruct::class_);
+}
+
+static inline bool
+IsBlock(HandleObject type)
+{
+    return type->hasClass(&BinaryArray::class_) ||
+           type->hasClass(&BinaryStruct::class_);
+}
+
+static inline JSObject *
+GetType(HandleObject block)
+{
+    JS_ASSERT(IsBlock(block));
+    return block->getFixedSlot(SLOT_DATATYPE).toObjectOrNull();
+}
+
+static size_t
+GetMemSize(JSContext *cx, HandleObject type)
+{
+    if (IsComplexType(type))
+        return type->getFixedSlot(SLOT_MEMSIZE).toInt32();
+
+    RootedObject typeObj(cx, type);
+    RootedValue val(cx);
+    JS_ASSERT(IsNumericType(type));
+    JSObject::getProperty(cx, typeObj, typeObj, cx->names().bytes, &val);
+    return val.toInt32();
+}
+
+static size_t
+GetAlign(JSContext *cx, HandleObject type)
+{
+    if (IsComplexType(type))
+        return type->getFixedSlot(SLOT_ALIGN).toInt32();
+
+    RootedObject typeObj(cx, type);
+    RootedValue val(cx);
+    JS_ASSERT(&NumericTypeClasses[NUMERICTYPE_UINT8] <= type->getClass() &&
+              type->getClass() <= &NumericTypeClasses[NUMERICTYPE_FLOAT64]);
+    JSObject::getProperty(cx, typeObj, typeObj, cx->names().bytes, &val);
+    return val.toInt32();
+}
+
+struct FieldInfo
+{
+    jsid name;
+    JSObject *type;
+    size_t offset;
+};
+
+typedef std::vector<FieldInfo> FieldList;
+
+static
+bool
+LookupFieldList(FieldList *list, jsid fieldName, FieldInfo *out)
+{
+    for (FieldList::const_iterator it = list->begin(); it != list->end(); ++it) {
+        if ((*it).name == fieldName) {
+            out->name = it->name;
+            out->type = it->type;
+            out->offset = it->offset;
+            return true;
+        }
+    }
+    return false;
+}
+
+static bool
+IsSameBinaryDataType(JSContext *cx, HandleObject type1, HandleObject type2);
+
+static bool
+IsSameArrayType(JSContext *cx, HandleObject type1, HandleObject type2)
+{
+    JS_ASSERT(IsArrayType(type1) && IsArrayType(type2));
+    if (ArrayType::length(cx, type1) != ArrayType::length(cx, type2))
+        return false;
+
+    RootedObject elementType1(cx, ArrayType::elementType(cx, type1));
+    RootedObject elementType2(cx, ArrayType::elementType(cx, type2));
+    return IsSameBinaryDataType(cx, elementType1, elementType2);
+}
+
+static bool
+IsSameStructType(JSContext *cx, HandleObject type1, HandleObject type2)
+{
+    JS_ASSERT(IsStructType(type1) && IsStructType(type2));
+
+    FieldList *fieldList1 = static_cast<FieldList *>(type1->getPrivate());
+    FieldList *fieldList2 = static_cast<FieldList *>(type2->getPrivate());
+
+    if (fieldList1->size() != fieldList2->size())
+        return false;
+
+    // Names and layout should be the same.
+    for (uint32_t i = 0; i < fieldList1->size(); ++i) {
+        FieldInfo fieldInfo1 = fieldList1->at(i);
+        FieldInfo fieldInfo2 = fieldList2->at(i);
+
+        if (fieldInfo1.name != fieldInfo2.name)
+            return false;
+
+        if (fieldInfo1.offset != fieldInfo2.offset)
+            return false;
+
+        RootedObject fieldType1(cx, fieldInfo1.type);
+        RootedObject fieldType2(cx, fieldInfo2.type);
+        if (!IsSameBinaryDataType(cx, fieldType1, fieldType2))
+            return false;
+    }
+
+    return true;
+}
+
+static bool
+IsSameBinaryDataType(JSContext *cx, HandleObject type1, HandleObject type2)
+{
+    JS_ASSERT(IsBinaryType(type1));
+    JS_ASSERT(IsBinaryType(type2));
+
+    if (IsNumericType(type1)) {
+        return type1->hasClass(type2->getClass());
+    } else if (IsArrayType(type1) && IsArrayType(type2)) {
+        return IsSameArrayType(cx, type1, type2);
+    } else if (IsStructType(type1) && IsStructType(type2)) {
+        return IsSameStructType(cx, type1, type2);
+    }
+
+    return false;
+}
+
+template <typename Domain, typename Input>
+bool
+InRange(Input x)
+{
+    return std::numeric_limits<Domain>::min() <= x &&
+           x <= std::numeric_limits<Domain>::max();
+}
+
+template <>
+bool
+InRange<float, int>(int x)
+{
+    return -std::numeric_limits<float>::max() <= x &&
+           x <= std::numeric_limits<float>::max();
+}
+
+template <>
+bool
+InRange<double, int>(int x)
+{
+    return -std::numeric_limits<double>::max() <= x &&
+           x <= std::numeric_limits<double>::max();
+}
+
+template <>
+bool
+InRange<float, double>(double x)
+{
+    return -std::numeric_limits<float>::max() <= x &&
+           x <= std::numeric_limits<float>::max();
+}
+
+template <>
+bool
+InRange<double, double>(double x)
+{
+    return -std::numeric_limits<double>::max() <= x &&
+           x <= std::numeric_limits<double>::max();
+}
+
+template <typename T>
+Class *
+js::NumericType<T>::typeToClass()
+{
+    abort();
+    return NULL;
+}
+
+#define BINARYDATA_TYPE_TO_CLASS(constant_, type_)\
+    template <>\
+    Class *\
+    NumericType<type_##_t>::typeToClass()\
+    {\
+        return &NumericTypeClasses[constant_];\
+    }
+
+/**
+ * This namespace declaration is required because of a weird 'specialization in
+ * different namespace' error that happens in gcc, only on type specialized
+ * template functions.
+ */
+namespace js {
+    BINARYDATA_FOR_EACH_NUMERIC_TYPES(BINARYDATA_TYPE_TO_CLASS);
+}
+
+template <typename T>
+bool
+NumericType<T>::convert(JSContext *cx, HandleValue val, T* converted)
+{
+    if (val.isInt32()) {
+        *converted = T(val.toInt32());
+        return true;
+    }
+
+    double d;
+    if (!ToDoubleForTypedArray(cx, val, &d)) {
+        Class *typeClass = typeToClass();
+        ReportTypeError(cx, val, typeClass->name);
+        return false;
+    }
+
+    if (TypeIsFloatingPoint<T>()) {
+        *converted = T(d);
+    } else if (TypeIsUnsigned<T>()) {
+        uint32_t n = ToUint32(d);
+        *converted = T(n);
+    } else {
+        int32_t n = ToInt32(d);
+        *converted = T(n);
+    }
+
+    return true;
+}
+
+template <typename T>
+JSBool
+NumericType<T>::call(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (args.length() < 1) {
+        char *fnName = JS_EncodeString(cx, args.callee().as<JSFunction>().atom());
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
+                             fnName, "0", "s");
+        JS_free(cx, (void *) fnName);
+        return false;
+    }
+
+    RootedValue arg(cx, args[0]);
+    T answer;
+    if (!convert(cx, arg, &answer))
+        return false; // convert() raises TypeError.
+
+    RootedValue reified(cx);
+    if (!NumericType<T>::reify(cx, &answer, &reified)) {
+        return false;
+    }
+
+    args.rval().set(reified);
+    return true;
+}
+
+template<unsigned int N>
+JSBool
+NumericTypeToString(JSContext *cx, unsigned int argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    JS_ASSERT(NUMERICTYPE_UINT8 <= N && N <= NUMERICTYPE_FLOAT64);
+    JSString *s = JS_NewStringCopyZ(cx, NumericTypeClasses[N].name);
+    args.rval().set(StringValue(s));
+    return true;
+}
+
+/*
+ * When creating:
+ *   var A = new ArrayType(uint8, 10)
+ * or
+ *   var S = new StructType({...})
+ *
+ * A.prototype.__proto__ === ArrayType.prototype.prototype (and similar for
+ * StructType).
+ *
+ * This function takes a reference to either ArrayType or StructType and
+ * returns a JSObject which can be set as A.prototype.
+ */
+JSObject *
+SetupAndGetPrototypeObjectForComplexTypeInstance(JSContext *cx,
+                                                 HandleObject complexTypeGlobal)
+{
+    RootedObject global(cx, cx->compartment()->maybeGlobal());
+    RootedValue complexTypePrototypeVal(cx);
+    RootedValue complexTypePrototypePrototypeVal(cx);
+
+    if (!JSObject::getProperty(cx, complexTypeGlobal, complexTypeGlobal,
+                               cx->names().classPrototype, &complexTypePrototypeVal))
+        return NULL;
+
+    RootedObject complexTypePrototypeObj(cx,
+        complexTypePrototypeVal.toObjectOrNull());
+
+    if (!JSObject::getProperty(cx, complexTypePrototypeObj,
+                               complexTypePrototypeObj,
+                               cx->names().classPrototype,
+                               &complexTypePrototypePrototypeVal))
+        return NULL;
+
+    RootedObject prototypeObj(cx,
+        NewObjectWithGivenProto(cx, &JSObject::class_, NULL, global));
+
+    if (!JS_SetPrototype(cx, prototypeObj,
+                         complexTypePrototypePrototypeVal.toObjectOrNull()))
+        return NULL;
+
+    return prototypeObj;
+}
+
+Class ArrayType::class_ = {
+    "ArrayType",
+    JSCLASS_HAS_RESERVED_SLOTS(TYPE_RESERVED_SLOTS) |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayType),
+    JS_PropertyStub,
+    JS_DeletePropertyStub,
+    JS_PropertyStub,
+    JS_StrictPropertyStub,
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    JS_ConvertStub,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    BinaryArray::construct,
+    NULL
+};
+
+Class BinaryArray::class_ = {
+    "BinaryArray",
+    Class::NON_NATIVE |
+    JSCLASS_HAS_RESERVED_SLOTS(BLOCK_RESERVED_SLOTS) |
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayType),
+    JS_PropertyStub,
+    JS_DeletePropertyStub,
+    JS_PropertyStub,
+    JS_StrictPropertyStub,
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    JS_ConvertStub,
+    BinaryArray::finalize,
+    NULL,           /* checkAccess */
+    NULL,           /* call        */
+    NULL,           /* construct   */
+    NULL,           /* hasInstance */
+    BinaryArray::obj_trace,
+    JS_NULL_CLASS_EXT,
+    {
+        BinaryArray::obj_lookupGeneric,
+        BinaryArray::obj_lookupProperty,
+        BinaryArray::obj_lookupElement,
+        BinaryArray::obj_lookupSpecial,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        BinaryArray::obj_getGeneric,
+        BinaryArray::obj_getProperty,
+        BinaryArray::obj_getElement,
+        BinaryArray::obj_getElementIfPresent,
+        BinaryArray::obj_getSpecial,
+        BinaryArray::obj_setGeneric,
+        BinaryArray::obj_setProperty,
+        BinaryArray::obj_setElement,
+        BinaryArray::obj_setSpecial,
+        BinaryArray::obj_getGenericAttributes,
+        BinaryArray::obj_getPropertyAttributes,
+        BinaryArray::obj_getElementAttributes,
+        BinaryArray::obj_getSpecialAttributes,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        BinaryArray::obj_enumerate,
+        NULL,
+    }
+};
+
+inline uint32_t
+ArrayType::length(JSContext *cx, HandleObject obj)
+{
+    JS_ASSERT(obj && IsArrayType(obj));
+    RootedValue vp(cx, UndefinedValue());
+    if (!JSObject::getProperty(cx, obj, obj, cx->names().length, &vp))
+        return -1;
+    JS_ASSERT(vp.isInt32());
+    JS_ASSERT(vp.toInt32() >= 0);
+    return (uint32_t) vp.toInt32();
+}
+
+inline JSObject *
+ArrayType::elementType(JSContext *cx, HandleObject array)
+{
+    JS_ASSERT(IsArrayType(array));
+    RootedObject arr(cx, array);
+    RootedValue elementTypeVal(cx);
+    if (!JSObject::getProperty(cx, arr, arr,
+                               cx->names().elementType, &elementTypeVal))
+
+    JS_ASSERT(elementTypeVal.isObject());
+    return elementTypeVal.toObjectOrNull();
+}
+
+bool
+ArrayType::convertAndCopyTo(JSContext *cx, HandleObject exemplar,
+                            HandleValue from, uint8_t *mem)
+{
+    if (!from.isObject()) {
+        return ReportTypeError(cx, from, exemplar);
+    }
+
+    RootedObject val(cx, from.toObjectOrNull());
+    if (IsBlock(val)) {
+        RootedObject type(cx, GetType(val));
+        if (IsSameBinaryDataType(cx, exemplar, type)) {
+            uint8_t *priv = (uint8_t*) val->getPrivate();
+            memcpy(mem, priv, GetMemSize(cx, exemplar));
+            return true;
+        }
+        return ReportTypeError(cx, from, exemplar);
+    }
+
+    RootedObject valRooted(cx, val);
+    RootedValue fromLenVal(cx);
+    if (!JSObject::getProperty(cx, valRooted, valRooted,
+                               cx->names().length, &fromLenVal) ||
+        !fromLenVal.isInt32())
+    {
+        return ReportTypeError(cx, from, exemplar);
+    }
+
+    uint32_t fromLen = fromLenVal.toInt32();
+
+    if (ArrayType::length(cx, exemplar) != fromLen) {
+        return ReportTypeError(cx, from, exemplar);
+    }
+
+    RootedObject elementType(cx, ArrayType::elementType(cx, exemplar));
+
+    uint32_t offsetMult = GetMemSize(cx, elementType);
+
+    for (uint32_t i = 0; i < fromLen; i++) {
+        RootedValue fromElem(cx);
+        if (!JSObject::getElement(cx, valRooted, valRooted, i, &fromElem)) {
+            return ReportTypeError(cx, from, exemplar);
+        }
+
+        if (!ConvertAndCopyTo(cx, elementType, fromElem,
+                              (uint8_t *) mem + (offsetMult * i))) {
+            return false; // TypeError raised by ConvertAndCopyTo.
+        }
+    }
+
+    return true;
+}
+
+inline bool
+ArrayType::reify(JSContext *cx, HandleObject type,
+                 HandleObject owner, size_t offset, MutableHandleValue to)
+{
+    JSObject *obj = BinaryArray::create(cx, type, owner, offset);
+    if (!obj)
+        return false;
+    to.setObject(*obj);
+    return true;
+}
+
+JSObject *
+ArrayType::create(JSContext *cx, HandleObject arrayTypeGlobal,
+                  HandleObject elementType, uint32_t length)
+{
+    JS_ASSERT(elementType);
+    JS_ASSERT(IsBinaryType(elementType));
+
+    RootedObject obj(cx, NewBuiltinClassInstance(cx, &ArrayType::class_));
+    if (!obj)
+        return NULL;
+
+    RootedValue elementTypeVal(cx, ObjectValue(*elementType));
+    if (!JSObject::defineProperty(cx, obj, cx->names().elementType,
+                                  elementTypeVal, NULL, NULL,
+                                  JSPROP_READONLY | JSPROP_PERMANENT))
+        return NULL;
+
+    RootedValue lengthVal(cx, Int32Value(length));
+    if (!JSObject::defineProperty(cx, obj, cx->names().length,
+                                  lengthVal, NULL, NULL,
+                                  JSPROP_READONLY | JSPROP_PERMANENT))
+        return NULL;
+
+    RootedValue elementTypeBytes(cx);
+    if (!JSObject::getProperty(cx, elementType, elementType, cx->names().bytes, &elementTypeBytes))
+        return NULL;
+
+    JS_ASSERT(elementTypeBytes.isInt32());
+
+    /* since this is the JS visible size and maybe not
+     * the actual size in terms of memory layout, it is
+     * always elementType.bytes * length */
+    RootedValue typeBytes(cx, NumberValue(elementTypeBytes.toInt32() * length));
+    if (!JSObject::defineProperty(cx, obj, cx->names().bytes,
+                                  typeBytes,
+                                  NULL, NULL, JSPROP_READONLY | JSPROP_PERMANENT))
+        return NULL;
+
+    obj->setFixedSlot(SLOT_MEMSIZE,
+                      Int32Value(::GetMemSize(cx, elementType) * length));
+
+    obj->setFixedSlot(SLOT_ALIGN, Int32Value(::GetAlign(cx, elementType)));
+
+    RootedObject prototypeObj(cx,
+        SetupAndGetPrototypeObjectForComplexTypeInstance(cx, arrayTypeGlobal));
+
+    if (!prototypeObj)
+        return NULL;
+
+    if (!LinkConstructorAndPrototype(cx, obj, prototypeObj))
+        return NULL;
+
+    JSFunction *fillFun = DefineFunctionWithReserved(cx, prototypeObj, "fill", BinaryArray::fill, 1, 0);
+    if (!fillFun)
+        return NULL;
+
+    // This is important
+    // so that A.prototype.fill.call(b, val)
+    // where b.type != A raises an error
+    SetFunctionNativeReserved(fillFun, 0, ObjectValue(*obj));
+
+    RootedId id(cx, NON_INTEGER_ATOM_TO_JSID(cx->names().length));
+    unsigned flags = JSPROP_SHARED | JSPROP_GETTER | JSPROP_PERMANENT;
+
+    RootedObject global(cx, cx->compartment()->maybeGlobal());
+    JSObject *getter =
+        NewFunction(cx, NullPtr(), BinaryArray::lengthGetter,
+                    0, JSFunction::NATIVE_FUN, global, NullPtr());
+    if (!getter)
+        return NULL;
+
+    RootedValue value(cx);
+    if (!DefineNativeProperty(cx, prototypeObj, id, value,
+                                JS_DATA_TO_FUNC_PTR(PropertyOp, getter), NULL,
+                                flags, 0, 0))
+        return NULL;
+    return obj;
+}
+
+JSBool
+ArrayType::construct(JSContext *cx, unsigned argc, Value *vp)
+{
+    if (!JS_IsConstructing(cx, vp)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                             JSMSG_NOT_FUNCTION, "ArrayType");
+        return false;
+    }
+
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (argc != 2 ||
+        !args[0].isObject() ||
+        !args[1].isNumber() ||
+        args[1].toNumber() < 0)
+    {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                             JSMSG_BINARYDATA_ARRAYTYPE_BAD_ARGS);
+        return false;
+    }
+
+    RootedObject arrayTypeGlobal(cx, &args.callee());
+    RootedObject elementType(cx, args[0].toObjectOrNull());
+
+    if (!IsBinaryType(elementType)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                             JSMSG_BINARYDATA_ARRAYTYPE_BAD_ARGS);
+        return false;
+    }
+
+    JSObject *obj = create(cx, arrayTypeGlobal, elementType, args[1].toInt32());
+    if (!obj)
+        return false;
+    args.rval().setObject(*obj);
+    return true;
+}
+
+JSBool
+DataInstanceUpdate(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage,
+                             NULL, JSMSG_MORE_ARGS_NEEDED,
+                             "update()", "0", "s");
+        return false;
+    }
+
+    RootedObject thisObj(cx, args.thisv().toObjectOrNull());
+    if (!IsBlock(thisObj)) {
+        ReportTypeError(cx, ObjectValue(*thisObj), "BinaryData block");
+        return false;
+    }
+
+    RootedValue val(cx, args[0]);
+    uint8_t *memory = (uint8_t*) thisObj->getPrivate();
+    RootedObject type(cx, GetType(thisObj));
+    if (!ConvertAndCopyTo(cx, type, val, memory)) {
+        ReportTypeError(cx, val, type);
+        return false;
+    }
+
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
+FillBinaryArrayWithValue(JSContext *cx, HandleObject array, HandleValue val)
+{
+    JS_ASSERT(IsBinaryArray(array));
+
+    RootedObject type(cx, GetType(array));
+    RootedObject elementType(cx, ArrayType::elementType(cx, type));
+
+    uint8_t *base = (uint8_t *) array->getPrivate();
+
+    // set array[0] = [[Convert]](val)
+    if (!ConvertAndCopyTo(cx, elementType, val, base)) {
+        ReportTypeError(cx, val, elementType);
+        return false;
+    }
+
+    size_t elementSize = GetMemSize(cx, elementType);
+    // Copy a[0] into remaining indices.
+    for (uint32_t i = 1; i < ArrayType::length(cx, type); i++) {
+        uint8_t *dest = base + elementSize * i;
+        memcpy(dest, base, elementSize);
+    }
+
+    return true;
+}
+
+JSBool
+ArrayType::repeat(JSContext *cx, unsigned int argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage,
+                             NULL, JSMSG_MORE_ARGS_NEEDED,
+                             "repeat()", "0", "s");
+        return false;
+    }
+
+    RootedObject thisObj(cx, args.thisv().toObjectOrNull());
+    if (!IsArrayType(thisObj)) {
+        JSString *valueStr = JS_ValueToString(cx, args.thisv());
+        char *valueChars = "(unknown type)";
+        if (valueStr)
+            valueChars = JS_EncodeString(cx, valueStr);
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO, "ArrayType", "repeat", valueChars);
+        if (valueStr)
+            JS_free(cx, valueChars);
+        return false;
+    }
+
+    RootedObject binaryArray(cx, BinaryArray::create(cx, thisObj));
+    if (!binaryArray)
+        return false;
+
+    RootedValue val(cx, args[0]);
+    if (!FillBinaryArrayWithValue(cx, binaryArray, val))
+        return false;
+
+    args.rval().setObject(*binaryArray);
+    return true;
+}
+
+JSBool
+ArrayType::toString(JSContext *cx, unsigned int argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedObject thisObj(cx, args.thisv().toObjectOrNull());
+    JS_ASSERT(thisObj);
+    if (!IsArrayType(thisObj)) {
+        RootedObject obj(cx, args.thisv().toObjectOrNull());
+        JS_ReportErrorNumber(cx, js_GetErrorMessage,
+                             NULL, JSMSG_INCOMPATIBLE_PROTO, "ArrayType", "toString", JS_GetClass(obj)->name);
+        return false;
+    }
+
+    StringBuffer contents(cx);
+    contents.append("ArrayType(");
+
+    RootedValue elementTypeVal(cx, ObjectValue(*elementType(cx, thisObj)));
+    JSString *str = ToString<CanGC>(cx, elementTypeVal);
+
+    contents.append(str);
+    contents.append(", ");
+
+    Value len = NumberValue(length(cx, thisObj));
+    contents.append(JS_ValueToString(cx, len));
+    contents.append(")");
+    args.rval().setString(contents.finishString());
+    return true;
+}
+
+JSObject *
+BinaryArray::createEmpty(JSContext *cx, HandleObject type)
+{
+    JS_ASSERT(IsArrayType(type));
+    RootedObject typeRooted(cx, type);
+
+    RootedValue protoVal(cx);
+    if (!JSObject::getProperty(cx, typeRooted, typeRooted,
+                               cx->names().classPrototype, &protoVal))
+        return NULL;
+
+    RootedObject obj(cx,
+        NewObjectWithClassProto(cx, &BinaryArray::class_,
+                                protoVal.toObjectOrNull(), NULL));
+    obj->setFixedSlot(SLOT_DATATYPE, ObjectValue(*type));
+    obj->setFixedSlot(SLOT_BLOCKREFOWNER, NullValue());
+    return obj;
+}
+
+JSObject *
+BinaryArray::create(JSContext *cx, HandleObject type)
+{
+    JSObject *obj = createEmpty(cx, type);
+    if (!obj)
+        return NULL;
+
+    int32_t memsize = GetMemSize(cx, type);
+    void *memory = JS_malloc(cx, memsize);
+    if (!memory)
+        return NULL;
+    memset(memory, 0, memsize);
+    obj->setPrivate(memory);
+    return obj;
+}
+
+JSObject *
+BinaryArray::create(JSContext *cx, HandleObject type, HandleValue initial)
+{
+    JSObject *obj = create(cx, type);
+    if (!obj)
+        return NULL;
+
+    uint8_t *memory = (uint8_t*) obj->getPrivate();
+    if (!ConvertAndCopyTo(cx, type, initial, memory))
+        return NULL;
+
+    return obj;
+}
+
+JSObject *
+BinaryArray::create(JSContext *cx, HandleObject type,
+                    HandleObject owner, size_t offset)
+{
+    JS_ASSERT(IsBlock(owner));
+    JSObject *obj = createEmpty(cx, type);
+    if (!obj)
+        return NULL;
+
+    obj->setPrivate(((uint8_t *) owner->getPrivate()) + offset);
+    obj->setFixedSlot(SLOT_BLOCKREFOWNER, ObjectValue(*owner));
+    return obj;
+}
+
+JSBool
+BinaryArray::construct(JSContext *cx, unsigned int argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedObject callee(cx, &args.callee());
+
+    if (!IsArrayType(callee)) {
+        ReportTypeError(cx, args.calleev(), "is not an ArrayType");
+        return false;
+    }
+
+    JSObject *obj = NULL;
+    if (argc == 1) {
+        RootedValue v(cx, args[0]);
+        obj = create(cx, callee, v);
+    } else {
+        obj = create(cx, callee);
+    }
+
+    if (obj)
+        args.rval().setObject(*obj);
+
+    return obj != NULL;
+}
+
+void
+BinaryArray::finalize(js::FreeOp *op, JSObject *obj)
+{
+    if (obj->getFixedSlot(SLOT_BLOCKREFOWNER).isNull())
+        op->free_(obj->getPrivate());
+}
+
+void
+BinaryArray::obj_trace(JSTracer *tracer, JSObject *obj)
+{
+    Value val = obj->getFixedSlot(SLOT_BLOCKREFOWNER);
+    if (val.isObject()) {
+        HeapPtrObject owner(val.toObjectOrNull());
+        MarkObject(tracer, &owner, "binaryarray.blockRefOwner");
+    }
+}
+
+JSBool
+BinaryArray::lengthGetter(JSContext *cx, unsigned int argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    RootedObject thisObj(cx, args.thisv().toObjectOrNull());
+    JS_ASSERT(IsBinaryArray(thisObj));
+
+    RootedObject type(cx, GetType(thisObj));
+    vp->setInt32(ArrayType::length(cx, type));
+    return true;
+}
+
+/**
+ * The subarray function first creates an ArrayType instance
+ * which will act as the elementType for the subarray.
+ *
+ * var MA = new ArrayType(elementType, 10);
+ * var mb = MA.repeat(val);
+ *
+ * mb.subarray(begin, end=mb.length) => (Only for +ve)
+ *     var internalSA = new ArrayType(elementType, end-begin);
+ *     var ret = new internalSA()
+ *     for (var i = begin; i < end; i++)
+ *         ret[i-begin] = ret[i]
+ *     return ret
+ *
+ * The range specified by the begin and end values is clamped to the valid
+ * index range for the current array. If the computed length of the new
+ * TypedArray would be negative, it is clamped to zero.
+ * see: http://www.khronos.org/registry/typedarray/specs/latest/#7
+ *
+ */
+JSBool BinaryArray::subarray(JSContext *cx, unsigned int argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage,
+                             NULL, JSMSG_MORE_ARGS_NEEDED,
+                             "subarray()", "0", "s");
+        return false;
+    }
+
+    if (!args[0].isInt32()) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                             JSMSG_BINARYDATA_SUBARRAY_INTEGER_ARG, "1");
+        return false;
+    }
+
+    RootedObject thisObj(cx, &args.thisv().toObject());
+    if (!IsBinaryArray(thisObj)) {
+        ReportTypeError(cx, ObjectValue(*thisObj), "binary array");
+        return false;
+    }
+
+    RootedObject type(cx, GetType(thisObj));
+    RootedObject elementType(cx, ArrayType::elementType(cx, type));
+    uint32_t length = ArrayType::length(cx, type);
+
+    int32_t begin = args[0].toInt32();
+    int32_t end = length;
+
+    if (args.length() >= 2) {
+        if (!args[1].isInt32()) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                    JSMSG_BINARYDATA_SUBARRAY_INTEGER_ARG, "2");
+            return false;
+        }
+
+        end = args[1].toInt32();
+    }
+
+    if (begin < 0)
+        begin = length + begin;
+    if (end < 0)
+        end = length + end;
+
+    begin = Clamp(begin, 0, length);
+    end = Clamp(end, 0, length);
+
+    int32_t sublength = end - begin; // end exclusive
+    sublength = Clamp(sublength, 0, length);
+
+    RootedObject globalObj(cx, cx->compartment()->maybeGlobal());
+    JS_ASSERT(globalObj);
+    Rooted<GlobalObject*> global(cx, &globalObj->as<GlobalObject>());
+    RootedObject arrayTypeGlobal(cx, global->getOrCreateArrayTypeObject(cx));
+
+    RootedObject subArrayType(cx, ArrayType::create(cx, arrayTypeGlobal,
+                                                    elementType, sublength));
+    if (!subArrayType)
+        return false;
+
+    int32_t elementSize = GetMemSize(cx, elementType);
+    size_t offset = elementSize * begin;
+
+    RootedObject subarray(cx, BinaryArray::create(cx, subArrayType, thisObj, offset));
+    if (!subarray)
+        return false;
+
+    args.rval().setObject(*subarray);
+    return true;
+}
+
+JSBool
+BinaryArray::fill(JSContext *cx, unsigned int argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage,
+                             NULL, JSMSG_MORE_ARGS_NEEDED,
+                             "fill()", "0", "s");
+        return false;
+    }
+
+    if (!args.thisv().isObject())
+        return false;
+
+    RootedObject thisObj(cx, args.thisv().toObjectOrNull());
+    if (!IsBinaryArray(thisObj)) {
+        ReportTypeError(cx, ObjectValue(*thisObj), "binary array");
+        return false;
+    }
+
+    Value funArrayTypeVal = GetFunctionNativeReserved(&args.callee(), 0);
+    JS_ASSERT(funArrayTypeVal.isObject());
+
+    RootedObject type(cx, GetType(thisObj));
+    RootedObject funArrayType(cx, funArrayTypeVal.toObjectOrNull());
+    if (!IsSameBinaryDataType(cx, funArrayType, type)) {
+        ReportTypeError(cx, ObjectValue(*thisObj), funArrayType);
+        return false;
+    }
+
+    args.rval().setUndefined();
+    RootedValue val(cx, args[0]);
+    return FillBinaryArrayWithValue(cx, thisObj, val);
+}
+
+JSBool
+BinaryArray::obj_lookupGeneric(JSContext *cx, HandleObject obj, HandleId id,
+                                MutableHandleObject objp, MutableHandleShape propp)
+{
+    JS_ASSERT(IsBinaryArray(obj));
+    RootedObject type(cx, GetType(obj));
+
+    uint32_t index;
+    if (js_IdIsIndex(id, &index) &&
+        index < ArrayType::length(cx, type)) {
+        MarkNonNativePropertyFound(propp);
+        objp.set(obj);
+        return true;
+    }
+
+    if (JSID_IS_ATOM(id, cx->names().length)) {
+        MarkNonNativePropertyFound(propp);
+        objp.set(obj);
+        return true;
+    }
+
+    RootedObject proto(cx, obj->getProto());
+    if (!proto) {
+        objp.set(NULL);
+        propp.set(NULL);
+        return true;
+    }
+
+    return JSObject::lookupGeneric(cx, proto, id, objp, propp);
+}
+
+JSBool
+BinaryArray::obj_lookupProperty(JSContext *cx,
+                                HandleObject obj,
+                                HandlePropertyName name,
+                                MutableHandleObject objp,
+                                MutableHandleShape propp)
+{
+    RootedId id(cx, NameToId(name));
+    return obj_lookupGeneric(cx, obj, id, objp, propp);
+}
+
+JSBool
+BinaryArray::obj_lookupElement(JSContext *cx, HandleObject obj, uint32_t index,
+                                MutableHandleObject objp, MutableHandleShape propp)
+{
+    JS_ASSERT(IsBinaryArray(obj));
+    RootedObject type(cx, GetType(obj));
+
+    if (index < ArrayType::length(cx, type)) {
+        MarkNonNativePropertyFound(propp);
+        objp.set(obj);
+        return true;
+    }
+
+    RootedObject proto(cx, obj->getProto());
+    if (proto)
+        return JSObject::lookupElement(cx, proto, index, objp, propp);
+
+    objp.set(NULL);
+    propp.set(NULL);
+    return true;
+}
+
+JSBool
+BinaryArray::obj_lookupSpecial(JSContext *cx, HandleObject obj,
+                               HandleSpecialId sid, MutableHandleObject objp,
+                               MutableHandleShape propp)
+{
+    RootedId id(cx, SPECIALID_TO_JSID(sid));
+    return obj_lookupGeneric(cx, obj, id, objp, propp);
+}
+
+JSBool
+BinaryArray::obj_getGeneric(JSContext *cx, HandleObject obj, HandleObject receiver,
+                             HandleId id, MutableHandleValue vp)
+{
+    uint32_t index;
+    if (js_IdIsIndex(id, &index)) {
+        return obj_getElement(cx, obj, receiver, index, vp);
+    }
+
+    RootedValue idValue(cx, IdToValue(id));
+    Rooted<PropertyName*> name(cx, ToAtom<CanGC>(cx, idValue)->asPropertyName());
+    return obj_getProperty(cx, obj, receiver, name, vp);
+}
+
+JSBool
+BinaryArray::obj_getProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
+                              HandlePropertyName name, MutableHandleValue vp)
+{
+    RootedObject proto(cx, obj->getProto());
+    if (!proto) {
+        vp.setUndefined();
+        return true;
+    }
+
+    return JSObject::getProperty(cx, proto, receiver, name, vp);
+}
+
+JSBool
+BinaryArray::obj_getElement(JSContext *cx, HandleObject obj, HandleObject receiver,
+                             uint32_t index, MutableHandleValue vp)
+{
+    RootedObject type(cx, GetType(obj));
+
+    if (index < ArrayType::length(cx, type)) {
+        RootedObject elementType(cx, ArrayType::elementType(cx, type));
+        size_t offset = GetMemSize(cx, elementType) * index;
+        return Reify(cx, elementType, obj, offset, vp);
+    }
+
+    RootedObject proto(cx, obj->getProto());
+    if (!proto) {
+        vp.setUndefined();
+        return true;
+    }
+
+    return JSObject::getElement(cx, proto, receiver, index, vp);
+}
+
+JSBool
+BinaryArray::obj_getElementIfPresent(JSContext *cx, HandleObject obj,
+                                     HandleObject receiver, uint32_t index,
+                                     MutableHandleValue vp, bool *present)
+{
+    RootedObject type(cx, GetType(obj));
+
+    if (index < ArrayType::length(cx, type)) {
+        *present = true;
+        return obj_getElement(cx, obj, receiver, index, vp);
+    }
+
+    *present = false;
+    vp.setUndefined();
+    return true;
+}
+
+JSBool
+BinaryArray::obj_getSpecial(JSContext *cx, HandleObject obj,
+                            HandleObject receiver, HandleSpecialId sid,
+                            MutableHandleValue vp)
+{
+    RootedId id(cx, SPECIALID_TO_JSID(sid));
+    return obj_getGeneric(cx, obj, receiver, id, vp);
+}
+
+JSBool
+BinaryArray::obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id,
+                             MutableHandleValue vp, JSBool strict)
+{
+	uint32_t index;
+	if (js_IdIsIndex(id, &index)) {
+	    return obj_setElement(cx, obj, index, vp, strict);
+    }
+
+    if (JSID_IS_ATOM(id, cx->names().length)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage,
+                             NULL, JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
+        return false;
+    }
+
+	return true;
+}
+
+JSBool
+BinaryArray::obj_setProperty(JSContext *cx, HandleObject obj,
+                             HandlePropertyName name, MutableHandleValue vp,
+                             JSBool strict)
+{
+    RootedId id(cx, NameToId(name));
+    return obj_setGeneric(cx, obj, id, vp, strict);
+}
+
+JSBool
+BinaryArray::obj_setElement(JSContext *cx, HandleObject obj, uint32_t index,
+                             MutableHandleValue vp, JSBool strict)
+{
+    RootedObject type(cx, GetType(obj));
+    if (index >= ArrayType::length(cx, type)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage,
+                             NULL, JSMSG_BINARYDATA_BINARYARRAY_BAD_INDEX);
+        return false;
+    }
+
+    RootedValue elementTypeVal(cx);
+    if (!JSObject::getProperty(cx, type, type, cx->names().elementType,
+                               &elementTypeVal))
+        return false;
+
+    RootedObject elementType(cx, elementTypeVal.toObjectOrNull());
+    uint32_t offset = GetMemSize(cx, elementType) * index;
+
+    bool result =
+        ConvertAndCopyTo(cx, elementType, vp,
+                         ((uint8_t*) obj->getPrivate()) + offset );
+
+    if (!result) {
+        return false;
+    }
+
+    return true;
+}
+
+JSBool
+BinaryArray::obj_setSpecial(JSContext *cx, HandleObject obj,
+                             HandleSpecialId sid, MutableHandleValue vp,
+                             JSBool strict)
+{
+    RootedId id(cx, SPECIALID_TO_JSID(sid));
+    return obj_setGeneric(cx, obj, id, vp, strict);
+}
+
+JSBool
+BinaryArray::obj_getGenericAttributes(JSContext *cx, HandleObject obj,
+                                       HandleId id, unsigned *attrsp)
+{
+    uint32_t index;
+    RootedObject type(cx, GetType(obj));
+
+    if (js_IdIsIndex(id, &index) &&
+        index < ArrayType::length(cx, type)) {
+        *attrsp = JSPROP_ENUMERATE | JSPROP_PERMANENT; // should we report JSPROP_INDEX?
+        return true;
+    }
+
+    if (JSID_IS_ATOM(id, cx->names().length)) {
+        *attrsp = JSPROP_READONLY | JSPROP_PERMANENT;
+        return true;
+    }
+
+	return false;
+}
+
+JSBool
+BinaryArray::obj_getPropertyAttributes(JSContext *cx, HandleObject obj,
+                                        HandlePropertyName name,
+                                        unsigned *attrsp)
+{
+    RootedId id(cx, NameToId(name));
+    return obj_getGenericAttributes(cx, obj, id, attrsp);
+}
+
+JSBool
+BinaryArray::obj_getElementAttributes(JSContext *cx, HandleObject obj,
+                                       uint32_t index, unsigned *attrsp)
+{
+    RootedId id(cx, INT_TO_JSID(index));
+    return obj_getGenericAttributes(cx, obj, id, attrsp);
+}
+
+JSBool
+BinaryArray::obj_getSpecialAttributes(JSContext *cx, HandleObject obj,
+                                       HandleSpecialId sid, unsigned *attrsp)
+{
+    RootedId id(cx, SPECIALID_TO_JSID(sid));
+    return obj_getGenericAttributes(cx, obj, id, attrsp);
+}
+
+JSBool
+BinaryArray::obj_enumerate(JSContext *cx, HandleObject obj, JSIterateOp enum_op,
+                            MutableHandleValue statep, MutableHandleId idp)
+{
+    JS_ASSERT(IsBinaryArray(obj));
+
+    RootedObject type(cx, GetType(obj));
+
+    uint32_t index;
+    switch (enum_op) {
+        case JSENUMERATE_INIT_ALL:
+        case JSENUMERATE_INIT:
+            statep.setInt32(0);
+            idp.set(INT_TO_JSID(ArrayType::length(cx, type)));
+            break;
+
+        case JSENUMERATE_NEXT:
+            index = static_cast<uint32_t>(statep.toInt32());
+
+            if (index < ArrayType::length(cx, type)) {
+                idp.set(INT_TO_JSID(index));
+                statep.setInt32(index + 1);
+            } else {
+                JS_ASSERT(index == ArrayType::length(cx, type));
+                statep.setNull();
+            }
+
+            break;
+
+        case JSENUMERATE_DESTROY:
+            statep.setNull();
+            break;
+    }
+
+	return true;
+}
+
+/*********************************
+ * Structs
+ *********************************/
+Class StructType::class_ = {
+    "StructType",
+    JSCLASS_HAS_RESERVED_SLOTS(TYPE_RESERVED_SLOTS) |
+    JSCLASS_HAS_PRIVATE | // used to store FieldList
+    JSCLASS_HAS_CACHED_PROTO(JSProto_StructType),
+    JS_PropertyStub,
+    JS_DeletePropertyStub,
+    JS_PropertyStub,
+    JS_StrictPropertyStub,
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    JS_ConvertStub,
+    StructType::finalize,
+    NULL,
+    NULL,
+    NULL,
+    BinaryStruct::construct,
+    NULL
+};
+
+Class BinaryStruct::class_ = {
+    "BinaryStruct",
+    Class::NON_NATIVE |
+    JSCLASS_HAS_RESERVED_SLOTS(BLOCK_RESERVED_SLOTS) |
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_StructType),
+    JS_PropertyStub,
+    JS_DeletePropertyStub,
+    JS_PropertyStub,
+    JS_StrictPropertyStub,
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    JS_ConvertStub,
+    BinaryStruct::finalize,
+    NULL,           /* checkAccess */
+    NULL,           /* call        */
+    NULL,           /* construct   */
+    NULL,           /* hasInstance */
+    BinaryStruct::obj_trace,
+    JS_NULL_CLASS_EXT,
+    {
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        BinaryStruct::obj_getGeneric,
+        BinaryStruct::obj_getProperty,
+        NULL,
+        NULL,
+        BinaryStruct::obj_getSpecial,
+        BinaryStruct::obj_setGeneric,
+        BinaryStruct::obj_setProperty,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+        NULL,
+    }
+};
+
+/*
+ * NOTE: layout() does not check for duplicates in fields since the arguments
+ * to StructType are currently passed as an object literal. Fix this if it
+ * changes to taking an array of arrays.
+ */
+bool
+StructType::layout(JSContext *cx, HandleObject structType, HandleObject fields)
+{
+    AutoIdVector fieldProps(cx);
+    if (!GetPropertyNames(cx, fields, JSITER_OWNONLY, &fieldProps))
+        return false;
+
+    FieldList *fieldList = new FieldList(fieldProps.length());
+
+    uint32_t structAlign = 0;
+    uint32_t structMemSize = 0;
+    uint32_t structByteSize = 0;
+
+    for (unsigned int i = 0; i < fieldProps.length(); i++) {
+        RootedValue fieldTypeVal(cx);
+        RootedId id(cx, fieldProps[i]);
+        if (!JSObject::getGeneric(cx, fields, fields, id, &fieldTypeVal))
+            return false;
+
+        RootedObject fieldType(cx, fieldTypeVal.toObjectOrNull());
+        if (!IsBinaryType(fieldType))
+            return false;
+
+        size_t fieldMemSize = GetMemSize(cx, fieldType);
+        size_t fieldAlign = GetAlign(cx, fieldType);
+        size_t fieldOffset = AlignBytes(structMemSize, fieldAlign);
+
+        structMemSize = fieldOffset + fieldMemSize;
+
+        if (fieldAlign > structAlign)
+            structAlign = fieldAlign;
+
+        RootedValue fieldTypeBytes(cx);
+        if (!JSObject::getProperty(cx, fieldType, fieldType, cx->names().bytes, &fieldTypeBytes))
+            return false;
+
+        JS_ASSERT(fieldTypeBytes.isInt32());
+        structByteSize += fieldTypeBytes.toInt32();
+
+        (*fieldList)[i].name = fieldProps[i];
+        (*fieldList)[i].type = fieldType.get();
+        (*fieldList)[i].offset = fieldOffset;
+    }
+
+    size_t structTail = AlignBytes(structMemSize, structAlign);
+    JS_ASSERT(structTail >= structMemSize);
+    structMemSize = structTail;
+
+    structType->setFixedSlot(SLOT_MEMSIZE, Int32Value(structMemSize));
+    structType->setFixedSlot(SLOT_ALIGN, Int32Value(structAlign));
+    structType->setPrivate(fieldList);
+
+    if (!JS_DefineProperty(cx, structType, "bytes",
+                           Int32Value(structByteSize), NULL, NULL,
+                           JSPROP_READONLY | JSPROP_PERMANENT))
+        return false;
+
+    return true;
+}
+
+bool
+StructType::convertAndCopyTo(JSContext *cx, HandleObject exemplar,
+                             HandleValue from, uint8_t *mem)
+{
+
+    if (!from.isObject()) {
+        return ReportTypeError(cx, from, exemplar);
+    }
+
+    RootedObject val(cx, from.toObjectOrNull());
+    if (IsBlock(val)) {
+        RootedObject type(cx, GetType(val));
+        if (IsSameBinaryDataType(cx, exemplar, type)) {
+            uint8_t *priv = (uint8_t*) val->getPrivate();
+            memcpy(mem, priv, GetMemSize(cx, exemplar));
+            return true;
+        }
+        return ReportTypeError(cx, from, exemplar);
+    }
+
+    RootedObject valRooted(cx, val);
+    AutoIdVector ownProps(cx);
+    if (!GetPropertyNames(cx, valRooted, JSITER_OWNONLY, &ownProps))
+        return ReportTypeError(cx, from, exemplar);
+
+    FieldList *fieldList = static_cast<FieldList *>(exemplar->getPrivate());
+
+    if (ownProps.length() != fieldList->size()) {
+        return ReportTypeError(cx, from, exemplar);
+    }
+
+    FieldInfo info;
+    for (unsigned int i = 0; i < ownProps.length(); i++) {
+        if (!LookupFieldList(fieldList, ownProps[i], &info)) {
+            return ReportTypeError(cx, from, exemplar);
+        }
+    }
+
+    for (FieldList::const_iterator it = fieldList->begin(); it != fieldList->end(); ++it) {
+        RootedPropertyName fieldName(cx, JSID_TO_ATOM(it->name)->asPropertyName());
+
+        RootedValue fromProp(cx);
+        if (!JSObject::getProperty(cx, valRooted, valRooted,
+                                   fieldName, &fromProp)) {
+            return ReportTypeError(cx, from, exemplar);
+        }
+
+        RootedObject fieldType(cx, it->type);
+        if (!ConvertAndCopyTo(cx, fieldType, fromProp,
+                              (uint8_t *) mem + it->offset)) {
+            return false; // TypeError raised by ConvertAndCopyTo.
+        }
+    }
+    return true;
+}
+
+bool
+StructType::reify(JSContext *cx, HandleObject type, HandleObject owner,
+                  size_t offset, MutableHandleValue to) {
+    JSObject *obj = BinaryStruct::create(cx, type, owner, offset);
+    if (!obj)
+        return false;
+    to.setObject(*obj);
+    return true;
+}
+
+JSObject *
+StructType::create(JSContext *cx, HandleObject structTypeGlobal,
+                   HandleObject fields)
+{
+    RootedObject obj(cx, NewBuiltinClassInstance(cx, &StructType::class_));
+    if (!obj)
+        return NULL;
+
+    if (!StructType::layout(cx, obj, fields)) {
+        ReportTypeError(cx, ObjectValue(*fields), "StructType field specifier");
+        return NULL;
+    }
+
+    RootedObject fieldsProto(cx);
+    if (!JSObject::getProto(cx, fields, &fieldsProto))
+        return NULL;
+
+    RootedObject clone(cx, CloneObject(cx, fields, fieldsProto, NullPtr()));
+    if (!clone)
+        return NULL;
+
+    if (!JS_DefineProperty(cx, obj, "fields",
+                           ObjectValue(*fields), NULL, NULL,
+                           JSPROP_READONLY | JSPROP_PERMANENT))
+        return NULL;
+
+    JSObject *prototypeObj =
+        SetupAndGetPrototypeObjectForComplexTypeInstance(cx, structTypeGlobal);
+
+    if (!prototypeObj)
+        return NULL;
+
+    if (!LinkConstructorAndPrototype(cx, obj, prototypeObj))
+        return NULL;
+
+    return obj;
+}
+
+JSBool
+StructType::construct(JSContext *cx, unsigned int argc, Value *vp)
+{
+    if (!JS_IsConstructing(cx, vp)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                             JSMSG_NOT_FUNCTION, "StructType");
+        return false;
+    }
+
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (argc >= 1 && args[0].isObject()) {
+        RootedObject structTypeGlobal(cx, &args.callee());
+        RootedObject fields(cx, args[0].toObjectOrNull());
+        JSObject *obj = create(cx, structTypeGlobal, fields);
+        if (!obj)
+            return false;
+        args.rval().setObject(*obj);
+        return true;
+    }
+
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                         JSMSG_BINARYDATA_STRUCTTYPE_BAD_ARGS);
+    return false;
+}
+
+void
+StructType::finalize(FreeOp *op, JSObject *obj)
+{
+    FieldList *list = static_cast<FieldList *>(obj->getPrivate());
+    delete list;
+}
+
+JSBool
+StructType::toString(JSContext *cx, unsigned int argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedObject thisObj(cx, args.thisv().toObjectOrNull());
+
+    if (!IsStructType(thisObj))
+        return false;
+
+    StringBuffer contents(cx);
+    contents.append("StructType({");
+
+    FieldList *fieldList = static_cast<FieldList *>(thisObj->getPrivate());
+    JS_ASSERT(fieldList);
+
+    for (FieldList::const_iterator it = fieldList->begin(); it != fieldList->end(); ++it) {
+        if (it != fieldList->begin())
+            contents.append(", ");
+
+        contents.append(IdToString(cx, it->name));
+        contents.append(": ");
+
+        Value fieldStringVal;
+        if (!JS_CallFunctionName(cx, it->type,
+                                 "toString", 0, NULL, &fieldStringVal))
+            return false;
+
+        contents.append(fieldStringVal.toString());
+    }
+
+    contents.append("})");
+
+    args.rval().setString(contents.finishString());
+    return true;
+}
+
+JSObject *
+BinaryStruct::createEmpty(JSContext *cx, HandleObject type)
+{
+    JS_ASSERT(IsStructType(type));
+    RootedObject typeRooted(cx, type);
+
+    RootedValue protoVal(cx);
+    if (!JSObject::getProperty(cx, typeRooted, typeRooted,
+                               cx->names().classPrototype, &protoVal))
+        return NULL;
+
+    RootedObject obj(cx,
+        NewObjectWithClassProto(cx, &BinaryStruct::class_,
+                                protoVal.toObjectOrNull(), NULL));
+
+    obj->setFixedSlot(SLOT_DATATYPE, ObjectValue(*type));
+    obj->setFixedSlot(SLOT_BLOCKREFOWNER, NullValue());
+    return obj;
+}
+
+JSObject *
+BinaryStruct::create(JSContext *cx, HandleObject type)
+{
+    JSObject *obj = createEmpty(cx, type);
+    if (!obj)
+        return NULL;
+
+    int32_t memsize = GetMemSize(cx, type);
+    void *memory = JS_malloc(cx, memsize);
+    if (!memory)
+        return NULL;
+    memset(memory, 0, memsize);
+    obj->setPrivate(memory);
+    return obj;
+}
+
+JSObject *
+BinaryStruct::create(JSContext *cx, HandleObject type,
+                     HandleObject owner, size_t offset)
+{
+    JS_ASSERT(IsBlock(owner));
+    JSObject *obj = createEmpty(cx, type);
+    if (!obj)
+        return NULL;
+
+    obj->setPrivate(((uint8_t*) owner->getPrivate()) + offset);
+    obj->setFixedSlot(SLOT_BLOCKREFOWNER, ObjectValue(*owner));
+    return obj;
+}
+
+JSBool
+BinaryStruct::construct(JSContext *cx, unsigned int argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    RootedObject callee(cx, &args.callee());
+
+    if (!IsStructType(callee)) {
+        ReportTypeError(cx, args.calleev(), "is not an StructType");
+        return false;
+    }
+
+    JSObject *obj = create(cx, callee);
+
+    if (obj)
+        args.rval().setObject(*obj);
+
+    return obj != NULL;
+}
+
+void
+BinaryStruct::finalize(js::FreeOp *op, JSObject *obj)
+{
+    if (obj->getFixedSlot(SLOT_BLOCKREFOWNER).isNull())
+        op->free_(obj->getPrivate());
+}
+
+void
+BinaryStruct::obj_trace(JSTracer *tracer, JSObject *obj)
+{
+    Value val = obj->getFixedSlot(SLOT_BLOCKREFOWNER);
+    if (val.isObject()) {
+        HeapPtrObject owner(val.toObjectOrNull());
+        MarkObject(tracer, &owner, "binarystruct.blockRefOwner");
+    }
+
+    HeapPtrObject type(obj->getFixedSlot(SLOT_DATATYPE).toObjectOrNull());
+    MarkObject(tracer, &type, "binarystruct.type");
+}
+
+JSBool
+BinaryStruct::obj_getGeneric(JSContext *cx, HandleObject obj,
+                             HandleObject receiver, HandleId id,
+                             MutableHandleValue vp)
+{
+    if (!IsBinaryStruct(obj)) {
+        char *valueStr = JS_EncodeString(cx, JS_ValueToString(cx, ObjectValue(*obj)));
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                JSMSG_BINARYDATA_NOT_BINARYSTRUCT, valueStr);
+        JS_free(cx, (void *) valueStr);
+        return false;
+    }
+
+    RootedObject type(cx, GetType(obj));
+    JS_ASSERT(IsStructType(type));
+
+    FieldList *fieldList = static_cast<FieldList *>(type->getPrivate());
+    JS_ASSERT(fieldList);
+
+    FieldInfo fieldInfo;
+    if (!LookupFieldList(fieldList, id, &fieldInfo)) {
+        RootedObject proto(cx, obj->getProto());
+        if (!proto) {
+            vp.setUndefined();
+            return true;
+        }
+
+        return JSObject::getGeneric(cx, proto, receiver, id, vp);
+    }
+
+    RootedObject fieldType(cx, fieldInfo.type);
+    return Reify(cx, fieldType, obj, fieldInfo.offset, vp);
+}
+
+JSBool
+BinaryStruct::obj_getProperty(JSContext *cx, HandleObject obj,
+                              HandleObject receiver, HandlePropertyName name,
+                              MutableHandleValue vp)
+{
+    RootedId id(cx, NON_INTEGER_ATOM_TO_JSID(&(*name)));
+    return obj_getGeneric(cx, obj, receiver, id, vp);
+}
+
+JSBool
+BinaryStruct::obj_getSpecial(JSContext *cx, HandleObject obj,
+                             HandleObject receiver, HandleSpecialId sid,
+                             MutableHandleValue vp)
+{
+    RootedId id(cx, SPECIALID_TO_JSID(sid));
+    return obj_getGeneric(cx, obj, receiver, id, vp);
+}
+
+JSBool
+BinaryStruct::obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id,
+                             MutableHandleValue vp, JSBool strict)
+{
+    if (!IsBinaryStruct(obj)) {
+        char *valueStr = JS_EncodeString(cx, JS_ValueToString(cx, ObjectValue(*obj)));
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                JSMSG_BINARYDATA_NOT_BINARYSTRUCT, valueStr);
+        JS_free(cx, (void *) valueStr);
+        return false;
+    }
+
+    RootedObject type(cx, GetType(obj));
+    JS_ASSERT(IsStructType(type));
+
+    FieldList *fieldList = static_cast<FieldList *>(type->getPrivate());
+    JS_ASSERT(fieldList);
+
+    FieldInfo fieldInfo;
+    if (!LookupFieldList(fieldList, id, &fieldInfo)) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
+                             JSMSG_UNDEFINED_PROP, IdToString(cx, id));
+        return false;
+    }
+
+    uint8_t *loc = ((uint8_t *) obj->getPrivate()) + fieldInfo.offset;
+
+    RootedObject fieldType(cx, fieldInfo.type);
+    if (!ConvertAndCopyTo(cx, fieldType, vp, loc))
+        return false;
+
+    return true;
+}
+
+JSBool
+BinaryStruct::obj_setProperty(JSContext *cx, HandleObject obj,
+                              HandlePropertyName name, MutableHandleValue vp,
+                              JSBool strict)
+{
+    RootedId id(cx, NON_INTEGER_ATOM_TO_JSID(&(*name)));
+    return obj_setGeneric(cx, obj, id, vp, strict);
+}
+
+static bool
+Reify(JSContext *cx, HandleObject type,
+      HandleObject owner, size_t offset, MutableHandleValue to)
+{
+    if (IsArrayType(type)) {
+        return ArrayType::reify(cx, type, owner, offset, to);
+    } else if (IsStructType(type)) {
+        return StructType::reify(cx, type, owner, offset, to);
+    }
+
+    JS_ASSERT(&NumericTypeClasses[NUMERICTYPE_UINT8] <= type->getClass() &&
+              type->getClass() <= &NumericTypeClasses[NUMERICTYPE_FLOAT64]);
+
+#define REIFY_CASES(constant_, type_)\
+        case constant_:\
+            return NumericType<type_##_t>::reify(cx,\
+                    ((uint8_t *) owner->getPrivate()) + offset, to);
+
+    switch(type->getFixedSlot(SLOT_DATATYPE).toInt32()) {
+        BINARYDATA_FOR_EACH_NUMERIC_TYPES(REIFY_CASES);
+        default:
+            abort();
+    }
+#undef REIFY_CASES
+    return false;
+}
+
+static bool
+ConvertAndCopyTo(JSContext *cx, HandleObject type, HandleValue from, uint8_t *mem)
+{
+    if (IsComplexType(type)) {
+        if (IsArrayType(type)) {
+            if (!ArrayType::convertAndCopyTo(cx, type, from, mem))
+                return false;
+        } else if (IsStructType(type)) {
+            if (!StructType::convertAndCopyTo(cx, type, from, mem))
+                return false;
+        } else {
+            MOZ_ASSUME_UNREACHABLE("Unexpected complex BinaryData type!");
+        }
+
+        return true;
+    }
+
+    JS_ASSERT(&NumericTypeClasses[NUMERICTYPE_UINT8] <= type->getClass() &&
+              type->getClass() <= &NumericTypeClasses[NUMERICTYPE_FLOAT64]);
+
+#define CONVERT_CASES(constant_, type_)\
+        case constant_:\
+                       {\
+            type_##_t temp;\
+            bool ok = NumericType<type_##_t>::convert(cx, from, &temp);\
+            if (!ok)\
+                return false;\
+            memcpy(mem, &temp, sizeof(type_##_t));\
+            return true; }
+
+    switch(type->getFixedSlot(0).toInt32()) {
+        BINARYDATA_FOR_EACH_NUMERIC_TYPES(CONVERT_CASES);
+        default:
+            abort();
+    }
+#undef CONVERT_CASES
+    return false;
+}
+
+bool
+GlobalObject::initDataObject(JSContext *cx, Handle<GlobalObject *> global)
+{
+    RootedObject DataProto(cx);
+    DataProto = NewObjectWithGivenProto(cx, &DataClass,
+                                        global->getOrCreateObjectPrototype(cx),
+                                        global, SingletonObject);
+    if (!DataProto)
+        return false;
+
+    RootedAtom DataName(cx, ClassName(JSProto_Data, cx));
+    RootedFunction DataCtor(cx,
+            global->createConstructor(cx, DataThrowError, DataName,
+                                      1, JSFunction::ExtendedFinalizeKind));
+
+    if (!DataCtor)
+        return false;
+
+    if (!JS_DefineFunction(cx, DataProto, "update", DataInstanceUpdate, 1, 0))
+        return false;
+
+    if (!LinkConstructorAndPrototype(cx, DataCtor, DataProto))
+        return false;
+
+    if (!DefineConstructorAndPrototype(cx, global, JSProto_Data,
+                                       DataCtor, DataProto))
+        return false;
+
+    global->setReservedSlot(JSProto_Data, ObjectValue(*DataCtor));
+    return true;
+}
+
+bool
+GlobalObject::initTypeObject(JSContext *cx, Handle<GlobalObject *> global)
+{
+    RootedObject TypeProto(cx, global->getOrCreateDataObject(cx));
+    if (!TypeProto)
+        return false;
+
+    RootedAtom TypeName(cx, ClassName(JSProto_Type, cx));
+    RootedFunction TypeCtor(cx,
+            global->createConstructor(cx, TypeThrowError, TypeName,
+                                      1, JSFunction::ExtendedFinalizeKind));
+    if (!TypeCtor)
+        return false;
+
+    if (!LinkConstructorAndPrototype(cx, TypeCtor, TypeProto))
+        return false;
+
+    if (!DefineConstructorAndPrototype(cx, global, JSProto_Type,
+                                       TypeCtor, TypeProto))
+        return false;
+
+    global->setReservedSlot(JSProto_Type, ObjectValue(*TypeCtor));
+    return true;
+}
+
+bool
+GlobalObject::initArrayTypeObject(JSContext *cx, Handle<GlobalObject *> global)
+{
+    RootedFunction ctor(cx,
+        global->createConstructor(cx, ArrayType::construct,
+                                  cx->names().ArrayType, 2));
+
+    global->setReservedSlot(JSProto_ArrayTypeObject, ObjectValue(*ctor));
+    return true;
+}
+
+static JSObject *
+SetupComplexHeirarchy(JSContext *cx, HandleObject obj, JSProtoKey protoKey,
+                      HandleObject complexObject, MutableHandleObject proto,
+                      MutableHandleObject protoProto)
+{
+    Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+    // get the 'Type' constructor
+    RootedObject TypeObject(cx, global->getOrCreateTypeObject(cx));
+    if (!TypeObject)
+        return NULL;
+
+    // Set complexObject.__proto__ = Type
+    if (!JS_SetPrototype(cx, complexObject, TypeObject))
+        return NULL;
+
+    RootedObject DataObject(cx, global->getOrCreateDataObject(cx));
+    if (!DataObject)
+        return NULL;
+
+    RootedValue DataProtoVal(cx);
+    if (!JSObject::getProperty(cx, DataObject, DataObject,
+                               cx->names().classPrototype, &DataProtoVal))
+        return NULL;
+
+    RootedObject DataProto(cx, DataProtoVal.toObjectOrNull());
+    if (!DataProto)
+        return NULL;
+
+    RootedObject prototypeObj(cx,
+        NewObjectWithGivenProto(cx, &JSObject::class_, NULL, global));
+    if (!prototypeObj)
+        return NULL;
+    if (!LinkConstructorAndPrototype(cx, complexObject, prototypeObj))
+        return NULL;
+    if (!DefineConstructorAndPrototype(cx, global, protoKey,
+                                       complexObject, prototypeObj))
+        return NULL;
+
+    // Set complexObject.prototype.__proto__ = Data
+    if (!JS_SetPrototype(cx, prototypeObj, DataObject))
+        return NULL;
+
+    proto.set(prototypeObj);
+
+    // Set complexObject.prototype.prototype.__proto__ = Data.prototype
+    RootedObject prototypePrototypeObj(cx, JS_NewObject(cx, NULL, NULL,
+                                       global));
+
+    if (!LinkConstructorAndPrototype(cx, prototypeObj,
+                                     prototypePrototypeObj))
+        return NULL;
+
+    if (!JS_SetPrototype(cx, prototypePrototypeObj, DataProto))
+        return NULL;
+
+    protoProto.set(prototypePrototypeObj);
+
+    return complexObject;
+}
+
+static JSObject *
+InitArrayType(JSContext *cx, HandleObject obj)
+{
+    JS_ASSERT(obj->isNative());
+    Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+    RootedObject ctor(cx, global->getOrCreateArrayTypeObject(cx));
+    if (!ctor)
+        return NULL;
+
+    RootedObject proto(cx);
+    RootedObject protoProto(cx);
+    if (!SetupComplexHeirarchy(cx, obj, JSProto_ArrayType,
+                               ctor, &proto, &protoProto))
+        return NULL;
+
+    if (!JS_DefineFunction(cx, proto, "repeat", ArrayType::repeat, 1, 0))
+        return NULL;
+
+    if (!JS_DefineFunction(cx, proto, "toString", ArrayType::toString, 0, 0))
+        return NULL;
+
+    RootedObject arrayProto(cx);
+    if (!FindProto(cx, &ArrayObject::class_, &arrayProto))
+        return NULL;
+
+    RootedValue forEachFunVal(cx);
+    RootedAtom forEachAtom(cx, Atomize(cx, "forEach", 7));
+    RootedId forEachId(cx, AtomToId(forEachAtom));
+    if (!JSObject::getProperty(cx, arrayProto, arrayProto, forEachAtom->asPropertyName(), &forEachFunVal))
+        return NULL;
+
+    if (!JSObject::defineGeneric(cx, protoProto, forEachId, forEachFunVal, NULL, NULL, 0))
+        return NULL;
+
+    if (!JS_DefineFunction(cx, protoProto, "subarray",
+                           BinaryArray::subarray, 1, 0))
+        return NULL;
+
+    return proto;
+}
+
+static JSObject *
+InitStructType(JSContext *cx, HandleObject obj)
+{
+    JS_ASSERT(obj->isNative());
+    Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+    RootedFunction ctor(cx,
+        global->createConstructor(cx, StructType::construct,
+                                  cx->names().StructType, 1));
+
+    if (!ctor)
+        return NULL;
+
+    RootedObject proto(cx);
+    RootedObject protoProto(cx);
+    if (!SetupComplexHeirarchy(cx, obj, JSProto_StructType,
+                               ctor, &proto, &protoProto))
+        return NULL;
+
+    if (!JS_DefineFunction(cx, proto, "toString", StructType::toString, 0, 0))
+        return NULL;
+
+    return proto;
+}
+
+JSObject *
+js_InitBinaryDataClasses(JSContext *cx, HandleObject obj)
+{
+    JS_ASSERT(obj->is<GlobalObject>());
+    Rooted<GlobalObject *> global(cx, &obj->as<GlobalObject>());
+
+    JSObject *funProto = JS_GetFunctionPrototype(cx, global);
+#define BINARYDATA_NUMERIC_DEFINE(constant_, type_)\
+    do {\
+        RootedObject numFun(cx, JS_DefineObject(cx, global, #type_,\
+                    (JSClass *) &NumericTypeClasses[constant_], funProto, 0));\
+\
+        if (!numFun)\
+            return NULL;\
+\
+        numFun->setFixedSlot(SLOT_DATATYPE, Int32Value(constant_));\
+\
+        RootedValue sizeVal(cx, NumberValue(sizeof(type_##_t)));\
+        if (!JSObject::defineProperty(cx, numFun, cx->names().bytes,\
+                                      sizeVal,\
+                                      NULL, NULL,\
+                                      JSPROP_READONLY | JSPROP_PERMANENT))\
+            return NULL;\
+\
+        if (!JS_DefineFunction(cx, numFun, "toString",\
+                               NumericTypeToString<constant_>, 0, 0))\
+            return NULL;\
+    } while(0);
+    BINARYDATA_FOR_EACH_NUMERIC_TYPES(BINARYDATA_NUMERIC_DEFINE)
+#undef BINARYDATA_NUMERIC_DEFINE
+
+    if (!InitArrayType(cx, obj))
+        return NULL;
+
+    if (!InitStructType(cx, obj))
+        return NULL;
+
+    return global;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/BinaryData.h
@@ -0,0 +1,344 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef builtin_BinaryData_h
+#define builtin_BinaryData_h
+
+#include "jsapi.h"
+#include "jsobj.h"
+#include "jsfriendapi.h"
+#include "gc/Heap.h"
+
+namespace js {
+typedef float float32_t;
+typedef double float64_t;
+
+enum {
+    NUMERICTYPE_UINT8 = 0,
+    NUMERICTYPE_UINT16,
+    NUMERICTYPE_UINT32,
+    NUMERICTYPE_UINT64,
+    NUMERICTYPE_INT8,
+    NUMERICTYPE_INT16,
+    NUMERICTYPE_INT32,
+    NUMERICTYPE_INT64,
+    NUMERICTYPE_FLOAT32,
+    NUMERICTYPE_FLOAT64,
+    NUMERICTYPES
+};
+
+enum TypeCommonSlots {
+    SLOT_MEMSIZE = 0,
+    SLOT_ALIGN,
+    TYPE_RESERVED_SLOTS
+};
+
+enum BlockCommonSlots {
+    SLOT_DATATYPE = 0,
+    SLOT_BLOCKREFOWNER,
+    BLOCK_RESERVED_SLOTS
+};
+
+static Class DataClass = {
+    "Data",
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Data),
+    JS_PropertyStub,
+    JS_DeletePropertyStub,
+    JS_PropertyStub,
+    JS_StrictPropertyStub,
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    JS_ConvertStub
+};
+
+static Class TypeClass = {
+    "Type",
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Type),
+    JS_PropertyStub,
+    JS_DeletePropertyStub,
+    JS_PropertyStub,
+    JS_StrictPropertyStub,
+    JS_EnumerateStub,
+    JS_ResolveStub,
+    JS_ConvertStub
+};
+
+template <typename T>
+class NumericType
+{
+    private:
+        static Class * typeToClass();
+    public:
+        static bool convert(JSContext *cx, HandleValue val, T *converted);
+        static bool reify(JSContext *cx, void *mem, MutableHandleValue vp);
+        static JSBool call(JSContext *cx, unsigned argc, Value *vp);
+};
+
+template <typename T>
+JS_ALWAYS_INLINE
+bool NumericType<T>::reify(JSContext *cx, void *mem, MutableHandleValue vp)
+{
+    vp.setInt32(* ((T*)mem) );
+    return true;
+}
+
+template <>
+JS_ALWAYS_INLINE
+bool NumericType<float32_t>::reify(JSContext *cx, void *mem, MutableHandleValue vp)
+{
+    vp.setNumber(* ((float32_t*)mem) );
+    return true;
+}
+
+template <>
+JS_ALWAYS_INLINE
+bool NumericType<float64_t>::reify(JSContext *cx, void *mem, MutableHandleValue vp)
+{
+    vp.setNumber(* ((float64_t*)mem) );
+    return true;
+}
+
+#define BINARYDATA_FOR_EACH_NUMERIC_TYPES(macro_)\
+    macro_(NUMERICTYPE_UINT8,    uint8)\
+    macro_(NUMERICTYPE_UINT16,   uint16)\
+    macro_(NUMERICTYPE_UINT32,   uint32)\
+    macro_(NUMERICTYPE_UINT64,   uint64)\
+    macro_(NUMERICTYPE_INT8,     int8)\
+    macro_(NUMERICTYPE_INT16,    int16)\
+    macro_(NUMERICTYPE_INT32,    int32)\
+    macro_(NUMERICTYPE_INT64,    int64)\
+    macro_(NUMERICTYPE_FLOAT32,  float32)\
+    macro_(NUMERICTYPE_FLOAT64,  float64)
+
+#define BINARYDATA_NUMERIC_CLASSES(constant_, type_)\
+{\
+    #type_,\
+    JSCLASS_HAS_RESERVED_SLOTS(1) |\
+    JSCLASS_HAS_CACHED_PROTO(JSProto_##type_),\
+    JS_PropertyStub,       /* addProperty */\
+    JS_DeletePropertyStub, /* delProperty */\
+    JS_PropertyStub,       /* getProperty */\
+    JS_StrictPropertyStub, /* setProperty */\
+    JS_EnumerateStub,\
+    JS_ResolveStub,\
+    JS_ConvertStub,\
+    NULL,\
+    NULL,\
+    NumericType<type_##_t>::call,\
+    NULL,\
+    NULL,\
+    NULL\
+},
+
+static Class NumericTypeClasses[NUMERICTYPES] = {
+    BINARYDATA_FOR_EACH_NUMERIC_TYPES(BINARYDATA_NUMERIC_CLASSES)
+};
+
+/* This represents the 'A' and it's [[Prototype]] chain
+ * in:
+ *   A = new ArrayType(Type, N);
+ *   a = new A();
+ */
+class ArrayType : public JSObject
+{
+    private:
+    public:
+        static Class class_;
+
+        static JSObject *create(JSContext *cx, HandleObject arrayTypeGlobal,
+                                HandleObject elementType, uint32_t length);
+        static JSBool construct(JSContext *cx, unsigned int argc, jsval *vp);
+        static JSBool repeat(JSContext *cx, unsigned int argc, jsval *vp);
+
+        static JSBool toString(JSContext *cx, unsigned int argc, jsval *vp);
+
+        static uint32_t length(JSContext *cx, HandleObject obj);
+        static JSObject *elementType(JSContext *cx, HandleObject obj);
+        static bool convertAndCopyTo(JSContext *cx, HandleObject exemplar,
+                            HandleValue from, uint8_t *mem);
+        static bool reify(JSContext *cx, HandleObject type, HandleObject owner,
+                          size_t offset, MutableHandleValue to);
+};
+
+/* This represents the 'a' and it's [[Prototype]] chain */
+class BinaryArray
+{
+    private:
+        static JSObject *createEmpty(JSContext *cx, HandleObject type);
+
+        // attempts to [[Convert]]
+        static JSObject *create(JSContext *cx, HandleObject type,
+                                HandleValue initial);
+
+    public:
+        static Class class_;
+
+        // creates initialized memory of size of type
+        static JSObject *create(JSContext *cx, HandleObject type);
+        // uses passed block as memory
+        static JSObject *create(JSContext *cx, HandleObject type,
+                                HandleObject owner, size_t offset);
+        static JSBool construct(JSContext *cx, unsigned int argc, jsval *vp);
+
+        static void finalize(FreeOp *op, JSObject *obj);
+        static void obj_trace(JSTracer *tracer, JSObject *obj);
+
+        static JSBool subarray(JSContext *cx, unsigned int argc, jsval *vp);
+        static JSBool fill(JSContext *cx, unsigned int argc, jsval *vp);
+
+        static JSBool obj_lookupGeneric(JSContext *cx, HandleObject obj,
+                                        HandleId id, MutableHandleObject objp,
+                                        MutableHandleShape propp);
+
+        static JSBool obj_lookupProperty(JSContext *cx, HandleObject obj,
+                                         HandlePropertyName name,
+                                         MutableHandleObject objp,
+                                         MutableHandleShape propp);
+
+        static JSBool obj_lookupElement(JSContext *cx, HandleObject obj,
+                                        uint32_t index, MutableHandleObject objp,
+                                        MutableHandleShape propp);
+
+        static JSBool obj_lookupSpecial(JSContext *cx, HandleObject obj,
+                                        HandleSpecialId sid,
+                                        MutableHandleObject objp,
+                                        MutableHandleShape propp);
+
+        static JSBool obj_getGeneric(JSContext *cx, HandleObject obj,
+                                     HandleObject receiver,
+                                     HandleId id,
+                                     MutableHandleValue vp);
+
+        static JSBool obj_getProperty(JSContext *cx, HandleObject obj,
+                                      HandleObject receiver,
+                                      HandlePropertyName name,
+                                      MutableHandleValue vp);
+
+        static JSBool obj_getElement(JSContext *cx, HandleObject obj,
+                                     HandleObject receiver,
+                                     uint32_t index,
+                                     MutableHandleValue vp);
+
+        static JSBool obj_getElementIfPresent(JSContext *cx, HandleObject obj,
+                                              HandleObject receiver,
+                                              uint32_t index,
+                                              MutableHandleValue vp,
+                                              bool *present);
+
+        static JSBool obj_getSpecial(JSContext *cx, HandleObject obj,
+                                     HandleObject receiver,
+                                     HandleSpecialId sid,
+                                     MutableHandleValue vp);
+
+        static JSBool obj_setGeneric(JSContext *cx, HandleObject obj,
+                                     HandleId id, MutableHandleValue vp,
+                                     JSBool strict);
+
+        static JSBool obj_setProperty(JSContext *cx, HandleObject obj,
+                                      HandlePropertyName name,
+                                      MutableHandleValue vp,
+                                      JSBool strict);
+
+        static JSBool obj_setElement(JSContext *cx, HandleObject obj,
+                                     uint32_t index, MutableHandleValue vp,
+                                     JSBool strict);
+
+        static JSBool obj_setSpecial(JSContext *cx, HandleObject obj,
+                                     HandleSpecialId sid,
+                                     MutableHandleValue vp,
+                                     JSBool strict);
+
+        static JSBool obj_getGenericAttributes(JSContext *cx, HandleObject obj,
+                                               HandleId id, unsigned *attrsp);
+
+        static JSBool obj_getPropertyAttributes(JSContext *cx, HandleObject obj,
+                                                HandlePropertyName name,
+                                                unsigned *attrsp);
+
+        static JSBool obj_getElementAttributes(JSContext *cx, HandleObject obj,
+                                               uint32_t index, unsigned *attrsp);
+
+        static JSBool obj_getSpecialAttributes(JSContext *cx, HandleObject obj,
+                                               HandleSpecialId sid,
+                                               unsigned *attrsp);
+
+        static JSBool obj_enumerate(JSContext *cx, HandleObject obj,
+                                    JSIterateOp enum_op,
+                                    MutableHandleValue statep,
+                                    MutableHandleId idp);
+
+        static JSBool lengthGetter(JSContext *cx, unsigned int argc, jsval *vp);
+
+};
+
+class StructType : public JSObject
+{
+    private:
+        static JSObject *create(JSContext *cx, HandleObject structTypeGlobal,
+                                HandleObject fields);
+        /**
+         * Sets up structType slots based on calculated memory size
+         * and alignment and stores fieldmap as well.
+         */
+        static bool layout(JSContext *cx, HandleObject structType,
+                           HandleObject fields);
+
+    public:
+        static Class class_;
+
+        static JSBool construct(JSContext *cx, unsigned int argc, jsval *vp);
+        static JSBool toString(JSContext *cx, unsigned int argc, jsval *vp);
+
+        static bool convertAndCopyTo(JSContext *cx, HandleObject exemplar,
+                                     HandleValue from, uint8_t *mem);
+
+        static bool reify(JSContext *cx, HandleObject type, HandleObject owner,
+                          size_t offset, MutableHandleValue to);
+
+        static void finalize(js::FreeOp *op, JSObject *obj);
+};
+
+class BinaryStruct : public JSObject
+{
+    private:
+        static JSObject *createEmpty(JSContext *cx, HandleObject type);
+        static JSObject *create(JSContext *cx, HandleObject type);
+
+    public:
+        static Class class_;
+
+        static JSObject *create(JSContext *cx, HandleObject type,
+                                HandleObject owner, size_t offset);
+        static JSBool construct(JSContext *cx, unsigned int argc, jsval *vp);
+
+        static void finalize(js::FreeOp *op, JSObject *obj);
+        static void obj_trace(JSTracer *tracer, JSObject *obj);
+
+        static JSBool obj_getGeneric(JSContext *cx, HandleObject obj,
+                                     HandleObject receiver, HandleId id,
+                                     MutableHandleValue vp);
+
+        static JSBool obj_getProperty(JSContext *cx, HandleObject obj,
+                                      HandleObject receiver,
+                                      HandlePropertyName name,
+                                      MutableHandleValue vp);
+
+        static JSBool obj_getSpecial(JSContext *cx, HandleObject obj,
+                                     HandleObject receiver, HandleSpecialId sid,
+                                     MutableHandleValue vp);
+
+        static JSBool obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id,
+                                     MutableHandleValue vp, JSBool strict);
+
+        static JSBool obj_setProperty(JSContext *cx, HandleObject obj,
+                                      HandlePropertyName name,
+                                      MutableHandleValue vp,
+                                      JSBool strict);
+
+};
+}
+
+#endif /* builtin_BinaryData_h */
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -402,19 +402,19 @@ IntlInitialize(JSContext *cx, HandleObje
     JS_ASSERT(initializerValue.toObject().is<JSFunction>());
 
     InvokeArgs args(cx);
     if (!args.init(3))
         return false;
 
     args.setCallee(initializerValue);
     args.setThis(NullValue());
-    args[0] = ObjectValue(*obj);
-    args[1] = locales;
-    args[2] = options;
+    args[0].setObject(*obj);
+    args[1].set(locales);
+    args[2].set(options);
 
     return Invoke(cx, args);
 }
 
 // CountAvailable and GetAvailable describe the signatures used for ICU API
 // to determine available locales for various functionality.
 typedef int32_t
 (* CountAvailable)(void);
@@ -468,17 +468,17 @@ GetInternals(JSContext *cx, HandleObject
     JS_ASSERT(getInternalsValue.toObject().is<JSFunction>());
 
     InvokeArgs args(cx);
     if (!args.init(1))
         return false;
 
     args.setCallee(getInternalsValue);
     args.setThis(NullValue());
-    args[0] = ObjectValue(*obj);
+    args[0].setObject(*obj);
 
     if (!Invoke(cx, args))
         return false;
     internals.set(&args.rval().toObject());
     return true;
 }
 
 static bool
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -1013,18 +1013,18 @@ Class MapObject::class_ = {
     JS_PropertyStub,         // getProperty
     JS_StrictPropertyStub,   // setProperty
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     finalize,
     NULL,                    // checkAccess
     NULL,                    // call
+    NULL,                    // hasInstance
     NULL,                    // construct
-    NULL,                    // hasInstance
     mark
 };
 
 const JSPropertySpec MapObject::properties[] = {
     JS_PSG("size", size, 0),
     JS_PS_END
 };
 
@@ -1572,18 +1572,18 @@ Class SetObject::class_ = {
     JS_PropertyStub,         // getProperty
     JS_StrictPropertyStub,   // setProperty
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     finalize,
     NULL,                    // checkAccess
     NULL,                    // call
+    NULL,                    // hasInstance
     NULL,                    // construct
-    NULL,                    // hasInstance
     mark
 };
 
 const JSPropertySpec SetObject::properties[] = {
     JS_PSG("size", size, 0),
     JS_PS_END
 };
 
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -48,17 +48,17 @@ js::obj_construct(JSContext *cx, unsigne
 /* ES5 15.2.4.7. */
 static JSBool
 obj_propertyIsEnumerable(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     /* Step 1. */
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args.handleOrUndefinedAt(0), &id))
+    if (!ValueToId<CanGC>(cx, args.get(0), &id))
         return false;
 
     /* Step 2. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     /* Steps 3. */
@@ -357,17 +357,17 @@ DefineAccessor(JSContext *cx, unsigned a
     if (args.length() < 2 || !js_IsCallable(args[1])) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                              JSMSG_BAD_GETTER_OR_SETTER,
                              Type == Getter ? js_getter_str : js_setter_str);
         return false;
     }
 
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args.handleAt(0), &id))
+    if (!ValueToId<CanGC>(cx, args[0], &id))
         return false;
 
     RootedObject descObj(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
     if (!descObj)
         return false;
 
     JSAtomState &names = cx->names();
     RootedValue trueVal(cx, BooleanValue(true));
@@ -410,17 +410,17 @@ js::obj_defineSetter(JSContext *cx, unsi
 }
 
 static JSBool
 obj_lookupGetter(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args.handleOrUndefinedAt(0), &id))
+    if (!ValueToId<CanGC>(cx, args.get(0), &id))
         return JS_FALSE;
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return JS_FALSE;
     if (obj->is<ProxyObject>()) {
         // The vanilla getter lookup code below requires that the object is
         // native. Handle proxies separately.
         args.rval().setUndefined();
@@ -446,17 +446,17 @@ obj_lookupGetter(JSContext *cx, unsigned
 }
 
 static JSBool
 obj_lookupSetter(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args.handleOrUndefinedAt(0), &id))
+    if (!ValueToId<CanGC>(cx, args.get(0), &id))
         return JS_FALSE;
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return JS_FALSE;
     if (obj->is<ProxyObject>()) {
         // The vanilla setter lookup code below requires that the object is
         // native. Handle proxies separately.
         args.rval().setUndefined();
@@ -556,17 +556,17 @@ obj_watch(JSContext *cx, unsigned argc, 
         return false;
     }
 
     RootedObject callable(cx, ValueToCallable(cx, args[1], args.length() - 2));
     if (!callable)
         return false;
 
     RootedId propid(cx);
-    if (!ValueToId<CanGC>(cx, args.handleAt(0), &propid))
+    if (!ValueToId<CanGC>(cx, args[0], &propid))
         return false;
 
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     RootedValue tmp(cx);
     unsigned attrs;
@@ -584,33 +584,33 @@ obj_unwatch(JSContext *cx, unsigned argc
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
     args.rval().setUndefined();
     RootedId id(cx);
     if (argc != 0) {
-        if (!ValueToId<CanGC>(cx, args.handleAt(0), &id))
+        if (!ValueToId<CanGC>(cx, args[0], &id))
             return false;
     } else {
         id = JSID_VOID;
     }
     return JS_ClearWatchPoint(cx, obj, id, NULL, NULL);
 }
 
 #endif /* JS_HAS_OBJ_WATCHPOINT */
 
 /* ECMA 15.2.4.5. */
 static JSBool
 obj_hasOwnProperty(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    HandleValue idValue = args.handleOrUndefinedAt(0);
+    HandleValue idValue = args.get(0);
 
     /* Step 1, 2. */
     jsid id;
     if (args.thisv().isObject() && ValueToId<NoGC>(cx, idValue, &id)) {
         JSObject *obj = &args.thisv().toObject(), *obj2;
         Shape *prop;
         if (!obj->is<ProxyObject>() &&
             HasOwnProperty<NoGC>(cx, obj->getOps()->lookupGeneric, obj, id, &obj2, &prop))
@@ -726,17 +726,17 @@ obj_create(JSContext *cx, unsigned argc,
 static JSBool
 obj_getOwnPropertyDescriptor(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject obj(cx);
     if (!GetFirstArgumentAsObject(cx, args, "Object.getOwnPropertyDescriptor", &obj))
         return JS_FALSE;
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args.handleOrUndefinedAt(1), &id))
+    if (!ValueToId<CanGC>(cx, args.get(1), &id))
         return JS_FALSE;
     return GetOwnPropertyDescriptor(cx, obj, id, args.rval());
 }
 
 static JSBool
 obj_keys(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -831,21 +831,21 @@ static JSBool
 obj_defineProperty(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject obj(cx);
     if (!GetFirstArgumentAsObject(cx, args, "Object.defineProperty", &obj))
         return false;
 
     RootedId id(cx);
-    if (!ValueToId<CanGC>(cx, args.handleOrUndefinedAt(1), &id))
+    if (!ValueToId<CanGC>(cx, args.get(1), &id))
         return JS_FALSE;
 
     JSBool junk;
-    if (!DefineOwnProperty(cx, obj, id, args.handleOrUndefinedAt(2), &junk))
+    if (!DefineOwnProperty(cx, obj, id, args.get(2), &junk))
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 /* ES5 15.2.3.7: Object.defineProperties(O, Properties) */
 static JSBool
--- a/js/src/builtin/ParallelArray.cpp
+++ b/js/src/builtin/ParallelArray.cpp
@@ -165,17 +165,17 @@ ParallelArrayObject::constructHelper(JSC
     InvokeArgs args(cx);
     if (!args.init(args0.length()))
         return false;
 
     args.setCallee(ObjectValue(*ctor));
     args.setThis(ObjectValue(*result));
 
     for (uint32_t i = 0; i < args0.length(); i++)
-        args[i] = args0[i];
+        args[i].set(args0[i]);
 
     if (!Invoke(cx, args))
         return false;
 
     args0.rval().setObject(*result);
     return true;
 }
 
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -275,17 +275,17 @@ CompileRegExpObject(JSContext *cx, RegEx
 
         source = AtomizeString<CanGC>(cx, str);
         if (!source)
             return false;
     }
 
     RegExpFlag flags = RegExpFlag(0);
     if (args.hasDefined(1)) {
-        RootedString flagStr(cx, ToString<CanGC>(cx, args.handleAt(1)));
+        RootedString flagStr(cx, ToString<CanGC>(cx, args[1]));
         if (!flagStr)
             return false;
         args[1].setString(flagStr);
         if (!ParseRegExpFlags(cx, flagStr, &flags))
             return false;
     }
 
     RootedAtom escapedSourceStr(cx, EscapeNakedForwardSlashes(cx, source));
@@ -599,17 +599,17 @@ js::ExecuteRegExp(JSContext *cx, HandleO
 /* ES5 15.10.6.2 (and 15.10.6.3, which calls 15.10.6.2). */
 static RegExpRunStatus
 ExecuteRegExp(JSContext *cx, CallArgs args, MatchConduit &matches)
 {
     /* Step 1 (a) was performed by CallNonGenericMethod. */
     RootedObject regexp(cx, &args.thisv().toObject());
 
     /* Step 2. */
-    RootedString string(cx, ToString<CanGC>(cx, args.handleOrUndefinedAt(0)));
+    RootedString string(cx, ToString<CanGC>(cx, args.get(0)));
     if (!string)
         return RegExpRunStatus_Error;
 
     return ExecuteRegExp(cx, regexp, string, matches, UpdateRegExpStatics);
 }
 
 /* ES5 15.10.6.2. */
 static bool
@@ -632,17 +632,17 @@ regexp_exec_impl(JSContext *cx, CallArgs
 
     return CreateRegExpMatchResult(cx, string, matches, args.rval());
 }
 
 static bool
 regexp_exec_impl(JSContext *cx, CallArgs args)
 {
     RootedObject regexp(cx, &args.thisv().toObject());
-    RootedString string(cx, ToString<CanGC>(cx, args.handleOrUndefinedAt(0)));
+    RootedString string(cx, ToString<CanGC>(cx, args.get(0)));
     if (!string)
         return false;
 
     return regexp_exec_impl(cx, args, regexp, string, UpdateRegExpStatics);
 }
 
 JSBool
 js::regexp_exec(JSContext *cx, unsigned argc, Value *vp)
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -26,17 +26,17 @@ using namespace JS;
 using mozilla::ArrayLength;
 
 static JSBool
 GetBuildConfiguration(JSContext *cx, unsigned argc, jsval *vp)
 {
     RootedObject info(cx, JS_NewObject(cx, NULL, NULL, NULL));
     if (!info)
         return false;
-    Value value;
+    RootedValue value(cx);
 
 #ifdef JSGC_ROOT_ANALYSIS
     value = BooleanValue(true);
 #else
     value = BooleanValue(false);
 #endif
     if (!JS_SetProperty(cx, info, "rooting-analysis", &value))
         return false;
@@ -172,16 +172,24 @@ GetBuildConfiguration(JSContext *cx, uns
 #ifdef ENABLE_PARALLEL_JS
     value = BooleanValue(true);
 #else
     value = BooleanValue(false);
 #endif
     if (!JS_SetProperty(cx, info, "parallelJS", &value))
         return false;
 
+#ifdef ENABLE_BINARYDATA
+    value = BooleanValue(true);
+#else
+    value = BooleanValue(false);
+#endif
+    if (!JS_SetProperty(cx, info, "binary-data", &value))
+        return false;
+
     *vp = ObjectValue(*info);
     return true;
 }
 
 static JSBool
 GC(JSContext *cx, unsigned argc, jsval *vp)
 {
     /*
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -3127,17 +3127,17 @@ CType::ConstructBasic(JSContext* cx,
   }
 
   // construct a CData object
   RootedObject result(cx, CData::Create(cx, obj, NullPtr(), NULL, true));
   if (!result)
     return JS_FALSE;
 
   if (args.length() == 1) {
-    if (!ExplicitConvert(cx, args.handleAt(0), obj, CData::GetData(result)))
+    if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result)))
       return JS_FALSE;
   }
 
   args.rval().setObject(*result);
   return JS_TRUE;
 }
 
 JSObject*
@@ -3950,17 +3950,17 @@ PointerType::ConstructData(JSContext* cx
   //
   // Case 2 - Initialized pointer
   //
   if (!looksLikeClosure) {
     if (args.length() != 1) {
       JS_ReportError(cx, "first argument must be a function");
       return JS_FALSE;
     }
-    return ExplicitConvert(cx, args.handleAt(0), obj, CData::GetData(result));
+    return ExplicitConvert(cx, args[0], obj, CData::GetData(result));
   }
 
   //
   // Case 3 - Closure
   //
 
   // The second argument is an optional 'this' parameter with which to invoke
   // the given js function. Callers may leave this blank, or pass null if they
@@ -4339,17 +4339,17 @@ ArrayType::ConstructData(JSContext* cx,
 
   JSObject* result = CData::Create(cx, obj, NullPtr(), NULL, true);
   if (!result)
     return JS_FALSE;
 
   args.rval().setObject(*result);
 
   if (convertObject) {
-    if (!ExplicitConvert(cx, args.handleAt(0), obj, CData::GetData(result)))
+    if (!ExplicitConvert(cx, args[0], obj, CData::GetData(result)))
       return JS_FALSE;
   }
 
   return JS_TRUE;
 }
 
 JSObject*
 ArrayType::GetBaseType(JSObject* obj)
@@ -5018,17 +5018,17 @@ StructType::ConstructData(JSContext* cx,
     // 1) It may be an object '{ ... }' with properties representing the
     //    struct fields intended to ExplicitConvert wholesale to our StructType.
     // 2) If the struct contains one field, the arg may be intended to
     //    ImplicitConvert directly to that arg's CType.
     // Thankfully, the conditions for these two possibilities to succeed
     // are mutually exclusive, so we can pick the right one.
 
     // Try option 1) first.
-    if (ExplicitConvert(cx, args.handleAt(0), obj, buffer))
+    if (ExplicitConvert(cx, args[0], obj, buffer))
       return JS_TRUE;
 
     if (fields->count() != 1)
       return JS_FALSE;
 
     // If ExplicitConvert failed, and there is no pending exception, then assume
     // hard failure (out of memory, or some other similarly serious condition).
     if (!JS_IsExceptionPending(cx))
@@ -5042,17 +5042,17 @@ StructType::ConstructData(JSContext* cx,
   }
 
   // We have a type constructor of the form 'ctypes.StructType(a, b, c, ...)'.
   // ImplicitConvert each field.
   if (args.length() == fields->count()) {
     for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) {
       const FieldInfo& field = r.front().value;
       STATIC_ASSUME(field.mIndex < fields->count());  /* Quantified invariant */
-      if (!ImplicitConvert(cx, args.handleAt(field.mIndex), field.mType,
+      if (!ImplicitConvert(cx, args[field.mIndex], field.mType,
              buffer + field.mOffset,
              false, NULL))
         return JS_FALSE;
     }
 
     return JS_TRUE;
   }
 
@@ -5759,17 +5759,17 @@ FunctionType::Call(JSContext* cx,
   AutoValueAutoArray values;
   AutoValueAutoArray strings;
   if (!values.resize(args.length())) {
     JS_ReportOutOfMemory(cx);
     return false;
   }
 
   for (unsigned i = 0; i < argcFixed; ++i)
-    if (!ConvertArgument(cx, args.handleAt(i), fninfo->mArgTypes[i], &values[i], &strings))
+    if (!ConvertArgument(cx, args[i], fninfo->mArgTypes[i], &values[i], &strings))
       return false;
 
   if (fninfo->mIsVariadic) {
     if (!fninfo->mFFITypes.resize(args.length())) {
       JS_ReportOutOfMemory(cx);
       return false;
     }
 
@@ -5784,17 +5784,17 @@ FunctionType::Call(JSContext* cx,
         JS_ReportError(cx, "argument %d of type %s is not a CData object",
                        i, JS_GetTypeName(cx, JS_TypeOfValue(cx, args[i])));
         return false;
       }
       if (!(type = CData::GetCType(obj)) ||
           !(type = PrepareType(cx, OBJECT_TO_JSVAL(type))) ||
           // Relying on ImplicitConvert only for the limited purpose of
           // converting one CType to another (e.g., T[] to T*).
-          !ConvertArgument(cx, args.handleAt(i), type, &values[i], &strings) ||
+          !ConvertArgument(cx, args[i], type, &values[i], &strings) ||
           !(fninfo->mFFITypes[i] = CType::GetFFIType(cx, type))) {
         // These functions report their own errors.
         return false;
       }
     }
     if (!PrepareCIF(cx, fninfo))
       return false;
   }
--- a/js/src/gc/StoreBuffer.h
+++ b/js/src/gc/StoreBuffer.h
@@ -8,16 +8,17 @@
 #define gc_StoreBuffer_h
 
 #ifdef JSGC_GENERATIONAL
 
 #ifndef JSGC_USE_EXACT_ROOTING
 # error "Generational GC requires exact rooting."
 #endif
 
+#include "mozilla/DebugOnly.h"
 #include "mozilla/ReentrancyGuard.h"
 
 #include "jsalloc.h"
 #include "jsgc.h"
 #include "jsobj.h"
 
 #include "gc/Nursery.h"
 
@@ -95,17 +96,17 @@ class StoreBuffer
 
         /*
          * This set stores duplicates found when compacting. We create the set
          * here, rather than local to the algorithm to avoid malloc overhead in
          * the common case.
          */
         EdgeSet duplicates;
 
-        bool entered;
+        mozilla::DebugOnly<bool> entered;
 
         MonoTypeBuffer(StoreBuffer *owner)
           : owner(owner), base(NULL), pos(NULL), top(NULL), entered(false)
         {
             duplicates.init();
         }
 
         MonoTypeBuffer &operator=(const MonoTypeBuffer& other) MOZ_DELETE;
@@ -189,17 +190,17 @@ class StoreBuffer
         friend class mozilla::ReentrancyGuard;
 
         StoreBuffer *owner;
 
         uint8_t *base; /* Pointer to start of buffer. */
         uint8_t *pos;  /* Pointer to current buffer position. */
         uint8_t *top;  /* Pointer to one past the last entry. */
 
-        bool entered;
+        mozilla::DebugOnly<bool> entered;
 
         GenericBuffer(StoreBuffer *owner)
           : owner(owner), base(NULL), pos(NULL), top(NULL), entered(false)
         {}
 
         GenericBuffer &operator=(const GenericBuffer& other) MOZ_DELETE;
 
         bool enable(uint8_t *region, size_t len);
--- a/js/src/ion/AsmJSLink.cpp
+++ b/js/src/ion/AsmJSLink.cpp
@@ -443,17 +443,17 @@ HandleDynamicLinkFailure(JSContext *cx, 
 
     InvokeArgs args2(cx);
     if (!args2.init(argc))
         return false;
 
     args2.setCallee(ObjectValue(*fun));
     args2.setThis(args.thisv());
     for (unsigned i = 0; i < argc; i++)
-        args2[i] = args[i];
+        args2[i].set(args[i]);
 
     if (!Invoke(cx, args2))
         return false;
 
     args.rval().set(args2.rval());
 
     return true;
 }
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -1317,22 +1317,17 @@ CodeGenerator::visitPostWriteBarrierO(LP
 #ifdef JSGC_GENERATIONAL
     OutOfLineCallPostWriteBarrier *ool = new OutOfLineCallPostWriteBarrier(lir, lir->object());
     if (!addOutOfLineCode(ool))
         return false;
 
     Nursery &nursery = GetIonContext()->compartment->rt->gcNursery;
 
     if (lir->object()->isConstant()) {
-        JSObject *obj = &lir->object()->toConstant()->toObject();
-        JS_ASSERT(!nursery.isInside(obj));
-        /*
-        if (nursery.isInside(obj))
-            return true;
-        */
+        JS_ASSERT(!nursery.isInside(&lir->object()->toConstant()->toObject()));
     } else {
         Label tenured;
         Register objreg = ToRegister(lir->object());
         masm.branchPtr(Assembler::Below, objreg, ImmWord(nursery.start()), &tenured);
         masm.branchPtr(Assembler::Below, objreg, ImmWord(nursery.heapEnd()), ool->rejoin());
         masm.bind(&tenured);
     }
 
@@ -1354,22 +1349,17 @@ CodeGenerator::visitPostWriteBarrierV(LP
         return false;
 
     ValueOperand value = ToValue(lir, LPostWriteBarrierV::Input);
     masm.branchTestObject(Assembler::NotEqual, value, ool->rejoin());
 
     Nursery &nursery = GetIonContext()->compartment->rt->gcNursery;
 
     if (lir->object()->isConstant()) {
-        JSObject *obj = &lir->object()->toConstant()->toObject();
-        JS_ASSERT(!nursery.isInside(obj));
-        /*
-        if (nursery.isInside(obj))
-            return true;
-        */
+        JS_ASSERT(!nursery.isInside(&lir->object()->toConstant()->toObject()));
     } else {
         Label tenured;
         Register objreg = ToRegister(lir->object());
         masm.branchPtr(Assembler::Below, objreg, ImmWord(nursery.start()), &tenured);
         masm.branchPtr(Assembler::Below, objreg, ImmWord(nursery.heapEnd()), ool->rejoin());
         masm.bind(&tenured);
     }
 
@@ -4034,24 +4024,24 @@ CodeGenerator::visitConcat(LConcat *lir)
     JS_ASSERT(output == CallTempReg6);
 
     return emitConcat(lir, lhs, rhs, output);
 }
 
 bool
 CodeGenerator::visitConcatPar(LConcatPar *lir)
 {
-    Register slice = ToRegister(lir->forkJoinSlice());
+    DebugOnly<Register> slice = ToRegister(lir->forkJoinSlice());
     Register lhs = ToRegister(lir->lhs());
     Register rhs = ToRegister(lir->rhs());
     Register output = ToRegister(lir->output());
 
     JS_ASSERT(lhs == CallTempReg0);
     JS_ASSERT(rhs == CallTempReg1);
-    JS_ASSERT(slice == CallTempReg5);
+    JS_ASSERT((Register)slice == CallTempReg5);
     JS_ASSERT(ToRegister(lir->temp1()) == CallTempReg2);
     JS_ASSERT(ToRegister(lir->temp2()) == CallTempReg3);
     JS_ASSERT(ToRegister(lir->temp3()) == CallTempReg4);
     JS_ASSERT(output == CallTempReg6);
 
     return emitConcat(lir, lhs, rhs, output);
 }
 
--- a/js/src/ion/LICM.cpp
+++ b/js/src/ion/LICM.cpp
@@ -47,16 +47,22 @@ class Loop
   private:
     // These blocks define the loop.  header_ points to the loop header
     MBasicBlock *header_;
 
     // The pre-loop block is the first predecessor of the loop header.  It is where
     // the loop is first entered and where hoisted instructions will be placed.
     MBasicBlock* preLoop_;
 
+    // This indicates whether the loop contains calls or other things which
+    // clobber most or all floating-point registers. In such loops,
+    // floating-point constants should not be hoisted unless it enables further
+    // hoisting.
+    bool containsPossibleCall_;
+
     bool hoistInstructions(InstructionQueue &toHoist);
 
     // Utility methods for invariance testing and instruction hoisting.
     bool isInLoop(MDefinition *ins);
     bool isBeforeLoop(MDefinition *ins);
     bool isLoopInvariant(MInstruction *ins);
     bool isLoopInvariant(MDefinition *ins);
 
@@ -67,16 +73,18 @@ class Loop
     // Worklist and worklist usage methods
     InstructionQueue worklist_;
     bool insertInWorklist(MInstruction *ins);
     MInstruction* popFromWorklist();
 
     inline bool isHoistable(const MDefinition *ins) const {
         return ins->isMovable() && !ins->isEffectful() && !ins->neverHoist();
     }
+
+    bool requiresHoistedUse(const MDefinition *ins) const;
 };
 
 } /* namespace anonymous */
 
 LICM::LICM(MIRGenerator *mir, MIRGraph &graph)
   : mir(mir), graph(graph)
 {
 }
@@ -111,17 +119,18 @@ LICM::analyze()
         graph.unmarkBlocks();
     }
 
     return true;
 }
 
 Loop::Loop(MIRGenerator *mir, MBasicBlock *header)
   : mir(mir),
-    header_(header)
+    header_(header),
+    containsPossibleCall_(false)
 {
     preLoop_ = header_->getPredecessor(0);
 }
 
 Loop::LoopReturn
 Loop::init()
 {
     IonSpew(IonSpew_LICM, "Loop identified, headed by block %d", header_->id());
@@ -169,16 +178,21 @@ Loop::init()
         // If any block was added, process them first.
         if (block != inlooplist.back())
             continue;
 
         // Add all instructions in this block (but the control instruction) to the worklist
         for (MInstructionIterator i = block->begin(); i != block->end(); i++) {
             MInstruction *ins = *i;
 
+            // Remember whether this loop contains anything which clobbers most
+            // or all floating-point registers. This is just a rough heuristic.
+            if (ins->possiblyCalls())
+                containsPossibleCall_ = true;
+
             if (isHoistable(ins)) {
                 if (!insertInWorklist(ins))
                     return LoopReturn_Error;
             }
         }
 
         // All successors of this block are visited.
         inlooplist.popBack();
@@ -221,26 +235,42 @@ Loop::optimize()
     }
 
     if (!hoistInstructions(invariantInstructions))
         return false;
     return true;
 }
 
 bool
+Loop::requiresHoistedUse(const MDefinition *ins) const
+{
+    if (ins->isConstantElements() || ins->isBox())
+        return true;
+
+    // Integer constants can often be folded as immediates and aren't worth
+    // hoisting on their own, in general. Floating-point constants typically
+    // are worth hoisting, unless they'll end up being spilled (eg. due to a
+    // call).
+    if (ins->isConstant() && (ins->type() != MIRType_Double || containsPossibleCall_))
+        return true;
+
+    return false;
+}
+
+bool
 Loop::hoistInstructions(InstructionQueue &toHoist)
 {
     // Iterate in post-order (uses before definitions)
     for (int32_t i = toHoist.length() - 1; i >= 0; i--) {
         MInstruction *ins = toHoist[i];
 
-        // Don't hoist MConstantElements, MConstant and MBox
-        // if it doesn't enable us to hoist one of its uses.
-        // We want those instructions as close as possible to their use.
-        if (ins->isConstantElements() || ins->isConstant() || ins->isBox()) {
+        // Don't hoist a cheap constant if it doesn't enable us to hoist one of
+        // its uses. We want those instructions as close as possible to their
+        // use, to facilitate folding and minimize register pressure.
+        if (requiresHoistedUse(ins)) {
             bool loopInvariantUse = false;
             for (MUseDefIterator use(ins); use; use++) {
                 if (use.def()->isLoopInvariant()) {
                     loopInvariantUse = true;
                     break;
                 }
             }
 
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -336,16 +336,22 @@ class MDefinition : public MNode
     void printName(FILE *fp) const;
     static void PrintOpcodeName(FILE *fp, Opcode op);
     virtual void printOpcode(FILE *fp) const;
     void dump(FILE *fp) const;
 
     // For LICM.
     virtual bool neverHoist() const { return false; }
 
+    // Also for LICM. Test whether this definition is likely to be a call, which
+    // would clobber all or many of the floating-point registers, such that
+    // hoisting floating-point constants out of containing loops isn't likely to
+    // be worthwhile.
+    virtual bool possiblyCalls() const { return false; }
+
     void setTrackedPc(jsbytecode *pc) {
         trackedPc_ = pc;
     }
 
     jsbytecode *trackedPc() {
         return trackedPc_;
     }
 
@@ -1108,16 +1114,19 @@ class MThrow
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     virtual AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MNewParallelArray : public MNullaryInstruction
 {
     CompilerRootObject templateObject_;
 
     MNewParallelArray(JSObject *templateObject)
       : templateObject_(templateObject)
@@ -1308,16 +1317,19 @@ class MInitProp
     }
 
     PropertyName *propertyName() const {
         return name_;
     }
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MInitElem
   : public MAryInstruction<3>,
     public Mix3Policy<ObjectPolicy<0>, BoxPolicy<1>, BoxPolicy<2> >
 {
     MInitElem(MDefinition *obj, MDefinition *id, MDefinition *value)
     {
@@ -1341,16 +1353,19 @@ class MInitElem
         return getOperand(1);
     }
     MDefinition *getValue() const {
         return getOperand(2);
     }
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Designates the start of call frame construction.
 // Generates code to adjust the stack pointer for the argument vector.
 // Argc is inferred by checking the use chain during lowering.
 class MPrepareCall : public MNullaryInstruction
 {
   public:
@@ -1494,16 +1509,20 @@ class MCall
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     AliasSet getAliasSet() const {
         return AliasSet::Store(AliasSet::Any);
     }
+
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // fun.apply(self, arguments)
 class MApplyArgs
   : public MAryInstruction<3>,
     public MixPolicy<ObjectPolicy<0>, MixPolicy<IntPolicy<1>, BoxPolicy<2> > >
 {
   protected:
@@ -1538,16 +1557,19 @@ class MApplyArgs
     }
     MDefinition *getThis() const {
         return getOperand(2);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MGetDynamicName
   : public MAryInstruction<2>,
     public MixPolicy<ObjectPolicy<0>, StringPolicy<1> >
 {
   protected:
     MGetDynamicName(MDefinition *scopeChain, MDefinition *name)
@@ -1570,16 +1592,19 @@ class MGetDynamicName
     }
     MDefinition *getName() const {
         return getOperand(1);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Bailout if the input string contains 'arguments'
 class MFilterArguments
   : public MAryInstruction<1>,
     public StringPolicy<0>
 {
   protected:
@@ -1600,16 +1625,19 @@ class MFilterArguments
 
     MDefinition *getString() const {
         return getOperand(0);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MCallDirectEval
   : public MAryInstruction<3>,
     public MixPolicy<ObjectPolicy<0>, MixPolicy<StringPolicy<1>, BoxPolicy<2> > >
 {
   protected:
     MCallDirectEval(MDefinition *scopeChain, MDefinition *string, MDefinition *thisValue,
@@ -1644,16 +1672,20 @@ class MCallDirectEval
     jsbytecode  *pc() const {
         return pc_;
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
 
+    bool possiblyCalls() const {
+        return true;
+    }
+
   private:
     jsbytecode *pc_;
 };
 
 class MBinaryInstruction : public MAryInstruction<2>
 {
   protected:
     MBinaryInstruction(MDefinition *left, MDefinition *right)
@@ -2177,16 +2209,19 @@ class MCreateThisWithProto
 
     // Although creation of |this| modifies global state, it is safely repeatable.
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Caller-side allocation of |this| for |new|:
 // Constructs |this| when possible, else MagicValue(JS_IS_CONSTRUCTING).
 class MCreateThis
   : public MUnaryInstruction,
     public ObjectPolicy<0>
 {
@@ -2209,16 +2244,19 @@ class MCreateThis
 
     // Although creation of |this| modifies global state, it is safely repeatable.
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Eager initialization of arguments object.
 class MCreateArgumentsObject
   : public MUnaryInstruction,
     public ObjectPolicy<0>
 {
     MCreateArgumentsObject(MDefinition *callObj)
@@ -2240,16 +2278,19 @@ class MCreateArgumentsObject
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MGetArgumentsObjectArg
   : public MUnaryInstruction,
     public ObjectPolicy<0>
 {
     size_t argno_;
 
@@ -2334,16 +2375,19 @@ class MRunOncePrologue
     }
 
   public:
     INSTRUCTION_HEADER(RunOncePrologue)
 
     static MRunOncePrologue *New() {
         return new MRunOncePrologue();
     }
+    bool possiblyCalls() const {
+        return true;