Merge m-c to fx-team.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 26 Jul 2013 10:49:26 -0400
changeset 152389 8da2f00eb92ea5dc7f8b0e2f312153f6c03fd607
parent 152388 8c3ee4235ec1e5cbb311fb52feb23c95b7003a7e (current diff)
parent 152384 52f9e8ffe111884e934c7efeae4ffea67a44b128 (diff)
child 152408 343f7c10ed89efdaafca4367e595f3b083088ae3
child 152455 8b32dad46ea1c5434719f3c31e7cdd115002aebf
child 152525 6f8cc9245141fb1cdf7131981d87ab8426ec973c
child 158579 0608a3b4c9d92738b3bca824af1f4a3ff7672bee
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to 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;
+    }
 };
<