Merge latest green fx-team changeset and mozilla-central
authorEd Morley <emorley@mozilla.com>
Mon, 29 Jul 2013 15:16:29 +0100
changeset 152624 b8c7acba4b40fb752418dce29f5e9835bdcc4c0d
parent 152623 5458a907d1a54a578067dc798738a2840b7ba1f4 (current diff)
parent 152617 ba0f15bf8b67729b35c4133ce4afa52d46145ea3 (diff)
child 152632 6732e155ad6865411ce6a4d34089fe4ea1cdb639
child 152638 85a192e6d2e5bd6b4b3fbd5b3171d630aed9de20
child 152651 c00aa70e83182fcf298f3c7362796539387119a5
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 latest green fx-team changeset and mozilla-central
--- a/accessible/src/base/EventQueue.cpp
+++ b/accessible/src/base/EventQueue.cpp
@@ -298,17 +298,17 @@ EventQueue::CoalesceSelChangeEvents(AccS
       aTailEvent->mPackedEvent = aThisEvent;
       return;
     }
 
     if (aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
         aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
       aTailEvent->mEventRule = AccEvent::eDoNotEmit;
       aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
-      aThisEvent->mPackedEvent = aThisEvent;
+      aThisEvent->mPackedEvent = aTailEvent;
       return;
     }
   }
 
   // Unpack the packed selection change event because we've got one
   // more selection add/remove.
   if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
     if (aThisEvent->mPackedEvent) {
@@ -467,16 +467,39 @@ EventQueue::ProcessEventQueue()
           hyperText->GetSelectionCount(&selectionCount);
           if (selectionCount)
             nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED,
                                     hyperText);
         }
         continue;
       }
 
+      // Fire selected state change events in support to selection events.
+      if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_ADD) {
+        nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
+                                true, event->mIsFromUserInput);
+
+      } else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_REMOVE) {
+        nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
+                                false, event->mIsFromUserInput);
+
+      } else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
+        AccSelChangeEvent* selChangeEvent = downcast_accEvent(event);
+        nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
+                                (selChangeEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd),
+                                event->mIsFromUserInput);
+
+        if (selChangeEvent->mPackedEvent) {
+          nsEventShell::FireEvent(selChangeEvent->mPackedEvent->mAccessible,
+                                  states::SELECTED,
+                                  (selChangeEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd),
+                                  selChangeEvent->mPackedEvent->mIsFromUserInput);
+        }
+      }
+
       nsEventShell::FireEvent(event);
 
       // Fire text change events.
       AccMutationEvent* mutationEvent = downcast_accEvent(event);
       if (mutationEvent) {
         if (mutationEvent->mTextChangeEvent)
           nsEventShell::FireEvent(mutationEvent->mTextChangeEvent);
       }
--- a/accessible/src/base/nsEventShell.h
+++ b/accessible/src/base/nsEventShell.h
@@ -31,16 +31,30 @@ public:
    * @param  aEventType   [in] the event type
    * @param  aAccessible  [in] the event target
    */
   static void FireEvent(uint32_t aEventType,
                         mozilla::a11y::Accessible* aAccessible,
                         mozilla::a11y::EIsFromUserInput aIsFromUserInput = mozilla::a11y::eAutoDetect);
 
   /**
+   * Fire state change event.
+   */
+  static void FireEvent(mozilla::a11y::Accessible* aTarget, uint64_t aState,
+                        bool aIsEnabled, bool aIsFromUserInput)
+  {
+    nsRefPtr<mozilla::a11y::AccStateChangeEvent> stateChangeEvent =
+      new mozilla::a11y::AccStateChangeEvent(aTarget, aState, aIsEnabled,
+                                             (aIsFromUserInput ?
+                                               mozilla::a11y::eFromUserInput :
+                                               mozilla::a11y::eNoUserInput));
+    FireEvent(stateChangeEvent);
+  }
+
+  /**
    * Append 'event-from-input' object attribute if the accessible event has
    * been fired just now for the given node.
    *
    * @param  aNode        [in] the DOM node
    * @param  aAttributes  [in, out] the attributes
    */
   static void GetEventAttributes(nsINode *aNode,
                                  nsIPersistentProperties *aAttributes);
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -503,69 +503,74 @@ function eventQueue(aEventType)
             ok(false,
                "Unique type " +
                eventQueue.getEventTypeAsString(checker) + " event was handled.");
           }
         }
       }
     }
 
-    var matchedChecker = null;
+    var hasMatchedCheckers = false;
     for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
       var eventSeq = this.mScenarios[scnIdx];
 
       // Check if handled event matches expected sync event.
       var nextChecker = this.getNextExpectedEvent(eventSeq);
       if (nextChecker) {
         if (eventQueue.compareEvents(nextChecker, aEvent)) {
-          matchedChecker = nextChecker;
-          matchedChecker.wasCaught++;
-          break;
+          this.processMatchedChecker(aEvent, nextChecker, scnIdx, eventSeq.idx);
+          hasMatchedCheckers = true;
+          continue;
         }
       }
 
       // Check if handled event matches any expected async events.
       for (idx = 0; idx < eventSeq.length; idx++) {
         if (!eventSeq[idx].unexpected && eventSeq[idx].async) {
           if (eventQueue.compareEvents(eventSeq[idx], aEvent)) {
-            matchedChecker = eventSeq[idx];
-            matchedChecker.wasCaught++;
+            this.processMatchedChecker(aEvent, eventSeq[idx], scnIdx, idx);
+            hasMatchedCheckers = true;
             break;
           }
         }
       }
     }
 
-    // Call 'check' functions on invoker's side.
-    if (matchedChecker) {
-      if ("check" in matchedChecker)
-        matchedChecker.check(aEvent);
-
+    if (hasMatchedCheckers) {
       var invoker = this.getInvoker();
       if ("check" in invoker)
         invoker.check(aEvent);
     }
 
-    // Dump handled event.
-    eventQueue.logEvent(aEvent, matchedChecker, this.areExpectedEventsLeft(),
-                        this.mNextInvokerStatus);
-
     // If we don't have more events to wait then schedule next invoker.
-    if (!this.areExpectedEventsLeft() &&
+    if (this.hasMatchedScenario() &&
         (this.mNextInvokerStatus == kInvokerNotScheduled)) {
       this.processNextInvokerInTimeout();
       return;
     }
 
     // If we have scheduled a next invoker then cancel in case of match.
-    if ((this.mNextInvokerStatus == kInvokerPending) && matchedChecker)
+    if ((this.mNextInvokerStatus == kInvokerPending) && hasMatchedCheckers)
       this.mNextInvokerStatus = kInvokerCanceled;
   }
 
   // Helpers
+  this.processMatchedChecker =
+    function eventQueue_function(aEvent, aMatchedChecker, aScenarioIdx, aEventIdx)
+  {
+    aMatchedChecker.wasCaught++;
+
+    if ("check" in aMatchedChecker)
+      aMatchedChecker.check(aEvent);
+
+    eventQueue.logEvent(aEvent, aMatchedChecker, aScenarioIdx, aEventIdx,
+                        this.areExpectedEventsLeft(),
+                        this.mNextInvokerStatus);
+  }
+
   this.getNextExpectedEvent =
     function eventQueue_getNextExpectedEvent(aEventSeq)
   {
     if (!("idx" in aEventSeq))
       aEventSeq.idx = 0;
 
     while (aEventSeq.idx < aEventSeq.length &&
            (aEventSeq[aEventSeq.idx].unexpected ||
@@ -630,16 +635,26 @@ function eventQueue(aEventType)
           break;
       }
       if (idx == eventSeq.length)
         return true;
     }
 
     return false;
   }
+  
+  this.hasMatchedScenario =
+    function eventQueue_hasMatchedScenario()
+  {
+    for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
+      if (!this.areExpectedEventsLeft(this.mScenarios[scnIdx]))
+        return true;
+    }
+    return false;
+  }
 
   this.getInvoker = function eventQueue_getInvoker()
   {
     return this.mInvokers[this.mIndex];
   }
 
   this.getNextInvoker = function eventQueue_getNextInvoker()
   {
@@ -853,16 +868,17 @@ eventQueue.isSameEvent = function eventQ
   // target, thus we should filter text change and state change events since
   // they may occur on the same element because of complex changes.
   return this.compareEvents(aChecker, aEvent) &&
     !(aEvent instanceof nsIAccessibleTextChangeEvent) &&
     !(aEvent instanceof nsIAccessibleStateChangeEvent);
 }
 
 eventQueue.logEvent = function eventQueue_logEvent(aOrigEvent, aMatchedChecker,
+                                                   aScenarioIdx, aEventIdx,
                                                    aAreExpectedEventsLeft,
                                                    aInvokerStatus)
 {
   if (!gLogger.isEnabled()) // debug stuff
     return;
 
   // Dump DOM event information. Skip a11y event since it is dumped by
   // gA11yEventObserver.
@@ -892,17 +908,18 @@ eventQueue.logEvent = function eventQueu
   if (!aMatchedChecker)
     return;
 
   var msg = "EQ: ";
   var emphText = "matched ";
 
   var currType = eventQueue.getEventTypeAsString(aMatchedChecker);
   var currTargetDescr = eventQueue.getEventTargetDescr(aMatchedChecker);
-  var consoleMsg = "*****\nEQ matched: " + currType + "\n*****";
+  var consoleMsg = "*****\nScenario " + aScenarioIdx + 
+    ", event " + aEventIdx + " matched: " + currType + "\n*****";
   gLogger.logToConsole(consoleMsg);
 
   msg += " event, type: " + currType + ", target: " + currTargetDescr;
 
   gLogger.logToDOM(msg, true, emphText);
 }
 
 
@@ -1722,17 +1739,18 @@ function stateChangeChecker(aState, aIsE
     var unxpdExtraState = aIsEnabled ? 0 : (aIsExtraState ? aState : 0);
     testStates(event.accessible, state, extraState, unxpdState, unxpdExtraState);
   }
 
   this.match = function stateChangeChecker_match(aEvent)
   {
     if (aEvent instanceof nsIAccessibleStateChangeEvent) {
       var scEvent = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
-      return aEvent.accessible = this.target && scEvent.state == aState;
+      return (aEvent.accessible == getAccessible(this.target)) &&
+        (scEvent.state == aState);
     }
     return false;
   }
 }
 
 function asyncStateChangeChecker(aState, aIsExtraState, aIsEnabled,
                                  aTargetOrFunc, aTargetFuncArg)
 {
@@ -1767,16 +1785,69 @@ function expandedStateChecker(aIsEnabled
       "Wrong state of statechange event state");
 
     testStates(event.accessible,
                (aIsEnabled ? STATE_EXPANDED : STATE_COLLAPSED));
   }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// Event sequances (array of predefined checkers)
+
+/**
+ * Event seq for single selection change.
+ */
+function selChangeSeq(aUnselectedID, aSelectedID)
+{
+  if (!aUnselectedID) {
+    return [
+      new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
+      new invokerChecker(EVENT_SELECTION, aSelectedID)
+    ];
+  }
+
+  // Return two possible scenarios: depending on widget type when selection is
+  // moved the the order of items that get selected and unselected may vary. 
+  return [
+    [
+      new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
+      new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
+      new invokerChecker(EVENT_SELECTION, aSelectedID)
+    ],
+    [
+      new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
+      new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
+      new invokerChecker(EVENT_SELECTION, aSelectedID)
+    ]
+  ];
+}
+
+/**
+ * Event seq for item removed form the selection.
+ */
+function selRemoveSeq(aUnselectedID)
+{
+  return [
+    new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
+    new invokerChecker(EVENT_SELECTION_REMOVE, aUnselectedID)
+  ];
+}
+
+/**
+ * Event seq for item added to the selection.
+ */
+function selAddSeq(aSelectedID)
+{
+  return [
+    new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
+    new invokerChecker(EVENT_SELECTION_ADD, aSelectedID)
+  ];
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // Private implementation details.
 ////////////////////////////////////////////////////////////////////////////////
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // General
 
 var gA11yEventListeners = {};
@@ -2038,23 +2109,30 @@ function sequenceItem(aProcessor, aEvent
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Event queue invokers
 
 /**
  * Invoker base class for prepare an action.
  */
-function synthAction(aNodeOrID, aCheckerOrEventSeq)
+function synthAction(aNodeOrID, aEventsObj)
 {
   this.DOMNode = getNode(aNodeOrID);
 
-  if (aCheckerOrEventSeq) {
-    if (aCheckerOrEventSeq instanceof Array) {
-      this.eventSeq = aCheckerOrEventSeq;
+  if (aEventsObj) {
+    var scenarios = null;
+    if (aEventsObj instanceof Array) {
+      if (aEventsObj[0] instanceof Array)
+        scenarios = aEventsObj; // scenarios
+      else
+        scenarios = [ aEventsObj ]; // event sequance
     } else {
-      this.eventSeq = [ aCheckerOrEventSeq ];
+      scenarios = [ [ aEventsObj ] ]; // a single checker object
     }
+
+    for (var i = 0; i < scenarios.length; i++)
+      defineScenario(this, scenarios[i]);
   }
 
   this.getID = function synthAction_getID()
     { return prettyName(aNodeOrID) + " action"; }
 }
--- a/accessible/tests/mochitest/events/test_selection.html
+++ b/accessible/tests/mochitest/events/test_selection.html
@@ -33,56 +33,61 @@
     function doTests()
     {
       gQueue = new eventQueue();
 
       // open combobox
       gQueue.push(new synthClick("combobox",
                                  new invokerChecker(EVENT_FOCUS, "cb1_item1")));
       gQueue.push(new synthDownKey("cb1_item1",
-                                   new invokerChecker(EVENT_SELECTION, "cb1_item2")));
+                                   selChangeSeq("cb1_item1", "cb1_item2")));
 
       // closed combobox
       gQueue.push(new synthEscapeKey("combobox",
                                      new invokerChecker(EVENT_FOCUS, "combobox")));
       gQueue.push(new synthDownKey("cb1_item2",
-                                   new invokerChecker(EVENT_SELECTION, "cb1_item3")));
+                                   selChangeSeq("cb1_item2", "cb1_item3")));
 
       // listbox
       gQueue.push(new synthClick("lb1_item1",
                                  new invokerChecker(EVENT_SELECTION, "lb1_item1")));
       gQueue.push(new synthDownKey("lb1_item1",
-                                   new invokerChecker(EVENT_SELECTION, "lb1_item2")));
+                                   selChangeSeq("lb1_item1", "lb1_item2")));
 
       // multiselectable listbox
       gQueue.push(new synthClick("lb2_item1",
-                                 new invokerChecker(EVENT_SELECTION, "lb2_item1")));
+                                 selChangeSeq(null, "lb2_item1")));
       gQueue.push(new synthDownKey("lb2_item1",
-                                   new invokerChecker(EVENT_SELECTION_ADD, "lb2_item2"),
+                                   selAddSeq("lb2_item2"),
                                    { shiftKey: true }));
       gQueue.push(new synthUpKey("lb2_item2",
-                                 new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item2"),
+                                 selRemoveSeq("lb2_item2"),
                                  { shiftKey: true }));
       gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true },
-                               new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item1")));
+                               selRemoveSeq("lb2_item1")));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
 
 <body>
 
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302"
      title="Incorrect selection events in HTML, XUL and ARIA">
-    Mozilla Bug 414302
+    Bug 414302
+  </a>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=810268"
+     title="There's no way to know unselected item when selection in single selection was changed">
+    Bug 810268
   </a>
 
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <select id="combobox">
--- a/accessible/tests/mochitest/events/test_selection_aria.html
+++ b/accessible/tests/mochitest/events/test_selection_aria.html
@@ -31,17 +31,17 @@
 
       this.eventSeq = [
         new invokerChecker(EVENT_SELECTION, aItemID)
       ];
 
       this.invoke = function selectItem_invoke() {
         var itemNode = this.selectNode.querySelector("*[aria-selected='true']");
         if (itemNode)
-          itemNode.removeAttribute("aria-selected", "true");
+          itemNode.removeAttribute("aria-selected");
 
         this.itemNode.setAttribute("aria-selected", "true");
       }
 
       this.getID = function selectItem_getID()
       {
         return "select item " + prettyName(aItemID);
       }
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -550,16 +550,18 @@ var shell = {
 
   openAppForSystemMessage: function shell_openAppForSystemMessage(msg) {
     let origin = Services.io.newURI(msg.manifest, null, null).prePath;
     this.sendChromeEvent({
       type: 'open-app',
       url: msg.uri,
       manifestURL: msg.manifest,
       isActivity: (msg.type == 'activity'),
+      onlyShowApp: msg.onlyShowApp,
+      showApp: msg.showApp,
       target: msg.target,
       expectingSystemMessage: true,
       extra: msg.extra
     });
   },
 
   receiveMessage: function shell_receiveMessage(message) {
     var activities = { 'content-handler': { name: 'view', response: null },
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "878cc221e0fdadb4d42dc110945533104f6dd572", 
+    "revision": "30af13f2784172ca635db0a5a72494c180c7f885", 
     "repo_path": "/integration/gaia-central"
 }
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -3151,18 +3151,19 @@ nsDocument::ElementFromPointHelper(float
   }
   nsIFrame *rootFrame = ps->GetRootFrame();
 
   // XUL docs, unlike HTML, have no frame tree until everything's done loading
   if (!rootFrame) {
     return nullptr; // return null to premature XUL callers as a reminder to wait
   }
 
-  nsIFrame *ptFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, pt, true,
-                                                      aIgnoreRootScrollFrame);
+  nsIFrame *ptFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, pt,
+    nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
+    (aIgnoreRootScrollFrame ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0));
   if (!ptFrame) {
     return nullptr;
   }
 
   nsIContent* elem = GetContentInThisDocument(ptFrame);
   if (elem && !elem->IsElement()) {
     elem = elem->GetParent();
   }
@@ -3206,17 +3207,18 @@ nsDocument::NodesFromRectHelper(float aX
   nsIFrame *rootFrame = ps->GetRootFrame();
 
   // XUL docs, unlike HTML, have no frame tree until everything's done loading
   if (!rootFrame)
     return NS_OK; // return nothing to premature XUL callers as a reminder to wait
 
   nsAutoTArray<nsIFrame*,8> outFrames;
   nsLayoutUtils::GetFramesForArea(rootFrame, rect, outFrames,
-                                  true, aIgnoreRootScrollFrame);
+    nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
+    (aIgnoreRootScrollFrame ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0));
 
   // Used to filter out repeated elements in sequence.
   nsIContent* lastAdded = nullptr;
 
   for (uint32_t i = 0; i < outFrames.Length(); i++) {
     nsIContent* node = GetContentInThisDocument(outFrames[i]);
 
     if (node && !node->IsElement() && !node->IsNodeOfType(nsINode::eTEXT)) {
@@ -9345,18 +9347,18 @@ nsIDocument::CaretPositionFromPoint(floa
 
   nsIFrame *rootFrame = ps->GetRootFrame();
 
   // XUL docs, unlike HTML, have no frame tree until everything's done loading
   if (!rootFrame) {
     return nullptr;
   }
 
-  nsIFrame *ptFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, pt, true,
-                                                      false);
+  nsIFrame *ptFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, pt,
+      nsLayoutUtils::IGNORE_PAINT_SUPPRESSION);
   if (!ptFrame) {
     return nullptr;
   }
 
   // GetContentOffsetsFromPoint requires frame-relative coordinates, so we need
   // to adjust to frame-relative coordinates before we can perform this call.
   // It should also not take into account the padding of the frame.
   nsPoint adjustedPoint = pt - ptFrame->GetOffsetTo(rootFrame);
--- a/content/media/ogg/OggReader.cpp
+++ b/content/media/ogg/OggReader.cpp
@@ -87,17 +87,16 @@ OggReader::OggReader(AbstractMediaDecode
     mVorbisState(nullptr),
     mOpusState(nullptr),
     mOpusEnabled(MediaDecoder::IsOpusEnabled()),
     mSkeletonState(nullptr),
     mVorbisSerial(0),
     mOpusSerial(0),
     mTheoraSerial(0),
     mOpusPreSkip(0),
-    mPageOffset(0),
     mIsChained(false),
     mDecodedAudioFrames(0)
 {
   MOZ_COUNT_CTOR(OggReader);
   memset(&mTheoraInfo, 0, sizeof(mTheoraInfo));
 }
 
 OggReader::~OggReader()
@@ -179,18 +178,17 @@ nsresult OggReader::ReadMetadata(VideoIn
 
   NS_ASSERTION(aTags, "Called with null MetadataTags**.");
   *aTags = nullptr;
 
   ogg_page page;
   nsAutoTArray<OggCodecState*,4> bitstreams;
   bool readAllBOS = false;
   while (!readAllBOS) {
-    int64_t pageOffset = ReadOggPage(&page);
-    if (pageOffset == -1) {
+    if (!ReadOggPage(&page)) {
       // Some kind of error...
       break;
     }
 
     int serial = ogg_page_serialno(&page);
     OggCodecState* codecState = 0;
 
     if (!ogg_page_bos(&page)) {
@@ -410,17 +408,17 @@ nsresult OggReader::DecodeVorbis(ogg_pac
         // No channel mapping for more than 8 channels.
         return NS_ERROR_FAILURE;
       }
       DownmixToStereo(buffer, channels, frames);
     }
 
     int64_t duration = mVorbisState->Time((int64_t)frames);
     int64_t startTime = mVorbisState->Time(endFrame - frames);
-    mAudioQueue.Push(new AudioData(mPageOffset,
+    mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(),
                                    startTime,
                                    duration,
                                    frames,
                                    buffer.forget(),
                                    channels));
 
     mDecodedAudioFrames += frames;
 
@@ -530,17 +528,17 @@ nsresult OggReader::DecodeOpus(ogg_packe
     if (channels > 8)
       return NS_ERROR_FAILURE;
     DownmixToStereo(buffer, channels, frames);
   }
 
   LOG(PR_LOG_DEBUG, ("Opus decoder pushing %d frames", frames));
   int64_t startTime = mOpusState->Time(startFrame);
   int64_t endTime = mOpusState->Time(endFrame);
-  mAudioQueue.Push(new AudioData(mPageOffset,
+  mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(),
                                  startTime,
                                  endTime - startTime,
                                  frames,
                                  buffer.forget(),
                                  channels));
 
   mDecodedAudioFrames += frames;
 
@@ -670,18 +668,17 @@ bool OggReader::ReadOggChain()
   long rate = 0;
   MetadataTags* tags = nullptr;
 
   if (HasVideo() || HasSkeleton() || !HasAudio()) {
     return false;
   }
 
   ogg_page page;
-  int64_t pageOffset = ReadOggPage(&page);
-  if ((pageOffset == -1) || (!ogg_page_bos(&page))) {
+  if (!ReadOggPage(&page) || !ogg_page_bos(&page)) {
     return false;
   }
 
   int serial = ogg_page_serialno(&page);
   if (mCodecStore.Contains(serial)) {
     return false;
   }
 
@@ -779,17 +776,17 @@ nsresult OggReader::DecodeTheora(ogg_pac
   int64_t endTime = mTheoraState->Time(aPacket->granulepos);
   if (endTime < aTimeThreshold) {
     // The end time of this frame is already before the current playback
     // position. It will never be displayed, don't bother enqueing it.
     return NS_OK;
   }
 
   if (ret == TH_DUPFRAME) {
-    VideoData* v = VideoData::CreateDuplicate(mPageOffset,
+    VideoData* v = VideoData::CreateDuplicate(mDecoder->GetResource()->Tell(),
                                               time,
                                               endTime,
                                               aPacket->granulepos);
     mVideoQueue.Push(v);
   } else if (ret == 0) {
     th_ycbcr_buffer buffer;
     ret = th_decode_ycbcr_out(mTheoraState->mCtx, buffer);
     NS_ASSERTION(ret == 0, "th_decode_ycbcr_out failed");
@@ -800,17 +797,17 @@ nsresult OggReader::DecodeTheora(ogg_pac
       b.mPlanes[i].mHeight = buffer[i].height;
       b.mPlanes[i].mWidth = buffer[i].width;
       b.mPlanes[i].mStride = buffer[i].stride;
       b.mPlanes[i].mOffset = b.mPlanes[i].mSkip = 0;
     }
 
     VideoData *v = VideoData::Create(mInfo,
                                      mDecoder->GetImageContainer(),
-                                     mPageOffset,
+                                     mDecoder->GetResource()->Tell(),
                                      time,
                                      endTime,
                                      b,
                                      isKeyframe,
                                      aPacket->granulepos,
                                      mPicture);
     if (!v) {
       // There may be other reasons for this error, but for
@@ -868,68 +865,65 @@ bool OggReader::DecodeVideoFrame(bool &a
     // there will be no more frames.
     mVideoQueue.Finish();
     return false;
   }
 
   return true;
 }
 
-int64_t OggReader::ReadOggPage(ogg_page* aPage)
+bool OggReader::ReadOggPage(ogg_page* aPage)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   int ret = 0;
   while((ret = ogg_sync_pageseek(&mOggState, aPage)) <= 0) {
     if (ret < 0) {
       // Lost page sync, have to skip up to next page.
-      mPageOffset += -ret;
       continue;
     }
     // Returns a buffer that can be written too
     // with the given size. This buffer is stored
     // in the ogg synchronisation structure.
     char* buffer = ogg_sync_buffer(&mOggState, 4096);
     NS_ASSERTION(buffer, "ogg_sync_buffer failed");
 
     // Read from the resource into the buffer
     uint32_t bytesRead = 0;
 
     nsresult rv = mDecoder->GetResource()->Read(buffer, 4096, &bytesRead);
     if (NS_FAILED(rv) || (bytesRead == 0 && ret == 0)) {
       // End of file.
-      return -1;
+      return false;
     }
 
     mDecoder->NotifyBytesConsumed(bytesRead);
     // Update the synchronisation layer with the number
     // of bytes written to the buffer
     ret = ogg_sync_wrote(&mOggState, bytesRead);
-    NS_ENSURE_TRUE(ret == 0, -1);    
+    NS_ENSURE_TRUE(ret == 0, false);
   }
-  int64_t offset = mPageOffset;
-  mPageOffset += aPage->header_len + aPage->body_len;
-  
-  return offset;
+
+  return true;
 }
 
 ogg_packet* OggReader::NextOggPacket(OggCodecState* aCodecState)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   if (!aCodecState || !aCodecState->mActive) {
     return nullptr;
   }
 
   ogg_packet* packet;
   while ((packet = aCodecState->PacketOut()) == nullptr) {
     // The codec state does not have any buffered pages, so try to read another
     // page from the channel.
     ogg_page page;
-    if (ReadOggPage(&page) == -1) {
+    if (!ReadOggPage(&page)) {
       return nullptr;
     }
 
     uint32_t serial = ogg_page_serialno(&page);
     OggCodecState* codecState = nullptr;
     codecState = mCodecStore.Get(serial);
     if (codecState && NS_FAILED(codecState->PageIn(&page))) {
       return nullptr;
@@ -943,17 +937,17 @@ ogg_packet* OggReader::NextOggPacket(Ogg
 static ogg_uint32_t
 GetChecksum(ogg_page* page)
 {
   if (page == 0 || page->header == 0 || page->header_len < 25) {
     return 0;
   }
   const unsigned char* p = page->header + 22;
   uint32_t c =  p[0] +
-               (p[1] << 8) + 
+               (p[1] << 8) +
                (p[2] << 16) +
                (p[3] << 24);
   return c;
 }
 
 int64_t OggReader::RangeStartTime(int64_t aOffset)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
@@ -1008,17 +1002,17 @@ int64_t OggReader::RangeEndTime(int64_t 
   int64_t readStartOffset = aEndOffset;
   int64_t readLimitOffset = aEndOffset;
   int64_t readHead = aEndOffset;
   int64_t endTime = -1;
   uint32_t checksumAfterSeek = 0;
   uint32_t prevChecksumAfterSeek = 0;
   bool mustBackOff = false;
   while (true) {
-    ogg_page page;    
+    ogg_page page;
     int ret = ogg_sync_pageseek(&sync.mState, &page);
     if (ret == 0) {
       // We need more data if we've not encountered a page we've seen before,
       // or we've read to the end of file.
       if (mustBackOff || readHead == aEndOffset || readHead == aStartOffset) {
         if (endTime != -1 || readStartOffset == 0) {
           // We have encountered a page before, or we're at the end of file.
           break;
@@ -1191,17 +1185,17 @@ OggReader::IndexedSeekResult OggReader::
 {
   mSkeletonState->Deactivate();
   MediaResource* resource = mDecoder->GetResource();
   NS_ENSURE_TRUE(resource != nullptr, SEEK_FATAL_ERROR);
   nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
   NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
   return SEEK_INDEX_FAIL;
 }
- 
+
 OggReader::IndexedSeekResult OggReader::SeekToKeyframeUsingIndex(int64_t aTarget)
 {
   MediaResource* resource = mDecoder->GetResource();
   NS_ENSURE_TRUE(resource != nullptr, SEEK_FATAL_ERROR);
   if (!HasSkeleton() || !mSkeletonState->HasIndex()) {
     return SEEK_INDEX_FAIL;
   }
   // We have an index from the Skeleton track, try to use it to seek.
@@ -1226,30 +1220,29 @@ OggReader::IndexedSeekResult OggReader::
     // Index must be invalid.
     return RollbackIndexedSeek(tell);
   }
   LOG(PR_LOG_DEBUG, ("Seeking using index to keyframe at offset %lld\n",
                      keyframe.mKeyPoint.mOffset));
   nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET,
                               keyframe.mKeyPoint.mOffset);
   NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
-  mPageOffset = keyframe.mKeyPoint.mOffset;
 
   // We've moved the read set, so reset decode.
   res = ResetDecode();
   NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
 
   // Check that the page the index thinks is exactly here is actually exactly
   // here. If not, the index is invalid.
   ogg_page page;
   int skippedBytes = 0;
   PageSyncResult syncres = PageSync(resource,
                                     &mOggState,
                                     false,
-                                    mPageOffset,
+                                    keyframe.mKeyPoint.mOffset,
                                     resource->GetLength(),
                                     &page,
                                     skippedBytes);
   NS_ENSURE_TRUE(syncres != PAGE_SYNC_ERROR, SEEK_FATAL_ERROR);
   if (syncres != PAGE_SYNC_OK || skippedBytes != 0) {
     LOG(PR_LOG_DEBUG, ("Indexed-seek failure: Ogg Skeleton Index is invalid "
                        "or sync error after seek"));
     return RollbackIndexedSeek(tell);
@@ -1264,17 +1257,16 @@ OggReader::IndexedSeekResult OggReader::
   if (codecState &&
       codecState->mActive &&
       ogg_stream_pagein(&codecState->mState, &page) != 0)
   {
     // Couldn't insert page into the ogg resource, or somehow the resource
     // is no longer active.
     return RollbackIndexedSeek(tell);
   }
-  mPageOffset = keyframe.mKeyPoint.mOffset + page.header_len + page.body_len;
   return SEEK_OK;
 }
 
 nsresult OggReader::SeekInBufferedRange(int64_t aTarget,
                                           int64_t aAdjustedTarget,
                                           int64_t aStartTime,
                                           int64_t aEndTime,
                                           const nsTArray<SeekRange>& aRanges,
@@ -1330,17 +1322,17 @@ nsresult OggReader::SeekInBufferedRange(
 }
 
 nsresult OggReader::SeekInUnbuffered(int64_t aTarget,
                                        int64_t aStartTime,
                                        int64_t aEndTime,
                                        const nsTArray<SeekRange>& aRanges)
 {
   LOG(PR_LOG_DEBUG, ("%p Seeking in unbuffered data to %lld using bisection search", mDecoder, aTarget));
-  
+
   // If we've got an active Theora bitstream, determine the maximum possible
   // time in usecs which a keyframe could be before a given interframe. We
   // subtract this from our seek target, seek to the new target, and then
   // will decode forward to the original seek target. We should encounter a
   // keyframe in that interval. This prevents us from needing to run two
   // bisections; one for the seek target frame, and another to find its
   // keyframe. It's usually faster to just download this extra data, rather
   // tham perform two bisections to find the seek target's keyframe. We
@@ -1381,17 +1373,16 @@ nsresult OggReader::Seek(int64_t aTarget
   }
 
   if (adjustedTarget == aStartTime) {
     // We've seeked to the media start. Just seek to the offset of the first
     // content page.
     res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0);
     NS_ENSURE_SUCCESS(res,res);
 
-    mPageOffset = 0;
     res = ResetDecode(true);
     NS_ENSURE_SUCCESS(res,res);
 
     NS_ASSERTION(aStartTime != -1, "mStartTime should be known");
     {
       ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
       mDecoder->UpdatePlaybackPosition(aStartTime);
     }
@@ -1479,28 +1470,28 @@ PageSync(MediaResource* aResource,
         // End of file.
         return PAGE_SYNC_END_OF_RANGE;
       }
       readHead += bytesRead;
 
       // Update the synchronisation layer with the number
       // of bytes written to the buffer
       ret = ogg_sync_wrote(aState, bytesRead);
-      NS_ENSURE_TRUE(ret == 0, PAGE_SYNC_ERROR);    
+      NS_ENSURE_TRUE(ret == 0, PAGE_SYNC_ERROR);
       continue;
     }
 
     if (ret < 0) {
       NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
       aSkippedBytes += -ret;
       NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
       continue;
     }
   }
-  
+
   return PAGE_SYNC_OK;
 }
 
 nsresult OggReader::SeekBisection(int64_t aTarget,
                                     const SeekRange& aRange,
                                     uint32_t aFuzz)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
@@ -1508,17 +1499,16 @@ nsresult OggReader::SeekBisection(int64_
   MediaResource* resource = mDecoder->GetResource();
 
   if (aTarget == aRange.mTimeStart) {
     if (NS_FAILED(ResetDecode())) {
       return NS_ERROR_FAILURE;
     }
     res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0);
     NS_ENSURE_SUCCESS(res,res);
-    mPageOffset = 0;
     return NS_OK;
   }
 
   // Bisection search, find start offset of last page with end time less than
   // the seek target.
   ogg_int64_t startOffset = aRange.mOffsetStart;
   ogg_int64_t startTime = aRange.mTimeStart;
   ogg_int64_t startLength = 0; // Length of the page at startOffset.
@@ -1548,17 +1538,17 @@ nsresult OggReader::SeekBisection(int64_
     ogg_int64_t pageLength = 0;
     ogg_int64_t granuleTime = -1;
     bool mustBackoff = false;
 
     // Guess where we should bisect to, based on the bit rate and the time
     // remaining in the interval. Loop until we can determine the time at
     // the guess offset.
     while (true) {
-  
+
       // Discard any previously buffered packets/pages.
       if (NS_FAILED(ResetDecode())) {
         return NS_ERROR_FAILURE;
       }
 
       interval = endOffset - startOffset - startLength;
       if (interval == 0) {
         // Our interval is empty, we've found the optimal seek point, as the
@@ -1609,45 +1599,44 @@ nsresult OggReader::SeekBisection(int64_
                               endOffset, endTime, interval, target, guess));
 
       NS_ASSERTION(guess >= startOffset + startLength, "Guess must be after range start");
       NS_ASSERTION(guess < endOffset, "Guess must be before range end");
       NS_ASSERTION(guess != previousGuess, "Guess should be different to previous");
       previousGuess = guess;
 
       hops++;
-    
+
       // Locate the next page after our seek guess, and then figure out the
       // granule time of the audio and video bitstreams there. We can then
       // make a bisection decision based on our location in the media.
       PageSyncResult res = PageSync(resource,
                                     &mOggState,
                                     false,
                                     guess,
                                     endOffset,
                                     &page,
                                     skippedBytes);
       NS_ENSURE_TRUE(res != PAGE_SYNC_ERROR, NS_ERROR_FAILURE);
 
-      // We've located a page of length |ret| at |guess + skippedBytes|.
-      // Remember where the page is located.
-      pageOffset = guess + skippedBytes;
-      pageLength = page.header_len + page.body_len;
-      mPageOffset = pageOffset + pageLength;
-
       if (res == PAGE_SYNC_END_OF_RANGE) {
         // Our guess was too close to the end, we've ended up reading the end
         // page. Backoff exponentially from the end point, in case the last
         // page/frame/sample is huge.
         mustBackoff = true;
         SEEK_LOG(PR_LOG_DEBUG, ("Hit the end of range, backing off"));
         continue;
       }
 
-      // Read pages until we can determine the granule time of the audio and 
+      // We've located a page of length |ret| at |guess + skippedBytes|.
+      // Remember where the page is located.
+      pageOffset = guess + skippedBytes;
+      pageLength = page.header_len + page.body_len;
+
+      // Read pages until we can determine the granule time of the audio and
       // video bitstream.
       ogg_int64_t audioTime = -1;
       ogg_int64_t videoTime = -1;
       do {
         // Add the page to its codec state, determine its granule time.
         uint32_t serial = ogg_page_serialno(&page);
         OggCodecState* codecState = mCodecStore.Get(serial);
         if (codecState && codecState->mActive) {
@@ -1661,43 +1650,43 @@ nsresult OggReader::SeekBisection(int64_
           if (mVorbisState && serial == mVorbisState->mSerial) {
             audioTime = mVorbisState->Time(granulepos);
 #ifdef MOZ_OPUS
           } else if (mOpusState && serial == mOpusState->mSerial) {
             audioTime = mOpusState->Time(granulepos);
 #endif
           }
         }
-        
+
         if (HasVideo() &&
             granulepos > 0 &&
             serial == mTheoraState->mSerial &&
             videoTime == -1) {
-          videoTime = mTheoraState->StartTime(granulepos);
+          videoTime = mTheoraState->Time(granulepos);
         }
 
-        if (mPageOffset == endOffset) {
+        if (pageOffset + pageLength >= endOffset) {
           // Hit end of readable data.
           break;
         }
 
-        if (ReadOggPage(&page) == -1) {
+        if (!ReadOggPage(&page)) {
           break;
         }
-        
+
       } while ((HasAudio() && audioTime == -1) ||
                (HasVideo() && videoTime == -1));
 
-      NS_ASSERTION(mPageOffset <= endOffset, "Page read cursor should be inside range");
 
       if ((HasAudio() && audioTime == -1) ||
-          (HasVideo() && videoTime == -1)) 
+          (HasVideo() && videoTime == -1))
       {
         // We don't have timestamps for all active tracks...
-        if (pageOffset == startOffset + startLength && mPageOffset == endOffset) {
+        if (pageOffset == startOffset + startLength &&
+            pageOffset + pageLength >= endOffset) {
           // We read the entire interval without finding timestamps for all
           // active tracks. We know the interval start offset is before the seek
           // target, and the interval end is after the seek target, and we can't
           // terminate inside the interval, so we terminate the seek at the
           // start of the interval.
           interval = 0;
           break;
         }
@@ -1717,33 +1706,31 @@ nsresult OggReader::SeekBisection(int64_
 
     if (interval == 0) {
       // Seek termination condition; we've found the page boundary of the
       // last page before the target, and the first page after the target.
       SEEK_LOG(PR_LOG_DEBUG, ("Terminating seek at offset=%lld", startOffset));
       NS_ASSERTION(startTime < aTarget, "Start time must always be less than target");
       res = resource->Seek(nsISeekableStream::NS_SEEK_SET, startOffset);
       NS_ENSURE_SUCCESS(res,res);
-      mPageOffset = startOffset;
       if (NS_FAILED(ResetDecode())) {
         return NS_ERROR_FAILURE;
       }
       break;
     }
 
     SEEK_LOG(PR_LOG_DEBUG, ("Time at offset %lld is %lld", guess, granuleTime));
     if (granuleTime < seekTarget && granuleTime > seekLowerBound) {
       // We're within the fuzzy region in which we want to terminate the search.
       res = resource->Seek(nsISeekableStream::NS_SEEK_SET, pageOffset);
       NS_ENSURE_SUCCESS(res,res);
-      mPageOffset = pageOffset;
       if (NS_FAILED(ResetDecode())) {
         return NS_ERROR_FAILURE;
       }
-      SEEK_LOG(PR_LOG_DEBUG, ("Terminating seek at offset=%lld", mPageOffset));
+      SEEK_LOG(PR_LOG_DEBUG, ("Terminating seek at offset=%lld", pageOffset));
       break;
     }
 
     if (granuleTime >= seekTarget) {
       // We've landed after the seek target.
       NS_ASSERTION(pageOffset < endOffset, "offset_end must decrease");
       endOffset = pageOffset;
       endTime = granuleTime;
@@ -1774,17 +1761,17 @@ nsresult OggReader::GetBuffered(TimeRang
 #ifdef OGG_ESTIMATE_BUFFERED
   MediaResource* stream = mDecoder->GetResource();
   int64_t durationUs = 0;
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     durationUs = mDecoder->GetMediaDuration();
   }
   GetEstimatedBufferedTimeRanges(stream, durationUs, aBuffered);
-  
+
   return NS_OK;
 #else
   // HasAudio and HasVideo are not used here as they take a lock and cause
   // a deadlock. Accessing mInfo doesn't require a lock - it doesn't change
   // after metadata is read.
   if (!mInfo.mHasVideo && !mInfo.mHasAudio) {
     // No need to search through the file if there are no audio or video tracks
     return NS_OK;
--- a/content/media/ogg/OggReader.h
+++ b/content/media/ogg/OggReader.h
@@ -207,17 +207,17 @@ private:
   // bisection's search space when the target isn't in a known buffered range.
   SeekRange SelectSeekRange(const nsTArray<SeekRange>& aRanges,
                             int64_t aTarget,
                             int64_t aStartTime,
                             int64_t aEndTime,
                             bool aExact);
 private:
 
-  // Decodes a packet of Vorbis data, and inserts its samples into the 
+  // Decodes a packet of Vorbis data, and inserts its samples into the
   // audio queue.
   nsresult DecodeVorbis(ogg_packet* aPacket);
 
   // Decodes a packet of Opus data, and inserts its samples into the
   // audio queue.
   nsresult DecodeOpus(ogg_packet* aPacket);
 
   // Downmix multichannel Audio samples to Stereo.
@@ -229,19 +229,19 @@ private:
 
   // Decodes a packet of Theora data, and inserts its frame into the
   // video queue. May return NS_ERROR_OUT_OF_MEMORY. Caller must have obtained
   // the reader's monitor. aTimeThreshold is the current playback position
   // in media time in microseconds. Frames with an end time before this will
   // not be enqueued.
   nsresult DecodeTheora(ogg_packet* aPacket, int64_t aTimeThreshold);
 
-  // Read a page of data from the Ogg file. Returns the offset of the start
-  // of the page, or -1 if the page read failed.
-  int64_t ReadOggPage(ogg_page* aPage);
+  // Read a page of data from the Ogg file. Returns true if a page has been
+  // read, false if the page read failed or end of file reached.
+  bool ReadOggPage(ogg_page* aPage);
 
   // Reads and decodes header packets for aState, until either header decode
   // fails, or is complete. Initializes the codec state before returning.
   // Returns true if reading headers and initializtion of the stream
   // succeeds.
   bool ReadHeaders(OggCodecState* aState);
 
   // Reads the next link in the chain.
@@ -290,20 +290,16 @@ private:
   // using this codec data.
   uint32_t mVorbisSerial;
   uint32_t mOpusSerial;
   uint32_t mTheoraSerial;
   vorbis_info mVorbisInfo;
   int mOpusPreSkip;
   th_info mTheoraInfo;
 
-  // The offset of the end of the last page we've read, or the start of
-  // the page we're about to read.
-  int64_t mPageOffset;
-
   // The picture region inside Theora frame to be displayed, if we have
   // a Theora video track.
   nsIntRect mPicture;
 
   // True if we are decoding a chained ogg. Reading or writing to this member
   // should be done with |mMonitor| acquired.
   bool mIsChained;
 
--- a/dom/activities/src/ActivityMessageConfigurator.js
+++ b/dom/activities/src/ActivityMessageConfigurator.js
@@ -16,18 +16,18 @@ function debug(aMsg) {
 /**
   * nsISystemMessagesConfigurator implementation.
   */
 function ActivityMessageConfigurator() {
   debug("ActivityMessageConfigurator");
 }
 
 ActivityMessageConfigurator.prototype = {
-  get safeToSendBeforeRunningApp() {
-    debug("safeToSendBeforeRunningApp returning false");
-    return false;
+  get mustShowRunningApp() {
+    debug("mustShowRunningApp returning true");
+    return true;
   },
 
   classID: Components.ID("{d2296daa-c406-4c5e-b698-e5f2c1715798}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemMessagesConfigurator])
 }
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ActivityMessageConfigurator]);
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -9627,17 +9627,17 @@ class CGJSImplClass(CGBindingImplClass):
             "// GlobalObject will go through wrappers as needed for us, and\n"
             "// is simpler than the right UnwrapArg incantation.\n"
             "GlobalObject global(cx, &args[0].toObject());\n"
             "if (global.Failed()) {\n"
             "  return false;\n"
             "}\n"
             "nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global.Get());\n"
             "if (!window) {\n"
-            '  return ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "Argument 1 of ${ifaceName}._create");\n'
+            '  return ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "Argument 1 of ${ifaceName}._create", "Window");\n'
             "}\n"
             "JS::Rooted<JSObject*> arg(cx, &args[1].toObject());\n"
             "nsRefPtr<${implName}> impl = new ${implName}(arg, window);\n"
             "return WrapNewBindingObject(cx, arg, impl, args.rval());").substitute({
                 "ifaceName": self.descriptor.interface.identifier.name,
                 "implName": self.descriptor.name
                 })
 
--- a/dom/bluetooth/BluetoothA2dpManager.cpp
+++ b/dom/bluetooth/BluetoothA2dpManager.cpp
@@ -40,20 +40,19 @@ BluetoothA2dpManager::Observe(nsISupport
     return NS_OK;
   }
 
   MOZ_ASSERT(false, "BluetoothA2dpManager got unexpected topic!");
   return NS_ERROR_UNEXPECTED;
 }
 
 BluetoothA2dpManager::BluetoothA2dpManager()
-  : mConnected(false)
-  , mPlaying(false)
-  , mSinkState(SinkState::SINK_DISCONNECTED)
 {
+  ResetA2dp();
+  ResetAvrcp();
 }
 
 bool
 BluetoothA2dpManager::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
@@ -70,30 +69,49 @@ BluetoothA2dpManager::~BluetoothA2dpMana
 {
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   NS_ENSURE_TRUE_VOID(obs);
   if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
     BT_WARNING("Failed to remove shutdown observer!");
   }
 }
 
-static SinkState
+void
+BluetoothA2dpManager::ResetA2dp()
+{
+  mA2dpConnected = false;
+  mPlaying = false;
+  mSinkState = SinkState::SINK_DISCONNECTED;
+}
+
+void
+BluetoothA2dpManager::ResetAvrcp()
+{
+  mAvrcpConnected = false;
+  mDuration = 0;
+  mMediaNumber = 0;
+  mTotalMediaCount = 0;
+  mPosition = 0;
+  mPlayStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN;
+}
+
+static BluetoothA2dpManager::SinkState
 StatusStringToSinkState(const nsAString& aStatus)
 {
-  SinkState state;
+  BluetoothA2dpManager::SinkState state;
   if (aStatus.EqualsLiteral("disconnected")) {
-    state = SinkState::SINK_DISCONNECTED;
+    state = BluetoothA2dpManager::SinkState::SINK_DISCONNECTED;
   } else if (aStatus.EqualsLiteral("connecting")) {
-    state = SinkState::SINK_CONNECTING;
+    state = BluetoothA2dpManager::SinkState::SINK_CONNECTING;
   } else if (aStatus.EqualsLiteral("connected")) {
-    state = SinkState::SINK_CONNECTED;
+    state = BluetoothA2dpManager::SinkState::SINK_CONNECTED;
   } else if (aStatus.EqualsLiteral("playing")) {
-    state = SinkState::SINK_PLAYING;
+    state = BluetoothA2dpManager::SinkState::SINK_PLAYING;
   } else if (aStatus.EqualsLiteral("disconnecting")) {
-    state = SinkState::SINK_DISCONNECTING;
+    state = BluetoothA2dpManager::SinkState::SINK_DISCONNECTING;
   } else {
     MOZ_ASSERT(false, "Unknown sink state");
   }
   return state;
 }
 
 //static
 BluetoothA2dpManager*
@@ -135,17 +153,17 @@ BluetoothA2dpManager::Connect(const nsAS
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!aDeviceAddress.IsEmpty());
 
   if (sInShutdown) {
     NS_WARNING("Connect called while in shutdown!");
     return false;
   }
 
-  if (mConnected) {
+  if (mA2dpConnected) {
     NS_WARNING("BluetoothA2dpManager is connected");
     return false;
   }
 
   mDeviceAddress = aDeviceAddress;
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, false);
@@ -153,17 +171,17 @@ BluetoothA2dpManager::Connect(const nsAS
                                     NS_LITERAL_STRING("Connect"));
 
   return NS_SUCCEEDED(rv);
 }
 
 void
 BluetoothA2dpManager::Disconnect()
 {
-  if (!mConnected) {
+  if (!mA2dpConnected) {
     NS_WARNING("BluetoothA2dpManager has been disconnected");
     return;
   }
 
   MOZ_ASSERT(!mDeviceAddress.IsEmpty());
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
@@ -180,17 +198,17 @@ BluetoothA2dpManager::HandleSinkProperty
     aSignal.value().get_ArrayOfBluetoothNamedValue();
   MOZ_ASSERT(arr.Length() == 1);
 
   const nsString& name = arr[0].name();
   const BluetoothValue& value = arr[0].value();
   if (name.EqualsLiteral("Connected")) {
     // Indicates if a stream is setup to a A2DP sink on the remote device.
     MOZ_ASSERT(value.type() == BluetoothValue::Tbool);
-    mConnected = value.get_bool();
+    mA2dpConnected = value.get_bool();
     NotifyStatusChanged();
     NotifyAudioManager();
   } else if (name.EqualsLiteral("Playing")) {
     // Indicates if a stream is active to a A2DP sink on the remote device.
     MOZ_ASSERT(value.type() == BluetoothValue::Tbool);
     mPlaying = value.get_bool();
   } else if (name.EqualsLiteral("State")) {
     MOZ_ASSERT(value.type() == BluetoothValue::TnsString);
@@ -239,17 +257,17 @@ BluetoothA2dpManager::HandleSinkStateCha
 void
 BluetoothA2dpManager::NotifyStatusChanged()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   NS_NAMED_LITERAL_STRING(type, BLUETOOTH_A2DP_STATUS_CHANGED_ID);
   InfallibleTArray<BluetoothNamedValue> parameters;
 
-  BluetoothValue v = mConnected;
+  BluetoothValue v = mA2dpConnected;
   parameters.AppendElement(
     BluetoothNamedValue(NS_LITERAL_STRING("connected"), v));
 
   v = mDeviceAddress;
   parameters.AppendElement(
     BluetoothNamedValue(NS_LITERAL_STRING("address"), v));
 
   if (!BroadcastSystemMessage(type, parameters)) {
@@ -263,17 +281,17 @@ BluetoothA2dpManager::NotifyAudioManager
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIObserverService> obs =
     do_GetService("@mozilla.org/observer-service;1");
   NS_ENSURE_TRUE_VOID(obs);
 
   nsAutoString data;
-  data.AppendInt(mConnected);
+  data.AppendInt(mA2dpConnected);
 
   if (NS_FAILED(obs->NotifyObservers(this,
                                      BLUETOOTH_A2DP_STATUS_CHANGED_ID,
                                      data.BeginReading()))) {
     NS_WARNING("Failed to notify bluetooth-a2dp-status-changed observsers!");
   }
 }
 
@@ -290,10 +308,93 @@ BluetoothA2dpManager::OnUpdateSdpRecords
 }
 
 void
 BluetoothA2dpManager::GetAddress(nsAString& aDeviceAddress)
 {
   aDeviceAddress = mDeviceAddress;
 }
 
+bool
+BluetoothA2dpManager::IsConnected()
+{
+  return mA2dpConnected;
+}
+
+void
+BluetoothA2dpManager::SetAvrcpConnected(bool aConnected)
+{
+  mAvrcpConnected = aConnected;
+  if (!aConnected) {
+    ResetAvrcp();
+  }
+}
+
+bool
+BluetoothA2dpManager::IsAvrcpConnected()
+{
+  return mAvrcpConnected;
+}
+
+void
+BluetoothA2dpManager::UpdateMetaData(const nsAString& aTitle,
+                                     const nsAString& aArtist,
+                                     const nsAString& aAlbum,
+                                     uint32_t aMediaNumber,
+                                     uint32_t aTotalMediaCount,
+                                     uint32_t aDuration)
+{
+  mTitle.Assign(aTitle);
+  mArtist.Assign(aArtist);
+  mAlbum.Assign(aAlbum);
+  mMediaNumber = aMediaNumber;
+  mTotalMediaCount = aTotalMediaCount;
+  mDuration = aDuration;
+}
+
+void
+BluetoothA2dpManager::UpdatePlayStatus(uint32_t aDuration,
+                                       uint32_t aPosition,
+                                       ControlPlayStatus aPlayStatus)
+{
+  mDuration = aDuration;
+  mPosition = aPosition;
+  mPlayStatus = aPlayStatus;
+}
+
+void
+BluetoothA2dpManager::GetAlbum(nsAString& aAlbum)
+{
+    aAlbum.Assign(mAlbum);
+}
+
+uint32_t
+BluetoothA2dpManager::GetDuration()
+{
+  return mDuration;
+}
+
+ControlPlayStatus
+BluetoothA2dpManager::GetPlayStatus()
+{
+  return mPlayStatus;
+}
+
+uint32_t
+BluetoothA2dpManager::GetPosition()
+{
+  return mPosition;
+}
+
+uint32_t
+BluetoothA2dpManager::GetMediaNumber()
+{
+  return mMediaNumber;
+}
+
+void
+BluetoothA2dpManager::GetTitle(nsAString& aTitle)
+{
+  aTitle.Assign(mTitle);
+}
+
 NS_IMPL_ISUPPORTS1(BluetoothA2dpManager, nsIObserver)
 
--- a/dom/bluetooth/BluetoothA2dpManager.h
+++ b/dom/bluetooth/BluetoothA2dpManager.h
@@ -7,60 +7,93 @@
 #ifndef mozilla_dom_bluetooth_bluetootha2dpmanager_h__
 #define mozilla_dom_bluetooth_bluetootha2dpmanager_h__
 
 #include "BluetoothCommon.h"
 #include "BluetoothProfileManagerBase.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
-enum SinkState {
-  SINK_DISCONNECTED = 1,
-  SINK_CONNECTING,
-  SINK_CONNECTED,
-  SINK_PLAYING,
-  SINK_DISCONNECTING
-};
-
 class BluetoothA2dpManagerObserver;
-class BluetoothValue;
-class BluetoothSocket;
 
 class BluetoothA2dpManager : public BluetoothProfileManagerBase
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
+  enum SinkState {
+    SINK_DISCONNECTED = 1,
+    SINK_CONNECTING,
+    SINK_CONNECTED,
+    SINK_PLAYING,
+    SINK_DISCONNECTING
+  };
+
   static BluetoothA2dpManager* Get();
   ~BluetoothA2dpManager();
+  void ResetA2dp();
+  void ResetAvrcp();
 
-  bool Connect(const nsAString& aDeviceAddress);
-  void Disconnect();
-
+  // Member functions inherited from parent class BluetoothProfileManagerBase
   virtual void OnGetServiceChannel(const nsAString& aDeviceAddress,
                                    const nsAString& aServiceUuid,
                                    int aChannel) MOZ_OVERRIDE;
   virtual void OnUpdateSdpRecords(const nsAString& aDeviceAddress) MOZ_OVERRIDE;
   virtual void GetAddress(nsAString& aDeviceAddress) MOZ_OVERRIDE;
+  virtual bool IsConnected() MOZ_OVERRIDE;
 
+  // A2DP member functions
+  bool Connect(const nsAString& aDeviceAddress);
+  void Disconnect();
   void HandleSinkPropertyChanged(const BluetoothSignal& aSignal);
 
+  // AVRCP member functions
+  void SetAvrcpConnected(bool aConnected);
+  bool IsAvrcpConnected();
+  void UpdateMetaData(const nsAString& aTitle,
+                      const nsAString& aArtist,
+                      const nsAString& aAlbum,
+                      uint32_t aMediaNumber,
+                      uint32_t aTotalMediaCount,
+                      uint32_t aDuration);
+  void UpdatePlayStatus(uint32_t aDuration,
+                        uint32_t aPosition,
+                        ControlPlayStatus aPlayStatus);
+  void GetAlbum(nsAString& aAlbum);
+  uint32_t GetDuration();
+  ControlPlayStatus GetPlayStatus();
+  uint32_t GetPosition();
+  uint32_t GetMediaNumber();
+  void GetTitle(nsAString& aTitle);
+
 private:
   BluetoothA2dpManager();
   bool Init();
-  void Cleanup();
 
   void HandleSinkStateChanged(SinkState aState);
   void HandleShutdown();
 
   void NotifyStatusChanged();
   void NotifyAudioManager();
 
-  bool mConnected;
+  nsString mDeviceAddress;
+
+  // A2DP data member
+  bool mA2dpConnected;
   bool mPlaying;
-  nsString mDeviceAddress;
   SinkState mSinkState;
+
+  // AVRCP data member
+  bool mAvrcpConnected;
+  nsString mAlbum;
+  nsString mArtist;
+  nsString mTitle;
+  uint32_t mDuration;
+  uint32_t mMediaNumber;
+  uint32_t mTotalMediaCount;
+  uint32_t mPosition;
+  ControlPlayStatus mPlayStatus;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/BluetoothAdapter.cpp
+++ b/dom/bluetooth/BluetoothAdapter.cpp
@@ -1,35 +1,37 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "base/basictypes.h"
-#include "BluetoothAdapter.h"
-#include "BluetoothDevice.h"
-#include "BluetoothReplyRunnable.h"
-#include "BluetoothService.h"
-#include "BluetoothUtils.h"
 #include "GeneratedEvents.h"
-
 #include "nsContentUtils.h"
 #include "nsCxPusher.h"
 #include "nsDOMClassInfo.h"
 #include "nsIDOMBluetoothDeviceEvent.h"
 #include "nsTArrayHelpers.h"
 #include "DOMRequest.h"
 #include "nsThreadUtils.h"
 
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/LazyIdleThread.h"
 #include "mozilla/Util.h"
 
+#include "BluetoothAdapter.h"
+#include "BluetoothDevice.h"
+#include "BluetoothReplyRunnable.h"
+#include "BluetoothService.h"
+#include "BluetoothUtils.h"
+#include "MediaMetaData.h"
+#include "MediaPlayStatus.h"
+
 using namespace mozilla;
 
 USING_BLUETOOTH_NAMESPACE
 
 DOMCI_DATA(BluetoothAdapter, BluetoothAdapter)
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(BluetoothAdapter,
                                                nsDOMEventTargetHelper)
@@ -828,9 +830,76 @@ BluetoothAdapter::IsScoConnected(nsIDOMD
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
   bs->IsScoConnected(results);
 
   req.forget(aRequest);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+BluetoothAdapter::SendMediaMetaData(const JS::Value& aOptions,
+                                    nsIDOMDOMRequest** aRequest)
+{
+  MediaMetaData metadata;
+
+  nsresult rv;
+  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  AutoPushJSContext cx(sc->GetNativeContext());
+  rv = metadata.Init(cx, &aOptions);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<BluetoothReplyRunnable> results =
+    new BluetoothVoidReplyRunnable(req);
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
+  bs->SendMetaData(metadata.mTitle,
+                   metadata.mArtist,
+                   metadata.mAlbum,
+                   metadata.mMediaNumber,
+                   metadata.mTotalMediaCount,
+                   metadata.mDuration,
+                   results);
+
+  req.forget(aRequest);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+BluetoothAdapter::SendMediaPlayStatus(const JS::Value& aOptions,
+                                      nsIDOMDOMRequest** aRequest)
+{
+  MediaPlayStatus status;
+
+  nsresult rv;
+  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  AutoPushJSContext cx(sc->GetNativeContext());
+  rv = status.Init(cx, &aOptions);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIDOMDOMRequest> req;
+  rv = PrepareDOMRequest(GetOwner(), getter_AddRefs(req));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<BluetoothReplyRunnable> results =
+    new BluetoothVoidReplyRunnable(req);
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
+  bs->SendPlayStatus(status.mDuration,
+                     status.mPosition,
+                     status.mPlayStatus,
+                     results);
+
+  req.forget(aRequest);
+  return NS_OK;
+}
+
 NS_IMPL_EVENT_HANDLER(BluetoothAdapter, devicefound)
--- a/dom/bluetooth/BluetoothCommon.h
+++ b/dom/bluetooth/BluetoothCommon.h
@@ -80,11 +80,21 @@ typedef mozilla::Observer<BluetoothSigna
 enum BluetoothObjectType {
   TYPE_MANAGER = 0,
   TYPE_ADAPTER = 1,
   TYPE_DEVICE = 2,
 
   TYPE_INVALID
 };
 
+enum ControlPlayStatus {
+  PLAYSTATUS_STOPPED  = 0x00,
+  PLAYSTATUS_PLAYING  = 0x01,
+  PLAYSTATUS_PAUSED   = 0x02,
+  PLAYSTATUS_FWD_SEEK = 0x03,
+  PLAYSTATUS_REV_SEEK = 0x04,
+  PLAYSTATUS_UNKNOWN,
+  PLAYSTATUS_ERROR    = 0xFF,
+};
+
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_bluetoothcommon_h__
--- a/dom/bluetooth/BluetoothOppManager.cpp
+++ b/dom/bluetooth/BluetoothOppManager.cpp
@@ -336,17 +336,17 @@ BluetoothOppManager::Listen()
 
   return true;
 }
 
 void
 BluetoothOppManager::StartSendingNextFile()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!IsTransferring());
+  MOZ_ASSERT(!IsConnected());
   MOZ_ASSERT(mBlobs.Length() > mCurrentBlobIndex + 1);
 
   mIsServer = false;
   mBlob = mBlobs[++mCurrentBlobIndex];
 
   // Before sending content, we have to send a header including
   // information such as file name, file length and content type.
   ExtractBlobHeaders();
@@ -1099,17 +1099,17 @@ void
 BluetoothOppManager::CheckPutFinal(uint32_t aNumRead)
 {
   if (mSentFileLength + aNumRead >= mFileLength) {
     mWaitingToSendPutFinal = true;
   }
 }
 
 bool
-BluetoothOppManager::IsTransferring()
+BluetoothOppManager::IsConnected()
 {
   return (mConnected && !mSendTransferCompleteFlag);
 }
 
 void
 BluetoothOppManager::GetAddress(nsAString& aDeviceAddress)
 {
   return mSocket->GetAddress(aDeviceAddress);
--- a/dom/bluetooth/BluetoothOppManager.h
+++ b/dom/bluetooth/BluetoothOppManager.h
@@ -71,33 +71,30 @@ public:
   void SendPutFinalRequest();
   void SendDisconnectRequest();
   void SendAbortRequest();
 
   void ExtractPacketHeaders(const ObexHeaderSet& aHeader);
   bool ExtractBlobHeaders();
   void CheckPutFinal(uint32_t aNumRead);
 
-  // Return true if there is an ongoing file-transfer session, please see
-  // Bug 827267 for more information.
-  bool IsTransferring();
-
   // Implement interface BluetoothSocketObserver
   void ReceiveSocketData(
     BluetoothSocket* aSocket,
     nsAutoPtr<mozilla::ipc::UnixSocketRawData>& aMessage) MOZ_OVERRIDE;
 
   virtual void OnConnectSuccess(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnConnectError(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnGetServiceChannel(const nsAString& aDeviceAddress,
                                    const nsAString& aServiceUuid,
                                    int aChannel) MOZ_OVERRIDE;
   virtual void OnUpdateSdpRecords(const nsAString& aDeviceAddress) MOZ_OVERRIDE;
   virtual void GetAddress(nsAString& aDeviceAddress) MOZ_OVERRIDE;
+  virtual bool IsConnected() MOZ_OVERRIDE;
 
 private:
   BluetoothOppManager();
   bool Init();
   void HandleShutdown();
 
   void StartFileTransfer();
   void StartSendingNextFile();
--- a/dom/bluetooth/BluetoothProfileManagerBase.h
+++ b/dom/bluetooth/BluetoothProfileManagerBase.h
@@ -25,13 +25,14 @@ BEGIN_BLUETOOTH_NAMESPACE
 class BluetoothProfileManagerBase : public nsIObserver
 {
 public:
   virtual void OnGetServiceChannel(const nsAString& aDeviceAddress,
                                    const nsAString& aServiceUuid,
                                    int aChannel) = 0;
   virtual void OnUpdateSdpRecords(const nsAString& aDeviceAddress) = 0;
   virtual void GetAddress(nsAString& aDeviceAddress) = 0;
+  virtual bool IsConnected() = 0;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif  //#ifndef mozilla_dom_bluetooth_bluetoothprofilemanagerbase_h__
--- a/dom/bluetooth/BluetoothService.h
+++ b/dom/bluetooth/BluetoothService.h
@@ -265,16 +265,36 @@ public:
   ConnectSco(BluetoothReplyRunnable* aRunnable) = 0;
 
   virtual void
   DisconnectSco(BluetoothReplyRunnable* aRunnable) = 0;
 
   virtual void
   IsScoConnected(BluetoothReplyRunnable* aRunnable) = 0;
 
+  virtual void
+  SendMetaData(const nsAString& aTitle,
+               const nsAString& aArtist,
+               const nsAString& aAlbum,
+               int64_t aMediaNumber,
+               int64_t aTotalMediaCount,
+               int64_t aDuration,
+               BluetoothReplyRunnable* aRunnable) = 0;
+
+  virtual void
+  SendPlayStatus(int64_t aDuration,
+                 int64_t aPosition,
+                 const nsAString& aPlayStatus,
+                 BluetoothReplyRunnable* aRunnable) = 0;
+
+  virtual void
+  UpdatePlayStatus(uint32_t aDuration,
+                   uint32_t aPosition,
+                   ControlPlayStatus aPlayStatus) = 0;
+
   virtual nsresult
   SendSinkMessage(const nsAString& aDeviceAddresses,
                   const nsAString& aMessage) = 0;
 
   bool
   IsEnabled() const
   {
     return mEnabled;
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/MediaMetaData.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/ */
+
+#include "BluetoothCommon.h"
+#include "MediaMetaData.h"
+
+#include "nsCxPusher.h"
+#include "nsContentUtils.h"
+#include "nsJSUtils.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+USING_BLUETOOTH_NAMESPACE
+
+MediaMetaData::MediaMetaData() : mDuration(-1)
+                               , mMediaNumber(-1)
+                               , mTotalMediaCount(-1)
+{
+}
+
+nsresult
+MediaMetaData::Init(JSContext* aCx, const jsval* aVal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!aCx || !aVal) {
+    return NS_OK;
+  }
+
+  if (!aVal->isObject()) {
+    return aVal->isNullOrUndefined() ? NS_OK : NS_ERROR_TYPE_ERR;
+  }
+
+  JS::RootedObject obj(aCx, &aVal->toObject());
+  nsCxPusher pusher;
+  pusher.Push(aCx);
+  JSAutoCompartment ac(aCx, obj);
+
+  JS::Value value;
+  NS_ENSURE_STATE(JS_GetProperty(aCx, obj, "mAlbum", &value));
+  if (JSVAL_IS_STRING(value)) {
+    nsDependentJSString jsString;
+    NS_ENSURE_STATE(jsString.init(aCx, value.toString()));
+    mAlbum = jsString;
+  }
+
+  NS_ENSURE_STATE(JS_GetProperty(aCx, obj, "mArtist", &value));
+  if (JSVAL_IS_STRING(value)) {
+    nsDependentJSString jsString;
+    NS_ENSURE_STATE(JSVAL_IS_STRING(value));
+    NS_ENSURE_STATE(jsString.init(aCx, value.toString()));
+    mArtist = jsString;
+  }
+
+  NS_ENSURE_STATE(JS_GetProperty(aCx, obj, "mDuration", &value));
+  if (JSVAL_IS_INT(value)) {
+    NS_ENSURE_STATE(JS_ValueToInt64(aCx, value, &mDuration));
+  }
+
+  NS_ENSURE_STATE(JS_GetProperty(aCx, obj, "mMediaNumber", &value));
+  if (JSVAL_IS_INT(value)) {
+    NS_ENSURE_STATE(JS_ValueToInt64(aCx, value, &mMediaNumber));
+  }
+
+  NS_ENSURE_STATE(JS_GetProperty(aCx, obj, "mTitle", &value));
+  if (JSVAL_IS_STRING(value)) {
+    nsDependentJSString jsString;
+    NS_ENSURE_STATE(JSVAL_IS_STRING(value));
+    NS_ENSURE_STATE(jsString.init(aCx, value.toString()));
+    mTitle = jsString;
+  }
+
+  NS_ENSURE_STATE(JS_GetProperty(aCx, obj, "mTotalMediaCount", &value));
+  if (JSVAL_IS_INT(value)) {
+    NS_ENSURE_STATE(JS_ValueToInt64(aCx, value, &mTotalMediaCount));
+  }
+
+  return NS_OK;
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/MediaMetaData.h
@@ -0,0 +1,32 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_bluetooth_mediametadata_h__
+#define mozilla_dom_bluetooth_mediametadata_h__
+
+#include "jsapi.h"
+#include "nsString.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class MediaMetaData
+{
+public:
+  MediaMetaData();
+
+  nsresult Init(JSContext* aCx, const jsval* aVal);
+
+  nsString mAlbum;
+  nsString mArtist;
+  int64_t mDuration;
+  int64_t mMediaNumber;
+  nsString mTitle;
+  int64_t mTotalMediaCount;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/MediaPlayStatus.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/ */
+
+#include "BluetoothCommon.h"
+#include "MediaPlayStatus.h"
+
+#include "nsContentUtils.h"
+#include "nsCxPusher.h"
+#include "nsJSUtils.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+USING_BLUETOOTH_NAMESPACE
+
+MediaPlayStatus::MediaPlayStatus() : mDuration(-1)
+                                     , mPosition(-1)
+{
+}
+
+nsresult
+MediaPlayStatus::Init(JSContext* aCx, const jsval* aVal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!aCx || !aVal) {
+    return NS_OK;
+  }
+
+  if (!aVal->isObject()) {
+    return aVal->isNullOrUndefined() ? NS_OK : NS_ERROR_TYPE_ERR;
+  }
+
+  JS::RootedObject obj(aCx, &aVal->toObject());
+  nsCxPusher pusher;
+  pusher.Push(aCx);
+  JSAutoCompartment ac(aCx, obj);
+
+  JS::Value value;
+  NS_ENSURE_STATE(JS_GetProperty(aCx, obj, "mDuration", &value));
+  if (JSVAL_IS_INT(value)) {
+    NS_ENSURE_STATE(JS_ValueToInt64(aCx, value, &mDuration));
+  }
+
+  NS_ENSURE_STATE(JS_GetProperty(aCx, obj, "mPlayStatus", &value));
+  if (JSVAL_IS_STRING(value)) {
+    nsDependentJSString jsString;
+    NS_ENSURE_STATE(JSVAL_IS_STRING(value));
+    NS_ENSURE_STATE(jsString.init(aCx, value.toString()));
+    mPlayStatus = jsString;
+  }
+
+  NS_ENSURE_STATE(JS_GetProperty(aCx, obj, "mPosition", &value));
+  if (JSVAL_IS_INT(value)) {
+    NS_ENSURE_STATE(JS_ValueToInt64(aCx, value, &mPosition));
+  }
+
+  return NS_OK;
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/MediaPlayStatus.h
@@ -0,0 +1,29 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_bluetooth_mediaplaystatus_h__
+#define mozilla_dom_bluetooth_mediaplaystatus_h__
+
+#include "jsapi.h"
+#include "nsString.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class MediaPlayStatus
+{
+public:
+  MediaPlayStatus();
+
+  nsresult Init(JSContext* aCx, const jsval* aVal);
+
+  int64_t mDuration;
+  nsString mPlayStatus;
+  int64_t mPosition;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif
--- a/dom/bluetooth/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth/ipc/BluetoothParent.cpp
@@ -225,16 +225,20 @@ BluetoothParent::RecvPBluetoothRequestCo
     case Request::TDenyReceivingFileRequest:
       return actor->DoRequest(aRequest.get_DenyReceivingFileRequest());
     case Request::TConnectScoRequest:
       return actor->DoRequest(aRequest.get_ConnectScoRequest());
     case Request::TDisconnectScoRequest:
       return actor->DoRequest(aRequest.get_DisconnectScoRequest());
     case Request::TIsScoConnectedRequest:
       return actor->DoRequest(aRequest.get_IsScoConnectedRequest());
+    case Request::TSendMetaDataRequest:
+      return actor->DoRequest(aRequest.get_SendMetaDataRequest());
+    case Request::TSendPlayStatusRequest:
+      return actor->DoRequest(aRequest.get_SendPlayStatusRequest());
     default:
       MOZ_CRASH("Unknown type!");
   }
 
   MOZ_CRASH("Should never get here!");
 }
 
 PBluetoothRequestParent*
@@ -598,8 +602,37 @@ bool
 BluetoothRequestParent::DoRequest(const IsScoConnectedRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TIsScoConnectedRequest);
 
   mService->IsScoConnected(mReplyRunnable.get());
   return true;
 }
+
+bool
+BluetoothRequestParent::DoRequest(const SendMetaDataRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TSendMetaDataRequest);
+
+  mService->SendMetaData(aRequest.title(),
+                         aRequest.artist(),
+                         aRequest.album(),
+                         aRequest.mediaNumber(),
+                         aRequest.totalMediaCount(),
+                         aRequest.duration(),
+                         mReplyRunnable.get());
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(const SendPlayStatusRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TSendPlayStatusRequest);
+
+  mService->SendPlayStatus(aRequest.duration(),
+                           aRequest.position(),
+                           aRequest.playStatus(),
+                           mReplyRunnable.get());
+  return true;
+}
--- a/dom/bluetooth/ipc/BluetoothParent.h
+++ b/dom/bluetooth/ipc/BluetoothParent.h
@@ -189,13 +189,19 @@ protected:
   bool
   DoRequest(const ConnectScoRequest& aRequest);
 
   bool
   DoRequest(const DisconnectScoRequest& aRequest);
 
   bool
   DoRequest(const IsScoConnectedRequest& aRequest);
+
+  bool
+  DoRequest(const SendMetaDataRequest& aRequest);
+
+  bool
+  DoRequest(const SendPlayStatusRequest& aRequest);
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_ipc_bluetoothparent_h__
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
@@ -334,16 +334,42 @@ BluetoothServiceChildProcess::Disconnect
 }
 
 void
 BluetoothServiceChildProcess::IsScoConnected(BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, IsScoConnectedRequest());
 }
 
+void
+BluetoothServiceChildProcess::SendMetaData(const nsAString& aTitle,
+                                           const nsAString& aArtist,
+                                           const nsAString& aAlbum,
+                                           int64_t aMediaNumber,
+                                           int64_t aTotalMediaCount,
+                                           int64_t aDuration,
+                                           BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+              SendMetaDataRequest(nsString(aTitle), nsString(aArtist),
+                                  nsString(aAlbum), aMediaNumber,
+                                  aTotalMediaCount, aDuration));
+}
+
+void
+BluetoothServiceChildProcess::SendPlayStatus(int64_t aDuration,
+                                             int64_t aPosition,
+                                             const nsAString& aPlayStatus,
+                                             BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+              SendPlayStatusRequest(aDuration, aPosition,
+                                    nsString(aPlayStatus)));
+}
+
 nsresult
 BluetoothServiceChildProcess::HandleStartup()
 {
   // Don't need to do anything here for startup since our Create function takes
   // care of the actor machinery.
   return NS_OK;
 }
 
@@ -383,8 +409,17 @@ BluetoothServiceChildProcess::IsConnecte
 }
 
 nsresult
 BluetoothServiceChildProcess::SendSinkMessage(const nsAString& aDeviceAddresses,
                                               const nsAString& aMessage)
 {
   MOZ_CRASH("This should never be called!");
 }
+
+void
+BluetoothServiceChildProcess::UpdatePlayStatus(uint32_t aDuration,
+                                               uint32_t aPosition,
+                                               ControlPlayStatus aPlayStatus)
+{
+  MOZ_CRASH("This should never be called!");
+}
+
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
@@ -147,16 +147,36 @@ public:
   ConnectSco(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual void
   DisconnectSco(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual void
   IsScoConnected(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
+  virtual void
+  SendMetaData(const nsAString& aTitle,
+               const nsAString& aArtist,
+               const nsAString& aAlbum,
+               int64_t aMediaNumber,
+               int64_t aTotalMediaCount,
+               int64_t aDuration,
+               BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual void
+  SendPlayStatus(int64_t aDuration,
+                 int64_t aPosition,
+                 const nsAString& aPlayStatus,
+                 BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual void
+  UpdatePlayStatus(uint32_t aDuration,
+                   uint32_t aPosition,
+                   ControlPlayStatus aPlayStatus) MOZ_OVERRIDE;
+
   virtual nsresult
   SendSinkMessage(const nsAString& aDeviceAddresses,
                   const nsAString& aMessage) MOZ_OVERRIDE;
 
 protected:
   BluetoothServiceChildProcess();
   virtual ~BluetoothServiceChildProcess();
 
--- a/dom/bluetooth/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth/ipc/PBluetooth.ipdl
@@ -136,16 +136,33 @@ struct ConnectScoRequest
 struct DisconnectScoRequest
 {
 };
 
 struct IsScoConnectedRequest
 {
 };
 
+struct SendMetaDataRequest
+{
+  nsString title;
+  nsString artist;
+  nsString album;
+  int64_t mediaNumber;
+  int64_t totalMediaCount;
+  int64_t duration;
+};
+
+struct SendPlayStatusRequest
+{
+  int64_t duration;
+  int64_t position;
+  nsString playStatus;
+};
+
 union Request
 {
   DefaultAdapterPathRequest;
   SetPropertyRequest;
   GetPropertyRequest;
   StartDiscoveryRequest;
   StopDiscoveryRequest;
   PairRequest;
@@ -162,16 +179,18 @@ union Request
   DisconnectRequest;
   SendFileRequest;
   StopSendingFileRequest;
   ConfirmReceivingFileRequest;
   DenyReceivingFileRequest;
   ConnectScoRequest;
   DisconnectScoRequest;
   IsScoConnectedRequest;
+  SendMetaDataRequest;
+  SendPlayStatusRequest;
 };
 
 protocol PBluetooth
 {
   manager PContent;
   manages PBluetoothRequest;
 
   /**
--- a/dom/bluetooth/linux/BluetoothDBusService.cpp
+++ b/dom/bluetooth/linux/BluetoothDBusService.cpp
@@ -58,24 +58,27 @@
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 USING_BLUETOOTH_NAMESPACE
 
 #define B2G_AGENT_CAPABILITIES "DisplayYesNo"
 #define DBUS_MANAGER_IFACE BLUEZ_DBUS_BASE_IFC  ".Manager"
 #define DBUS_ADAPTER_IFACE BLUEZ_DBUS_BASE_IFC  ".Adapter"
-#define DBUS_DEVICE_IFACE BLUEZ_DBUS_BASE_IFC   ".Device"
-#define DBUS_AGENT_IFACE BLUEZ_DBUS_BASE_IFC    ".Agent"
-#define DBUS_SINK_IFACE BLUEZ_DBUS_BASE_IFC     ".AudioSink"
+#define DBUS_DEVICE_IFACE  BLUEZ_DBUS_BASE_IFC  ".Device"
+#define DBUS_AGENT_IFACE   BLUEZ_DBUS_BASE_IFC  ".Agent"
+#define DBUS_SINK_IFACE    BLUEZ_DBUS_BASE_IFC  ".AudioSink"
+#define DBUS_CTL_IFACE     BLUEZ_DBUS_BASE_IFC  ".Control"
 #define BLUEZ_DBUS_BASE_PATH      "/org/bluez"
 #define BLUEZ_DBUS_BASE_IFC       "org.bluez"
 #define BLUEZ_ERROR_IFC           "org.bluez.Error"
 
-#define PROP_DEVICE_CONNECTED_TYPE "org.bluez.device.conn.type"
+#define ERR_A2DP_IS_DISCONNECTED      "A2dpIsDisconnected"
+#define ERR_AVRCP_IS_DISCONNECTED     "AvrcpIsDisconnected"
+#define ERR_UNKNOWN_PROFILE           "UnknownProfileError"
 
 typedef struct {
   const char* name;
   int type;
 } Properties;
 
 static Properties sDeviceProperties[] = {
   {"Address", DBUS_TYPE_STRING},
@@ -118,16 +121,20 @@ static Properties sManagerProperties[] =
 };
 
 static Properties sSinkProperties[] = {
   {"State", DBUS_TYPE_STRING},
   {"Connected", DBUS_TYPE_BOOLEAN},
   {"Playing", DBUS_TYPE_BOOLEAN}
 };
 
+static Properties sControlProperties[] = {
+  {"Connected", DBUS_TYPE_BOOLEAN}
+};
+
 static const char* sBluetoothDBusIfaces[] =
 {
   DBUS_MANAGER_IFACE,
   DBUS_ADAPTER_IFACE,
   DBUS_DEVICE_IFACE
 };
 
 static const char* sBluetoothDBusSignals[] =
@@ -135,17 +142,18 @@ static const char* sBluetoothDBusSignals
   "type='signal',interface='org.freedesktop.DBus'",
   "type='signal',interface='org.bluez.Adapter'",
   "type='signal',interface='org.bluez.Manager'",
   "type='signal',interface='org.bluez.Device'",
   "type='signal',interface='org.bluez.Input'",
   "type='signal',interface='org.bluez.Network'",
   "type='signal',interface='org.bluez.NetworkServer'",
   "type='signal',interface='org.bluez.HealthDevice'",
-  "type='signal',interface='org.bluez.AudioSink'"
+  "type='signal',interface='org.bluez.AudioSink'",
+  "type='signal',interface='org.bluez.Control'"
 };
 
 /**
  * DBus Connection held for the BluetoothCommandThread to use. Should never be
  * used by any other thread.
  *
  */
 static nsRefPtr<RawDBusConnection> gThreadConnection;
@@ -204,30 +212,63 @@ public:
 
     return NS_OK;
   }
 
 private:
   BluetoothSignal mSignal;
 };
 
+class ControlPropertyChangedHandler : public nsRunnable
+{
+public:
+  ControlPropertyChangedHandler(const BluetoothSignal& aSignal)
+    : mSignal(aSignal)
+  {
+  }
+
+  nsresult Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (mSignal.value().type() != BluetoothValue::TArrayOfBluetoothNamedValue) {
+       BT_WARNING("Wrong value type for ControlPropertyChangedHandler");
+       return NS_ERROR_FAILURE;
+    }
+
+    InfallibleTArray<BluetoothNamedValue>& arr =
+      mSignal.value().get_ArrayOfBluetoothNamedValue();
+    MOZ_ASSERT(arr[0].name().EqualsLiteral("Connected"));
+    MOZ_ASSERT(arr[0].value().type() == BluetoothValue::Tbool);
+    bool connected = arr[0].value().get_bool();
+
+    BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get();
+    NS_ENSURE_TRUE(a2dp, NS_ERROR_FAILURE);
+    a2dp->SetAvrcpConnected(connected);
+    return NS_OK;
+  }
+
+private:
+  BluetoothSignal mSignal;
+};
+
 class SinkPropertyChangedHandler : public nsRunnable
 {
 public:
   SinkPropertyChangedHandler(const BluetoothSignal& aSignal)
     : mSignal(aSignal)
   {
   }
 
   NS_IMETHOD
   Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mSignal.name().EqualsLiteral("PropertyChanged"));
-    MOZ_ASSERT(mSignal.value().type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+    MOZ_ASSERT(mSignal.value().type() ==
+               BluetoothValue::TArrayOfBluetoothNamedValue);
 
     // Replace object path with device address
     nsString address = GetAddressFromObjectPath(mSignal.path());
     mSignal.path() = address;
 
     BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get();
     NS_ENSURE_TRUE(a2dp, NS_ERROR_FAILURE);
     a2dp->HandleSinkPropertyChanged(mSignal);
@@ -397,16 +438,21 @@ public:
     }
 
     BluetoothOppManager* opp = BluetoothOppManager::Get();
     if (!opp || !opp->Listen()) {
       NS_WARNING("Failed to start listening for BluetoothOppManager!");
       return NS_ERROR_FAILURE;
     }
 
+    BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get();
+    NS_ENSURE_TRUE(a2dp, NS_ERROR_FAILURE);
+    a2dp->ResetA2dp();
+    a2dp->ResetAvrcp();
+
     return NS_OK;
   }
 };
 
 static void
 RunDBusCallback(DBusMessage* aMsg, void* aBluetoothReplyRunnable,
                 UnpackFunc aFunc)
 {
@@ -466,44 +512,42 @@ static void
 GetIntCallback(DBusMessage* aMsg, void* aBluetoothReplyRunnable)
 {
   RunDBusCallback(aMsg, aBluetoothReplyRunnable,
                   UnpackIntMessage);
 }
 
 #ifdef DEBUG
 static void
-CheckForSinkError(bool aConnect, DBusMessage* aMsg, void *aParam)
+CheckForError(DBusMessage* aMsg, void *aParam, const nsAString& aError)
 {
   BluetoothValue v;
   nsAutoString replyError;
   UnpackVoidMessage(aMsg, nullptr, v, replyError);
   if (!v.get_bool()) {
-    if (aConnect) {
-      BT_WARNING("Failed to connect sink.");
-      return;
-    }
-    BT_WARNING("Failed to disconnect sink.");
+    BT_WARNING(NS_ConvertUTF16toUTF8(aError).get());
   }
 }
 #endif
 
 static void
 SinkConnectCallback(DBusMessage* aMsg, void* aParam)
 {
 #ifdef DEBUG
-  CheckForSinkError(true, aMsg, aParam);
+  NS_NAMED_LITERAL_STRING(errorStr, "Failed to connect sink");
+  CheckForError(aMsg, aParam, errorStr);
 #endif
 }
 
 static void
 SinkDisconnectCallback(DBusMessage* aMsg, void* aParam)
 {
 #ifdef DEBUG
-  CheckForSinkError(false, aMsg, aParam);
+  NS_NAMED_LITERAL_STRING(errorStr, "Failed to disconnect sink");
+  CheckForError(false, aMsg, errorStr);
 #endif
 }
 
 static bool
 HasAudioService(uint32_t aCodValue)
 {
   return ((aCodValue & 0x200000) == 0x200000);
 }
@@ -1253,16 +1297,41 @@ public:
     bs->DispatchToCommandThread(func);
     return NS_OK;
   }
 
 private:
   nsString mPath;
 };
 
+class SendPlayStatusTask : public nsRunnable
+{
+public:
+  SendPlayStatusTask()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+  }
+
+  nsresult Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get();
+    NS_ENSURE_TRUE(a2dp, NS_ERROR_FAILURE);
+
+    BluetoothService* bs = BluetoothService::Get();
+    NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
+
+    bs->UpdatePlayStatus(a2dp->GetDuration(),
+                         a2dp->GetPosition(),
+                         a2dp->GetPlayStatus());
+    return NS_OK;
+  }
+};
+
 // Called by dbus during WaitForAndDispatchEventNative()
 // This function is called on the IOThread
 static DBusHandlerResult
 EventFilter(DBusConnection* aConn, DBusMessage* aMsg, void* aData)
 {
   MOZ_ASSERT(!NS_IsMainThread(), "Shouldn't be called from Main Thread!");
 
   if (dbus_message_get_type(aMsg) != DBUS_MESSAGE_TYPE_SIGNAL) {
@@ -1438,30 +1507,41 @@ EventFilter(DBusConnection* aConn, DBusM
                         ArrayLength(sManagerProperties));
   } else if (dbus_message_is_signal(aMsg, DBUS_SINK_IFACE,
                                     "PropertyChanged")) {
     ParsePropertyChange(aMsg,
                         v,
                         errorStr,
                         sSinkProperties,
                         ArrayLength(sSinkProperties));
+  } else if (dbus_message_is_signal(aMsg, DBUS_CTL_IFACE, "GetPlayStatus")) {
+    NS_DispatchToMainThread(new SendPlayStatusTask());
+    return DBUS_HANDLER_RESULT_HANDLED;
+  } else if (dbus_message_is_signal(aMsg, DBUS_CTL_IFACE, "PropertyChanged")) {
+    ParsePropertyChange(aMsg,
+                        v,
+                        errorStr,
+                        sControlProperties,
+                        ArrayLength(sControlProperties));
   } else {
     errorStr = NS_ConvertUTF8toUTF16(dbus_message_get_member(aMsg));
     errorStr.AppendLiteral(" Signal not handled!");
   }
 
   if (!errorStr.IsEmpty()) {
     NS_WARNING(NS_ConvertUTF16toUTF8(errorStr).get());
     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
   }
 
   BluetoothSignal signal(signalName, signalPath, v);
   nsRefPtr<nsRunnable> task;
   if (signalInterface.EqualsLiteral(DBUS_SINK_IFACE)) {
     task = new SinkPropertyChangedHandler(signal);
+  } else if (signalInterface.EqualsLiteral(DBUS_CTL_IFACE)) {
+    task = new ControlPropertyChangedHandler(signal);
   } else {
     task = new DistributeBluetoothSignalTask(signal);
   }
 
   NS_DispatchToMainThread(task);
 
   return DBUS_HANDLER_RESULT_HANDLED;
 }
@@ -1896,47 +1976,40 @@ BluetoothDBusService::GetConnectedDevice
   BluetoothValue values = InfallibleTArray<BluetoothNamedValue>();
   if (!IsReady()) {
     NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
     DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
     return NS_OK;
   }
 
   nsTArray<nsString> deviceAddresses;
+  BluetoothProfileManagerBase* profile;
   if (aProfileId == BluetoothServiceClass::HANDSFREE ||
       aProfileId == BluetoothServiceClass::HEADSET) {
-    BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
-    if (hfp->IsConnected()) {
-      nsString address;
-      hfp->GetAddress(address);
-      deviceAddresses.AppendElement(address);
-    }
+    profile = BluetoothHfpManager::Get();
   } else if (aProfileId == BluetoothServiceClass::OBJECT_PUSH) {
-    BluetoothOppManager* opp = BluetoothOppManager::Get();
-    if (opp->IsTransferring()) {
-      nsString address;
-      opp->GetAddress(address);
-      deviceAddresses.AppendElement(address);
-    }
+    profile = BluetoothOppManager::Get();
   } else {
-    errorStr.AssignLiteral("Unknown profile");
-    DispatchBluetoothReply(aRunnable, values, errorStr);
+    DispatchBluetoothReply(aRunnable, values,
+                           NS_LITERAL_STRING(ERR_UNKNOWN_PROFILE));
     return NS_OK;
   }
 
+  if (profile->IsConnected()) {
+    nsString address;
+    profile->GetAddress(address);
+    deviceAddresses.AppendElement(address);
+  }
+
   nsRefPtr<BluetoothReplyRunnable> runnable = aRunnable;
   nsRefPtr<nsRunnable> func(
     new BluetoothArrayOfDevicePropertiesRunnable(deviceAddresses,
                                                  runnable,
                                                  GetConnectedDevicesFilter));
-
-  if (NS_FAILED(mBluetoothCommandThread->Dispatch(func, NS_DISPATCH_NORMAL))) {
-    NS_WARNING("Cannot dispatch task!");
-    return NS_ERROR_FAILURE;
-  }
+  mBluetoothCommandThread->Dispatch(func, NS_DISPATCH_NORMAL);
 
   runnable.forget();
   return NS_OK;
 }
 
 nsresult
 BluetoothDBusService::GetPairedDevicePropertiesInternal(
                                      const nsTArray<nsString>& aDeviceAddresses,
@@ -2379,17 +2452,17 @@ BluetoothDBusService::Connect(const nsAS
   } else if (aProfileId == BluetoothServiceClass::HEADSET) {
     BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
     hfp->Connect(aDeviceAddress, false, aRunnable);
   } else if (aProfileId == BluetoothServiceClass::OBJECT_PUSH) {
     BluetoothOppManager* opp = BluetoothOppManager::Get();
     opp->Connect(aDeviceAddress, aRunnable);
   } else {
     DispatchBluetoothReply(aRunnable, BluetoothValue(),
-                           NS_LITERAL_STRING("UnknownProfileError"));
+                           NS_LITERAL_STRING(ERR_UNKNOWN_PROFILE));
   }
 }
 
 void
 BluetoothDBusService::Disconnect(const uint16_t aProfileId,
                                  BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -2397,41 +2470,44 @@ BluetoothDBusService::Disconnect(const u
   if (aProfileId == BluetoothServiceClass::HANDSFREE ||
       aProfileId == BluetoothServiceClass::HEADSET) {
     BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
     hfp->Disconnect();
   } else if (aProfileId == BluetoothServiceClass::OBJECT_PUSH) {
     BluetoothOppManager* opp = BluetoothOppManager::Get();
     opp->Disconnect();
   } else {
-    NS_WARNING("Unknown profile");
+    BT_WARNING(ERR_UNKNOWN_PROFILE);
     return;
   }
 
   // Currently, just fire success because Disconnect() doesn't fail,
   // but we still make aRunnable pass into this function for future
   // once Disconnect will fail.
   DispatchBluetoothReply(aRunnable, BluetoothValue(true), EmptyString());
 }
 
 bool
 BluetoothDBusService::IsConnected(const uint16_t aProfileId)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  BluetoothProfileManagerBase* profile;
   if (aProfileId == BluetoothServiceClass::HANDSFREE ||
       aProfileId == BluetoothServiceClass::HEADSET) {
-    BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
-    return (hfp->IsConnected());
+    profile = BluetoothHfpManager::Get();
   } else if (aProfileId == BluetoothServiceClass::OBJECT_PUSH) {
-    BluetoothOppManager* opp = BluetoothOppManager::Get();
-    return opp->IsTransferring();
+    profile = BluetoothOppManager::Get();
+  } else {
+    NS_WARNING(ERR_UNKNOWN_PROFILE);
+    return false;
   }
 
-  return false;
+  NS_ENSURE_TRUE(profile, false);
+  return profile->IsConnected();
 }
 
 class ConnectBluetoothSocketRunnable : public nsRunnable
 {
 public:
   ConnectBluetoothSocketRunnable(BluetoothReplyRunnable* aRunnable,
                                  UnixSocketConsumer* aConsumer,
                                  const nsAString& aObjectPath,
@@ -2797,8 +2873,277 @@ BluetoothDBusService::IsScoConnected(Blu
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothHfpManager* hfp = BluetoothHfpManager::Get();
   NS_ENSURE_TRUE_VOID(hfp);
   DispatchBluetoothReply(aRunnable,
                          hfp->IsScoConnected(), EmptyString());
 }
 
+void
+BluetoothDBusService::SendMetaData(const nsAString& aTitle,
+                                   const nsAString& aArtist,
+                                   const nsAString& aAlbum,
+                                   int64_t aMediaNumber,
+                                   int64_t aTotalMediaCount,
+                                   int64_t aDuration,
+                                   BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!IsReady()) {
+    NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
+    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
+    return;
+  }
+
+  BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get();
+  NS_ENSURE_TRUE_VOID(a2dp);
+
+  if (!a2dp->IsConnected()) {
+    DispatchBluetoothReply(aRunnable, BluetoothValue(),
+                           NS_LITERAL_STRING(ERR_A2DP_IS_DISCONNECTED));
+    return;
+  } else if (!a2dp->IsAvrcpConnected()) {
+    DispatchBluetoothReply(aRunnable, BluetoothValue(),
+                           NS_LITERAL_STRING(ERR_AVRCP_IS_DISCONNECTED));
+    return;
+  }
+
+  nsAutoString address;
+  a2dp->GetAddress(address);
+  nsString objectPath =
+    GetObjectPathFromAddress(sAdapterPath, address);
+
+  nsCString tempTitle = NS_ConvertUTF16toUTF8(aTitle);
+  nsCString tempArtist = NS_ConvertUTF16toUTF8(aArtist);
+  nsCString tempAlbum = NS_ConvertUTF16toUTF8(aAlbum);
+
+  nsCString tempMediaNumber = EmptyCString();
+  nsCString tempTotalMediaCount = EmptyCString();
+  nsCString tempDuration = EmptyCString();
+  if (aMediaNumber >= 0) {
+    tempMediaNumber.AppendInt(aMediaNumber);
+  }
+  if (aTotalMediaCount >= 0) {
+    tempTotalMediaCount.AppendInt(aTotalMediaCount);
+  }
+  if (aDuration >= 0) {
+    tempDuration.AppendInt(aDuration);
+  }
+
+  const char* title = tempTitle.get();
+  const char* album = tempAlbum.get();
+  const char* artist = tempArtist.get();
+  const char* mediaNumber = tempMediaNumber.get();
+  const char* totalMediaCount = tempTotalMediaCount.get();
+  const char* duration = tempDuration.get();
+
+  nsRefPtr<BluetoothReplyRunnable> runnable(aRunnable);
+
+  bool ret = dbus_func_args_async(mConnection,
+                                  -1,
+                                  GetVoidCallback,
+                                  (void*)runnable.get(),
+                                  NS_ConvertUTF16toUTF8(objectPath).get(),
+                                  DBUS_CTL_IFACE,
+                                  "UpdateMetaData",
+                                  DBUS_TYPE_STRING, &title,
+                                  DBUS_TYPE_STRING, &artist,
+                                  DBUS_TYPE_STRING, &album,
+                                  DBUS_TYPE_STRING, &mediaNumber,
+                                  DBUS_TYPE_STRING, &totalMediaCount,
+                                  DBUS_TYPE_STRING, &duration,
+                                  DBUS_TYPE_INVALID);
+  NS_ENSURE_TRUE_VOID(ret);
+
+  runnable.forget();
+
+  nsAutoString prevTitle, prevAlbum;
+  a2dp->GetTitle(prevTitle);
+  a2dp->GetAlbum(prevAlbum);
+
+  if (aMediaNumber != a2dp->GetMediaNumber() ||
+      !aTitle.Equals(prevTitle) ||
+      !aAlbum.Equals(prevAlbum)) {
+    UpdateNotification(ControlEventId::EVENT_TRACK_CHANGED, aMediaNumber);
+  }
+
+  a2dp->UpdateMetaData(aTitle, aArtist, aAlbum,
+                       aMediaNumber, aTotalMediaCount, aDuration);
+}
+
+static ControlPlayStatus
+PlayStatusStringToControlPlayStatus(const nsAString& aPlayStatus)
+{
+  ControlPlayStatus playStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN;
+  if (aPlayStatus.EqualsLiteral("STOPPED")) {
+    playStatus = ControlPlayStatus::PLAYSTATUS_STOPPED;
+  } else if (aPlayStatus.EqualsLiteral("PLAYING")) {
+    playStatus = ControlPlayStatus::PLAYSTATUS_PLAYING;
+  } else if (aPlayStatus.EqualsLiteral("PAUSED")) {
+    playStatus = ControlPlayStatus::PLAYSTATUS_PAUSED;
+  } else if (aPlayStatus.EqualsLiteral("FWD_SEEK")) {
+    playStatus = ControlPlayStatus::PLAYSTATUS_FWD_SEEK;
+  } else if (aPlayStatus.EqualsLiteral("REV_SEEK")) {
+    playStatus = ControlPlayStatus::PLAYSTATUS_REV_SEEK;
+  } else if (aPlayStatus.EqualsLiteral("ERROR")) {
+    playStatus = ControlPlayStatus::PLAYSTATUS_ERROR;
+  }
+
+  return playStatus;
+}
+
+void
+BluetoothDBusService::SendPlayStatus(int64_t aDuration,
+                                     int64_t aPosition,
+                                     const nsAString& aPlayStatus,
+                                     BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!IsReady()) {
+    NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
+    DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
+    return;
+  }
+
+  ControlPlayStatus playStatus =
+    PlayStatusStringToControlPlayStatus(aPlayStatus);
+  if (playStatus == ControlPlayStatus::PLAYSTATUS_UNKNOWN) {
+    DispatchBluetoothReply(aRunnable, BluetoothValue(),
+                           NS_LITERAL_STRING("Invalid play status"));
+    return;
+  } else if (aDuration < 0) {
+    DispatchBluetoothReply(aRunnable, BluetoothValue(),
+                           NS_LITERAL_STRING("Invalid duration"));
+    return;
+  } else if (aPosition < 0) {
+    DispatchBluetoothReply(aRunnable, BluetoothValue(),
+                           NS_LITERAL_STRING("Invalid position"));
+    return;
+  }
+
+  BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get();
+  NS_ENSURE_TRUE_VOID(a2dp);
+
+  if (!a2dp->IsConnected()) {
+    DispatchBluetoothReply(aRunnable, BluetoothValue(),
+                           NS_LITERAL_STRING(ERR_A2DP_IS_DISCONNECTED));
+    return;
+  } else if (!a2dp->IsAvrcpConnected()) {
+    DispatchBluetoothReply(aRunnable, BluetoothValue(),
+                           NS_LITERAL_STRING(ERR_AVRCP_IS_DISCONNECTED));
+    return;
+  }
+
+  nsAutoString address;
+  a2dp->GetAddress(address);
+  nsString objectPath =
+    GetObjectPathFromAddress(sAdapterPath, address);
+
+  nsRefPtr<BluetoothReplyRunnable> runnable(aRunnable);
+
+  uint32_t tempPlayStatus = playStatus;
+  bool ret = dbus_func_args_async(mConnection,
+                                  -1,
+                                  GetVoidCallback,
+                                  (void*)runnable.get(),
+                                  NS_ConvertUTF16toUTF8(objectPath).get(),
+                                  DBUS_CTL_IFACE,
+                                  "UpdatePlayStatus",
+                                  DBUS_TYPE_UINT32, &aDuration,
+                                  DBUS_TYPE_UINT32, &aPosition,
+                                  DBUS_TYPE_UINT32, &tempPlayStatus,
+                                  DBUS_TYPE_INVALID);
+  NS_ENSURE_TRUE_VOID(ret);
+
+  runnable.forget();
+
+  ControlEventId eventId = ControlEventId::EVENT_UNKNOWN;
+  uint64_t data;
+  if (aPosition != a2dp->GetPosition()) {
+    eventId = ControlEventId::EVENT_PLAYBACK_POS_CHANGED;
+    data = aPosition;
+  } else if (playStatus != a2dp->GetPlayStatus()) {
+    eventId = ControlEventId::EVENT_PLAYBACK_STATUS_CHANGED;
+    data = tempPlayStatus;
+  }
+
+  if (eventId != ControlEventId::EVENT_UNKNOWN) {
+    UpdateNotification(eventId, data);
+  }
+
+  a2dp->UpdatePlayStatus(aDuration, aPosition, playStatus);
+}
+
+static void
+ControlCallback(DBusMessage* aMsg, void* aParam)
+{
+#ifdef DEBUG
+  NS_NAMED_LITERAL_STRING(errorStr, "Failed to update playstatus");
+  CheckForError(aMsg, aParam, errorStr);
+#endif
+}
+
+void
+BluetoothDBusService::UpdatePlayStatus(uint32_t aDuration,
+                                       uint32_t aPosition,
+                                       ControlPlayStatus aPlayStatus)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE_VOID(this->IsReady());
+
+  BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get();
+  NS_ENSURE_TRUE_VOID(a2dp);
+  MOZ_ASSERT(a2dp->IsConnected());
+  MOZ_ASSERT(a2dp->IsAvrcpConnected());
+
+  nsAutoString address;
+  a2dp->GetAddress(address);
+  nsString objectPath =
+    GetObjectPathFromAddress(sAdapterPath, address);
+
+  uint32_t tempPlayStatus = aPlayStatus;
+  bool ret = dbus_func_args_async(mConnection,
+                                  -1,
+                                  ControlCallback,
+                                  nullptr,
+                                  NS_ConvertUTF16toUTF8(objectPath).get(),
+                                  DBUS_CTL_IFACE,
+                                  "UpdatePlayStatus",
+                                  DBUS_TYPE_UINT32, &aDuration,
+                                  DBUS_TYPE_UINT32, &aPosition,
+                                  DBUS_TYPE_UINT32, &tempPlayStatus,
+                                  DBUS_TYPE_INVALID);
+  NS_ENSURE_TRUE_VOID(ret);
+}
+
+void
+BluetoothDBusService::UpdateNotification(ControlEventId aEventId,
+                                         uint64_t aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE_VOID(this->IsReady());
+
+  BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get();
+  NS_ENSURE_TRUE_VOID(a2dp);
+  MOZ_ASSERT(a2dp->IsConnected());
+  MOZ_ASSERT(a2dp->IsAvrcpConnected());
+
+  nsAutoString address;
+  a2dp->GetAddress(address);
+  nsString objectPath =
+    GetObjectPathFromAddress(sAdapterPath, address);
+  uint16_t eventId = aEventId;
+
+  bool ret = dbus_func_args_async(mConnection,
+                                  -1,
+                                  ControlCallback,
+                                  nullptr,
+                                  NS_ConvertUTF16toUTF8(objectPath).get(),
+                                  DBUS_CTL_IFACE,
+                                  "UpdateNotification",
+                                  DBUS_TYPE_UINT16, &eventId,
+                                  DBUS_TYPE_UINT64, &aData,
+                                  DBUS_TYPE_INVALID);
+  NS_ENSURE_TRUE_VOID(ret);
+}
--- a/dom/bluetooth/linux/BluetoothDBusService.h
+++ b/dom/bluetooth/linux/BluetoothDBusService.h
@@ -133,29 +133,71 @@ public:
   ConnectSco(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual void
   DisconnectSco(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
   virtual void
   IsScoConnected(BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
 
+  virtual void
+  SendMetaData(const nsAString& aTitle,
+               const nsAString& aArtist,
+               const nsAString& aAlbum,
+               int64_t aMediaNumber,
+               int64_t aTotalMediaCount,
+               int64_t aDuration,
+               BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual void
+  SendPlayStatus(int64_t aDuration,
+                 int64_t aPosition,
+                 const nsAString& aPlayStatus,
+                 BluetoothReplyRunnable* aRunnable) MOZ_OVERRIDE;
+
+  virtual void
+  UpdatePlayStatus(uint32_t aDuration,
+                   uint32_t aPosition,
+                   ControlPlayStatus aPlayStatus) MOZ_OVERRIDE;
+
   virtual nsresult
   SendSinkMessage(const nsAString& aDeviceAddresses,
                   const nsAString& aMessage) MOZ_OVERRIDE;
 
 private:
+  /**
+   * For DBus Control method of "UpdateNotification", event id should be
+   * specified as following:
+   * (Please see specification of AVRCP 1.3, Table 5.28 for more details.)
+   */
+  enum ControlEventId {
+    EVENT_PLAYBACK_STATUS_CHANGED            = 0x01,
+    EVENT_TRACK_CHANGED                      = 0x02,
+    EVENT_TRACK_REACHED_END                  = 0x03,
+    EVENT_TRACK_REACHED_START                = 0x04,
+    EVENT_PLAYBACK_POS_CHANGED               = 0x05,
+    EVENT_BATT_STATUS_CHANGED                = 0x06,
+    EVENT_SYSTEM_STATUS_CHANGED              = 0x07,
+    EVENT_PLAYER_APPLICATION_SETTING_CHANGED = 0x08,
+    EVENT_UNKNOWN
+  };
+
   nsresult SendGetPropertyMessage(const nsAString& aPath,
                                   const char* aInterface,
                                   void (*aCB)(DBusMessage *, void *),
                                   BluetoothReplyRunnable* aRunnable);
+
   nsresult SendDiscoveryMessage(const char* aMessageName,
                                 BluetoothReplyRunnable* aRunnable);
+
   nsresult SendSetPropertyMessage(const char* aInterface,
                                   const BluetoothNamedValue& aValue,
                                   BluetoothReplyRunnable* aRunnable);
 
+  void UpdateNotification(ControlEventId aEventId, uint64_t aData);
+
   void DisconnectAllAcls(const nsAString& aAdapterPath);
+
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/moz.build
+++ b/dom/bluetooth/moz.build
@@ -37,16 +37,18 @@ if CONFIG['MOZ_B2G_BT']:
         'BluetoothServiceChildProcess.cpp',
         'BluetoothUnixSocketConnector.cpp',
         'BluetoothA2dpManager.cpp',
         'BluetoothHfpManager.cpp',
         'BluetoothOppManager.cpp',
         'ObexBase.cpp',
         'BluetoothUuid.cpp',
         'BluetoothSocket.cpp',
+        'MediaMetaData.cpp',
+        'MediaPlayStatus.cpp'
     ]
 
     if CONFIG['MOZ_B2G_RIL']:
         CPP_SOURCES += [
             'BluetoothTelephonyListener.cpp',
         ]
 
     if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
--- a/dom/bluetooth/nsIDOMBluetoothAdapter.idl
+++ b/dom/bluetooth/nsIDOMBluetoothAdapter.idl
@@ -1,21 +1,54 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMEventTarget.idl"
 
+/**
+ * MediaMetadata and MediaPlayStatus are used to keep data from Applications.
+ * Please see specification of AVRCP 1.3 for more details.
+ *
+ * @title:           track title
+ * @artist:          artist name
+ * @album:           album name
+ * @mediaNumber:     track number
+ * @totalMediaCount: number of tracks in the album
+ * @duration:        playing time (ms)
+ */
+dictionary MediaMetaData
+{
+  DOMString  title;
+  DOMString  artist;
+  DOMString  album;
+  unsigned long  mediaNumber;
+  unsigned long  totalMediaCount;
+  unsigned long  duration;
+};
+
+/**
+ * @duration:   current track length (ms)
+ * @position:   playing time (ms)
+ * @playStatus: STOPPED/PLAYING/PAUSED/FWD_SEEK/REV_SEEK/ERROR
+ */
+dictionary MediaPlayStatus
+{
+  unsigned long  duration;
+  unsigned long  position;
+  DOMString  playStatus;
+};
+
 interface nsIDOMDOMRequest;
 interface nsIDOMBlob;
 interface nsIDOMBluetoothDevice;
 
-[scriptable, builtinclass, uuid(7058d214-3575-4913-99ad-0980296f617a)]
+[scriptable, builtinclass, uuid(1a0c6c90-23e3-4f4c-8076-98e4341c2024)]
 interface nsIDOMBluetoothAdapter : nsIDOMEventTarget
 {
   readonly attribute DOMString address;
   [binaryname(AdapterClass)] readonly attribute unsigned long class;
   readonly attribute bool discovering;
 
   [implicit_jscontext]
   readonly attribute jsval devices;
@@ -53,16 +86,20 @@ interface nsIDOMBluetoothAdapter : nsIDO
   nsIDOMDOMRequest connect(in DOMString aDeviceAddress, in unsigned short aProfile);
   nsIDOMDOMRequest disconnect(in unsigned short aProfile);
 
   // One device can only send one file at a time
   nsIDOMDOMRequest sendFile(in DOMString aDeviceAddress, in nsIDOMBlob aBlob);
   nsIDOMDOMRequest stopSendingFile(in DOMString aDeviceAddress);
   nsIDOMDOMRequest confirmReceivingFile(in DOMString aDeviceAddress, in bool aConfirmation);
 
+  // AVRCP 1.3 methods
+  nsIDOMDOMRequest sendMediaMetaData(in jsval aOptions);
+  nsIDOMDOMRequest sendMediaPlayStatus(in jsval aOptions);
+
   // Connect/Disconnect SCO (audio) connection
   nsIDOMDOMRequest connectSco();
   nsIDOMDOMRequest disconnectSco();
   nsIDOMDOMRequest isScoConnected();
 
   // Fired when discoverying and any device is discovered.
   [implicit_jscontext] attribute jsval ondevicefound;
 };
--- a/dom/contacts/fallback/ContactDB.jsm
+++ b/dom/contacts/fallback/ContactDB.jsm
@@ -13,17 +13,17 @@ const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
 
 const DB_NAME = "contacts";
-const DB_VERSION = 13;
+const DB_VERSION = 14;
 const STORE_NAME = "contacts";
 const SAVED_GETALL_STORE_NAME = "getallcache";
 const CHUNK_SIZE = 20;
 const REVISION_STORE = "revision";
 const REVISION_KEY = "revision";
 
 function exportContact(aRecord) {
   let contact = {};
@@ -487,17 +487,47 @@ ContactDB.prototype = {
               cursor.continue();
             } else {
               next();
             }
           }.bind(this);
         } else {
           next();
         }
-      }
+      },
+      function upgrade13to14() {
+        if (DEBUG) debug("Cleaning up empty substring entries in telMatch index");
+        if (!objectStore) {
+          objectStore = aTransaction.objectStore(STORE_NAME);
+        }
+        objectStore.openCursor().onsuccess = function(event) {
+          function removeEmptyStrings(value) {
+            if (value) {
+              const oldLength = value.length;
+              for (let i = 0; i < value.length; ++i) {
+                if (!value[i] || value[i] == "null") {
+                  value.splice(i, 1);
+                }
+              }
+              return oldLength !== value.length;
+            }
+          }
+
+          let cursor = event.target.result;
+          if (cursor) {
+            let modified = removeEmptyStrings(cursor.value.search.parsedTel);
+            let modified2 = removeEmptyStrings(cursor.value.search.tel);
+            if (modified || modified2) {
+              cursor.update(cursor.value);
+            }
+          } else {
+            next();
+          }
+        };
+      },
     ];
 
     let index = aOldVersion;
     let outer = this;
     function next() {
       if (index == aNewVersion) {
         if (aOldVersion === 0) {
           loadInitialContacts();
@@ -605,20 +635,24 @@ ContactDB.prototype = {
                 if (parsedNumber && parsedNumber.nationalFormat) {
                   let number = PhoneNumberUtils.normalize(parsedNumber.nationalFormat);
                   for (let i = 0; i < number.length; i++) {
                     containsSearch[number.substring(i, number.length)] = 1;
                   }
                 }
               }
               for (let num in containsSearch) {
-                contact.search.tel.push(num);
+                if (num != "null") {
+                  contact.search.tel.push(num);
+                }
               }
               for (let num in matchSearch) {
-                contact.search.parsedTel.push(num);
+                if (num != "null") {
+                  contact.search.parsedTel.push(num);
+                }
               }
             } else if ((field == "impp" || field == "email") && aContact.properties[field][i].value) {
               let value = aContact.properties[field][i].value;
               if (value && typeof value == "string") {
                 contact.search[field].push(value.toLowerCase());
               }
             } else {
               let val = aContact.properties[field][i];
@@ -976,16 +1010,22 @@ ContactDB.prototype = {
         let index = store.index("telMatch");
         let normalized = PhoneNumberUtils.normalize(options.filterValue,
                                                     /*numbersOnly*/ true);
 
         // Some countries need special handling for number matching. Bug 877302
         if (this.substringMatching && normalized.length > this.substringMatching) {
           normalized = normalized.slice(-this.substringMatching);
         }
+
+        if (!normalized.length) {
+          dump("ContactDB: normalized filterValue is empty, can't perform match search.\n");
+          return txn.abort();
+        }
+
         request = index.mozGetAll(normalized, limit);
       } else {
         // XXX: "contains" should be handled separately, this is "startsWith"
         if (options.filterOp === 'contains' && key !== 'tel') {
           dump("ContactDB: 'contains' only works for 'tel'. Falling back " +
                "to 'startsWith'.\n");
         }
         // not case sensitive
--- a/dom/contacts/moz.build
+++ b/dom/contacts/moz.build
@@ -1,16 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 # Android only supports the Contacts API on the Nightly channel.
-if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android' or CONFIG['NIGHTLY_BUILD']:
+if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
     TEST_DIRS += ['tests']
 
 EXTRA_COMPONENTS += [
     'ContactManager.js',
     'ContactManager.manifest',
 ]
 
 EXTRA_JS_MODULES += [
--- a/dom/devicestorage/test/test_823965.html
+++ b/dom/devicestorage/test/test_823965.html
@@ -19,17 +19,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 devicestorage_setup();
 
-var gFileName = "devicestorage/hi.png";
+var gFileName = "devicestorage/" + randomFilename(12) + "/hi.png";
 var gData = "My name is Doug Turner (?!?).  My IRC nick is DougT.  I like Maple cookies."
 var gDataBlob = new Blob([gData], {type: 'image/png'});
 
 function getSuccess(e) {
   var storage = navigator.getDeviceStorage("pictures");
   ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
   ok(e.target.result.name == gFileName, "File name should match");
@@ -58,17 +58,17 @@ function getError(e) {
   ok(false, "getError was called : " + e.target.error.name);
   devicestorage_cleanup();
 }
 
 function addSuccess(e) {
 
   var filename = e.target.result;
   if (filename[0] == "/") {
-    // We got /storgaeName/prefix/filename
+    // We got /storageName/prefix/filename
     // Remove the storageName (this shows up on FirefoxOS)
     filename = filename.substring(1); // Remove leading slash
     var slashIndex = filename.indexOf("/");
     if (slashIndex >= 0) {
       filename = filename.substring(slashIndex + 1); // Remove storageName
     }
   }
   ok(filename == gFileName, "File name should match");
@@ -88,17 +88,17 @@ function addError(e) {
   devicestorage_cleanup();
 }
 
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
 var storage = navigator.getDeviceStorage("pictures");
 ok(storage, "Should have gotten a storage");
 
-request = storage.addNamed(gDataBlob, "devicestorage/hi.png");
+request = storage.addNamed(gDataBlob, gFileName);
 ok(request, "Should have a non-null request");
 
 request.onsuccess = addSuccess;
 request.onerror = addError;
 
 </script>
 </pre>
 </body>
--- a/dom/devicestorage/test/test_basic.html
+++ b/dom/devicestorage/test/test_basic.html
@@ -19,17 +19,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 devicestorage_setup();
 
-var gFileName = "devicestorage/hi.png";
+var gFileName = "devicestorage/" + randomFilename(12) + "/hi.png";
 var gData = "My name is Doug Turner.  My IRC nick is DougT.  I like Maple cookies."
 var gDataBlob = new Blob([gData], {type: 'image/png'});
 var gFileReader = new FileReader();
 
 function getAfterDeleteSuccess(e) {
   ok(false, "file was deleted not successfully");
   devicestorage_cleanup();
 }
@@ -88,17 +88,31 @@ function readerCallback(e) {
 
 function getError(e) {
   ok(false, "getError was called : " + e.target.error.name);
   devicestorage_cleanup();
 }
 
 function addSuccess(e) {
 
-  ok(e.target.result == gFileName, "File name should match");
+  var filename = e.target.result;
+  if (filename[0] == "/") {
+    // We got /storageName/prefix/filename
+    // Remove the storageName (this shows up on FirefoxOS)
+    filename = filename.substring(1); // Remove leading slash
+    var slashIndex = filename.indexOf("/");
+    if (slashIndex >= 0) {
+      filename = filename.substring(slashIndex + 1); // Remove storageName
+    }
+  }
+  ok(filename == gFileName, "File name should match");
+
+  // Update gFileName to be the fully qualified name so that
+  // further checks will pass.
+  gFileName = e.target.result;
 
   var storage = navigator.getDeviceStorage("pictures");
   request = storage.get(gFileName);
   request.onsuccess = getSuccess;
   request.onerror = getError;
 
   ok(true, "addSuccess was called");
 }
@@ -108,17 +122,17 @@ function addError(e) {
   devicestorage_cleanup();
 }
 
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
 var storage = navigator.getDeviceStorage("pictures");
 ok(storage, "Should have gotten a storage");
 
-request = storage.addNamed(gDataBlob, "devicestorage/hi.png");
+request = storage.addNamed(gDataBlob, gFileName);
 ok(request, "Should have a non-null request");
 
 request.onsuccess = addSuccess;
 request.onerror = addError;
 
 </script>
 </pre>
 </body>
--- a/dom/devicestorage/test/test_enumerate.html
+++ b/dom/devicestorage/test/test_enumerate.html
@@ -30,17 +30,17 @@ function enumerateSuccess(e) {
     ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array")
     dump("We still have length = " + files.length + "\n");
     devicestorage_cleanup();
     return;
   }
   
   var filename = e.target.result.name;
   if (filename[0] == "/") {
-    // We got /storgaeName/prefix/filename
+    // We got /storageName/prefix/filename
     // Remove the storageName (this shows up on FirefoxOS)
     filename = filename.substring(1); // Remove leading slash
     var slashIndex = filename.indexOf("/");
     if (slashIndex >= 0) {
       filename = filename.substring(slashIndex + 1); // Remove storageName
     }
   }
   if (filename.startsWith(prefix)) {
--- a/dom/devicestorage/test/test_enumerateNoParam.html
+++ b/dom/devicestorage/test/test_enumerateNoParam.html
@@ -36,17 +36,17 @@ function enumerateSuccess(e) {
   if (e.target.result == null) {
     ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array")
     devicestorage_cleanup();
     return;
   }
   
   var filename = e.target.result.name;
   if (filename[0] == "/") {
-    // We got /storgaeName/prefix/filename
+    // We got /storageName/prefix/filename
     // Remove the storageName (this shows up on FirefoxOS)
     filename = filename.substring(1); // Remove leading slash
     var slashIndex = filename.indexOf("/");
     if (slashIndex >= 0) {
       filename = filename.substring(slashIndex + 1); // Remove storageName
     }
   }
   if (filename.startsWith(prefix)) {
--- a/dom/devicestorage/test/test_freeSpace.html
+++ b/dom/devicestorage/test/test_freeSpace.html
@@ -44,17 +44,18 @@ function addError(e) {
 function addSuccess(e) {
   request = storage.freeSpace();
   ok(request, "Should have a non-null request");
 
   request.onsuccess = freeSpaceSuccess;
   request.onerror = freeSpaceError;
 }
 
-request = storage.addNamed(createRandomBlob('image/png'), "a/b.png");
+var prefix = "devicestorage/" + randomFilename(12);
+request = storage.addNamed(createRandomBlob('image/png'), prefix + "/a/b.png");
 request.onsuccess = addSuccess;
 request.onerror = addError;
 
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/devicestorage/test/test_lastModificationFilter.html
+++ b/dom/devicestorage/test/test_lastModificationFilter.html
@@ -17,31 +17,40 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
+devicestorage_setup();
 
+var oldFiles = ["a.png", "b.png", "c.png"];
+var newFiles = ["d.png", "e.png", "f.png"];
+
+var storage = navigator.getDeviceStorage('pictures');
+var prefix = "devicestorage/" + randomFilename(12);
+var callback;
+var files;
+var i;
+var timestamp;
 
 function verifyAndDelete(prefix, files, e) {
-
   if (e.target.result == null) {
     ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array")
     dump("We still have length = " + files.length + "\n");
     dump(files + "\n");
     devicestorage_cleanup();
     return;
   }
 
   var filename = e.target.result.name;
   if (filename[0] == "/") {
-    // We got /storgaeName/prefix/filename
+    // We got /storageName/prefix/filename
     // Remove the storageName (this shows up on FirefoxOS)
     filename = filename.substring(1); // Remove leading slash
     var slashIndex = filename.indexOf("/");
     if (slashIndex >= 0) {
       filename = filename.substring(slashIndex + 1); // Remove storageName
     }
   }
   if (filename.startsWith(prefix)) {
@@ -51,76 +60,76 @@ function verifyAndDelete(prefix, files, 
   var index = files.indexOf(filename);
   ok(index > -1, "filename should be in the enumeration : " + e.target.result.name);
   if (index == -1)
     return;
 
   files.remove(index);
 
   // clean up
-  var storage = navigator.getDeviceStorage("pictures");
-  var cleanup = storage.delete(prefix + "/" + filename);
+  var cleanup = storage.delete(e.target.result.name);
   cleanup.onsuccess = function(e) {}
 }
 
-function addFiles(prefix, files, date, callback) {
-
-  const Cc = SpecialPowers.Cc;
-  const Ci = SpecialPowers.Ci;
-
-  var directoryService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
-
-  for (var i=0; i<files.length; i++) {
-
-    var f = directoryService.get("TmpD", Ci.nsIFile);
-    f.appendRelativePath("device-storage-testing");
-
-    var path = prefix + '/' + files[i];
-    path.split("/").forEach(function(p) {
-      f.appendRelativePath(p);
-    });
-    f.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0644);
-    f.lastModifiedTime = date;
+function addSuccess(e) {
+  i = i + 1;
+  if (i == files.length) {
+    callback();
+    return;
   }
-  callback();
+  addFile(files[i]);
 }
 
-
-devicestorage_setup();
-
-var prefix = "devicestorage/" + randomFilename(12)
-
-var oldFiles = ["a.png", "b.png", "c.png"];
-var newFiles = ["d.png", "e.png", "f.png"];
+function addError(e) {
+  ok(false, "addError was called : " + e.target.error.name);
+  devicestorage_cleanup();
+}
 
-// 157795200 is a long long time ago.
-addFiles(prefix, oldFiles, 157795200, addNewFiles);
-
-function enumerateNew() {
+function addFile(filename){
+  var req = storage.addNamed(createRandomBlob('image/png'), prefix + '/' + files[i]);
+  req.onsuccess = addSuccess;
+  req.onerror = addError;
+}
 
-  var storage = navigator.getDeviceStorage("pictures");
-  ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
-
-// 836031600 is a long time ago
-  var cursor = storage.enumerate(prefix, {"since": new Date(836031600)});
+function afterNewFiles() {
+  var cursor = storage.enumerate(prefix, {"since": timestamp});
   cursor.onsuccess = function(e) {
     verifyAndDelete(prefix, newFiles, e);
     if (e.target.result) {
       e.target.continue();
     }
-  }
-
+  };
   cursor.onerror = function (e) {
     ok(false, "handleError was called : " + e.target.error.name);
     devicestorage_cleanup();
-  }
+  };
 }
 
 function addNewFiles() {
-  addFiles(prefix, newFiles, Date.now(), enumerateNew);
+  i = 0;
+  files = newFiles;
+  callback = afterNewFiles;
+  addFile(files[0]);
+}
+
+function beforeNewFiles() {
+  timestamp = new Date();
+  setTimeout(addNewFiles, 1000);
 }
 
+function afterOldFiles() {
+  setTimeout(beforeNewFiles, 1000);
+}
+
+function addOldFiles() {
+  i = 0;
+  files = oldFiles;
+  callback = afterOldFiles;
+  addFile(files[0]);
+}
+
+addOldFiles();
 
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/devicestorage/test/test_usedSpace.html
+++ b/dom/devicestorage/test/test_usedSpace.html
@@ -44,17 +44,18 @@ function addError(e) {
 function addSuccess(e) {
   request = storage.usedSpace();
   ok(request, "Should have a non-null request");
 
   request.onsuccess = usedSpaceSuccess;
   request.onerror = usedSpaceError;
 }
 
-request = storage.addNamed(createRandomBlob('image/png'), "a/b.png");
+var prefix = "devicestorage/" + randomFilename(12);
+request = storage.addNamed(createRandomBlob('image/png'), prefix + "/a/b.png");
 request.onsuccess = addSuccess;
 request.onerror = addError;
 
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/devicestorage/test/test_watch.html
+++ b/dom/devicestorage/test/test_watch.html
@@ -35,17 +35,17 @@ function addError(e) {
 }
 
 function onChange(e) {
 
   dump("we saw: " + e.path + " " + e.reason + "\n");
 
   var filename = e.path;
   if (filename[0] == "/") {
-    // We got /storgaeName/prefix/filename
+    // We got /storageName/prefix/filename
     // Remove the storageName (this shows up on FirefoxOS)
     filename = filename.substring(1); // Remove leading slash
     var slashIndex = filename.indexOf("/");
     if (slashIndex >= 0) {
       filename = filename.substring(slashIndex + 1); // Remove storageName
     }
   }
   if (filename == gFileName) {
--- a/dom/devicestorage/test/test_watchOther.html
+++ b/dom/devicestorage/test/test_watchOther.html
@@ -35,17 +35,17 @@ function addError(e) {
 }
 
 function onChange(e) {
 
   dump("we saw: " + e.path + " " + e.reason + "\n");
 
   var filename = e.path;
   if (filename[0] == "/") {
-    // We got /storgaeName/prefix/filename
+    // We got /storageName/prefix/filename
     // Remove the storageName (this shows up on FirefoxOS)
     filename = filename.substring(1); // Remove leading slash
     var slashIndex = filename.indexOf("/");
     if (slashIndex >= 0) {
       filename = filename.substring(slashIndex + 1); // Remove storageName
     }
   }
   if (filename == gFileName) {
--- a/dom/messages/SystemMessageInternal.js
+++ b/dom/messages/SystemMessageInternal.js
@@ -44,18 +44,18 @@ const kMessages =["SystemMessageManager:
                   "child-process-shutdown"]
 
 function debug(aMsg) {
   // dump("-- SystemMessageInternal " + Date.now() + " : " + aMsg + "\n");
 }
 
 
 let defaultMessageConfigurator = {
-  get safeToSendBeforeRunningApp() {
-    return true;
+  get mustShowRunningApp() {
+    return false;
   }
 };
 
 const MSG_SENT_SUCCESS = 0;
 const MSG_SENT_FAILURE_PERM_DENIED = 1;
 const MSG_SENT_FAILURE_APP_NOT_RUNNING = 2;
 
 // Implementation of the component used by internal users.
@@ -203,20 +203,17 @@ SystemMessageInternal.prototype = {
       return;
     }
 
     let page = this._findPage(aType, aPageURI.spec, aManifestURI.spec);
     if (page) {
       // Queue this message in the corresponding pages.
       this._queueMessage(page, aMessage, messageID);
 
-      if (result === MSG_SENT_FAILURE_APP_NOT_RUNNING) {
-        // Don't open the page again if we already sent the message to it.
-        this._openAppPage(page, aMessage, aExtra);
-      }
+      this._openAppPage(page, aMessage, aExtra, result);
     }
   },
 
   broadcastMessage: function broadcastMessage(aType, aMessage, aExtra) {
     // Buffer system messages until the webapps' registration is ready,
     // so that we can know the correct pages registered to be broadcasted.
     if (!this._webappsRegistryReady) {
       this._bufferedSysMsgs.push({ how: "broadcast",
@@ -248,20 +245,17 @@ SystemMessageInternal.prototype = {
         // which was not allowed to be sent.
         if (result === MSG_SENT_FAILURE_PERM_DENIED) {
           return;
         }
 
         // Queue this message in the corresponding pages.
         this._queueMessage(aPage, aMessage, messageID);
 
-        if (result === MSG_SENT_FAILURE_APP_NOT_RUNNING) {
-          // Open app pages to handle their pending messages.
-          this._openAppPage(aPage, aMessage, aExtra);
-        }
+        this._openAppPage(aPage, aMessage, aExtra, result);
       }
     }, this);
   },
 
   registerPage: function registerPage(aType, aPageURI, aManifestURI) {
     if (!aPageURI || !aManifestURI) {
       throw Cr.NS_ERROR_INVALID_ARG;
     }
@@ -525,23 +519,38 @@ SystemMessageInternal.prototype = {
     // Queue the message for this page because we've never known if an app is
     // opened or not. We'll clean it up when the app has already received it.
     aPage.pendingMessages.push({ msg: aMessage, msgID: aMessageID });
     if (aPage.pendingMessages.length > kMaxPendingMessages) {
       aPage.pendingMessages.splice(0, 1);
     }
   },
 
-  _openAppPage: function _openAppPage(aPage, aMessage, aExtra) {
+  _openAppPage: function _openAppPage(aPage, aMessage, aExtra, aMsgSentStatus) {
+    // This means the app must be brought to the foreground.
+    let showApp = this._getMessageConfigurator(aPage.type).mustShowRunningApp;
+
+    // We should send the open-app message if the system message was
+    // not sent, or if it was sent but we should show the app anyway.
+    if ((aMsgSentStatus === MSG_SENT_SUCCESS) && !showApp) {
+      return;
+    }
+
+    // This flag means the app must *only* be brought to the foreground
+    // and we don't need to load the app to handle messages.
+    let onlyShowApp = (aMsgSentStatus === MSG_SENT_SUCCESS) && showApp;
+
     // We don't need to send the full object to observers.
     let page = { uri: aPage.uri,
                  manifest: aPage.manifest,
                  type: aPage.type,
                  extra: aExtra,
-                 target: aMessage.target };
+                 target: aMessage.target,
+                 onlyShowApp: onlyShowApp,
+                 showApp: showApp };
     debug("Asking to open " + JSON.stringify(page));
     Services.obs.notifyObservers(this, "system-messages-open-app", JSON.stringify(page));
   },
 
   _isPageMatched: function _isPageMatched(aPage, aType, aPageURI, aManifestURI) {
     return (aPage.type === aType &&
             aPage.manifest === aManifestURI &&
             aPage.uri === aPageURI)
@@ -575,50 +584,44 @@ SystemMessageInternal.prototype = {
       return MSG_SENT_FAILURE_PERM_DENIED;
     }
 
     let appPageIsRunning = false;
     let pageKey = this._createKeyForPage({ type: aType,
                                            manifest: aManifestURI,
                                            uri: aPageURI });
 
-    // Tries to send the message to a previously opened app only if it's safe
-    // to do so. Generically, it's safe to send the message if the app isn't
-    // going to be reloaded. And it's not safe otherwise
-    if (this._getMessageConfigurator(aType).safeToSendBeforeRunningApp) {
-
-      let targets = this._listeners[aManifestURI];
-      if (targets) {
-        for (let index = 0; index < targets.length; ++index) {
-          let target = targets[index];
-          // We only need to send the system message to the targets (processes)
-          // which contain the window page that matches the manifest/page URL of
-          // the destination of system message.
-          if (target.winCounts[aPageURI] === undefined) {
-            continue;
-          }
+    let targets = this._listeners[aManifestURI];
+    if (targets) {
+      for (let index = 0; index < targets.length; ++index) {
+        let target = targets[index];
+        // We only need to send the system message to the targets (processes)
+        // which contain the window page that matches the manifest/page URL of
+        // the destination of system message.
+        if (target.winCounts[aPageURI] === undefined) {
+          continue;
+        }
 
-          appPageIsRunning = true;
-          // We need to acquire a CPU wake lock for that page and expect that
-          // we'll receive a "SystemMessageManager:HandleMessagesDone" message
-          // when the page finishes handling the system message. At that point,
-          // we'll release the lock we acquired.
-          this._acquireCpuWakeLock(pageKey);
+        appPageIsRunning = true;
+        // We need to acquire a CPU wake lock for that page and expect that
+        // we'll receive a "SystemMessageManager:HandleMessagesDone" message
+        // when the page finishes handling the system message. At that point,
+        // we'll release the lock we acquired.
+        this._acquireCpuWakeLock(pageKey);
 
-          // Multiple windows can share the same target (process), the content
-          // window needs to check if the manifest/page URL is matched. Only
-          // *one* window should handle the system message.
-          let manager = target.target;
-          manager.sendAsyncMessage("SystemMessageManager:Message",
-                                   { type: aType,
-                                     msg: aMessage,
-                                     manifest: aManifestURI,
-                                     uri: aPageURI,
-                                     msgID: aMessageID });
-        }
+        // Multiple windows can share the same target (process), the content
+        // window needs to check if the manifest/page URL is matched. Only
+        // *one* window should handle the system message.
+        let manager = target.target;
+        manager.sendAsyncMessage("SystemMessageManager:Message",
+                                 { type: aType,
+                                   msg: aMessage,
+                                   manifest: aManifestURI,
+                                   uri: aPageURI,
+                                   msgID: aMessageID });
       }
     }
 
     if (!appPageIsRunning) {
       // The app page isn't running and relies on the 'open-app' chrome event to
       // wake it up. We still need to acquire a CPU wake lock for that page and
       // expect that we will receive a "SystemMessageManager:HandleMessagesDone"
       // message when the page finishes handling the system message with other
--- a/dom/messages/interfaces/nsISystemMessagesInternal.idl
+++ b/dom/messages/interfaces/nsISystemMessagesInternal.idl
@@ -51,18 +51,17 @@ interface nsISystemMessagesWrapper: nsIS
    */
   jsval wrapMessage(in jsval message, in nsIDOMWindow window);
 };
 
 /*
  * Implements an interface to allow specific message types to
  * configure some behaviors
  */
-[scriptable, uuid(8a71981b-a462-4697-b63c-925997f9d47b)]
+[scriptable, uuid(a0e970f6-faa9-4605-89d6-fafae8b10a80)]
 interface nsISystemMessagesConfigurator: nsISupports
 {
   /*
-   * Will be true if this type of system messages is safe to send
-   * before the frontend has activated the app to process it.
-   * The default value (if there's no overriding class) is true
+   * Will be true if this type of system messages assumes/requires
+   * that the app will be brought to the front always.
    */
-  readonly attribute boolean safeToSendBeforeRunningApp;
+  readonly attribute boolean mustShowRunningApp;
 };
--- a/dom/system/gonk/systemlibs.js
+++ b/dom/system/gonk/systemlibs.js
@@ -195,23 +195,53 @@ this.libnetutils = (function () {
   let sdkVersion = libcutils.property_get("ro.build.version.sdk") || "0";
   sdkVersion = parseInt(sdkVersion, 10);
   if (sdkVersion >= 15) {
     let ipaddrbuf = ctypes.char.array(4096)();
     let gatewaybuf = ctypes.char.array(4096)();
     let prefixLen = ctypes.int();
     let dns1buf = ctypes.char.array(4096)();
     let dns2buf = ctypes.char.array(4096)();
+    let dnslistbuf = ctypes.char.ptr.array(4)();
     let serverbuf = ctypes.char.array(4096)();
     let lease = ctypes.int();
     let vendorbuf = ctypes.char.array(4096)();
+    let domainbuf = ctypes.char.array(4096)();
     let c_dhcp_do_request;
+    let c_dhcp_do_request_renew;
 
-    // also changed for 16
-    if (sdkVersion >= 16) {
+    // also changed for 16 and 18
+    if (sdkVersion >= 18) {
+      dnslistbuf[0] = dns1buf;
+      dnslistbuf[1] = dns2buf;
+      c_dhcp_do_request =
+        library.declare("dhcp_do_request", ctypes.default_abi,
+                        ctypes.int,       // return value
+                        ctypes.char.ptr,  // ifname
+                        ctypes.char.ptr,  // ipaddr
+                        ctypes.char.ptr,  // gateway
+                        ctypes.int.ptr,   // prefixlen
+                        ctypes.char.ptr.array(), // dns
+                        ctypes.char.ptr,  // server
+                        ctypes.int.ptr,   // lease
+                        ctypes.char.ptr,  // vendorinfo
+                        ctypes.char.ptr); // domain
+      c_dhcp_do_request_renew =
+        library.declare("dhcp_do_request_renew", ctypes.default_abi,
+                        ctypes.int,       // return value
+                        ctypes.char.ptr,  // ifname
+                        ctypes.char.ptr,  // ipaddr
+                        ctypes.char.ptr,  // gateway
+                        ctypes.int.ptr,   // prefixlen
+                        ctypes.char.ptr.array(),  // dns
+                        ctypes.char.ptr,  // server
+                        ctypes.int.ptr,   // lease
+                        ctypes.char.ptr,  // vendorinfo
+                        ctypes.char.ptr); // domain
+    } else if (sdkVersion >= 16) {
       c_dhcp_do_request =
         library.declare("dhcp_do_request", ctypes.default_abi,
                         ctypes.int,       // return value
                         ctypes.char.ptr,  // ifname
                         ctypes.char.ptr,  // ipaddr
                         ctypes.char.ptr,  // gateway
                         ctypes.int.ptr,   // prefixlen
                         ctypes.char.ptr,  // dns1
@@ -231,17 +261,27 @@ this.libnetutils = (function () {
                         ctypes.char.ptr, // dns2
                         ctypes.char.ptr, // server
                         ctypes.int.ptr); // lease
     }
 
 
     iface.dhcp_do_request = function dhcp_do_request(ifname) {
       let ret;
-      if (sdkVersion >= 16) {
+      if (sdkVersion >= 18) {
+        ret = c_dhcp_do_request(ifname,
+                                ipaddrbuf,
+                                gatewaybuf,
+                                prefixLen.address(),
+                                dnslistbuf,
+                                serverbuf,
+                                lease.address(),
+                                vendorbuf,
+                                domainbuf);
+      } else if (sdkVersion >= 16) {
         ret = c_dhcp_do_request(ifname,
                                 ipaddrbuf,
                                 gatewaybuf,
                                 prefixLen.address(),
                                 dns1buf,
                                 dns2buf,
                                 serverbuf,
                                 lease.address(),
@@ -265,28 +305,31 @@ this.libnetutils = (function () {
         ret: ret | 0,
         ipaddr_str: ipaddrbuf.readString(),
         mask: netHelpers.makeMask(prefixLen.value),
         gateway_str: gatewaybuf.readString(),
         dns1_str: dns1buf.readString(),
         dns2_str: dns2buf.readString(),
         server_str: serverbuf.readString(),
         lease: lease.value | 0,
-        vendor_str: vendorbuf.readString()
+        vendor_str: vendorbuf.readString(),
+        domain_str: domainbuf.readString()
       };
       obj.ipaddr = netHelpers.stringToIP(obj.ipaddr_str);
       obj.mask_str = netHelpers.ipToString(obj.mask);
       obj.broadcast_str = netHelpers.ipToString((obj.ipaddr & obj.mask) + ~obj.mask);
       obj.gateway = netHelpers.stringToIP(obj.gateway_str);
       obj.dns1 = netHelpers.stringToIP(obj.dns1_str);
       obj.dns2 = netHelpers.stringToIP(obj.dns2_str);
       obj.server = netHelpers.stringToIP(obj.server_str);
       return obj;
     };
+
     // dhcp_do_request_renew() went away in newer libnetutils.
+    // .. and then came back in 4.3! XXX implement support for this
     iface.dhcp_do_request_renew = iface.dhcp_do_request;
 
     // Same deal with ifc_reset_connections.
     let c_ifc_reset_connections =
       library.declare("ifc_reset_connections",
                       ctypes.default_abi,
                       ctypes.int,
                       ctypes.char.ptr,
--- a/editor/libeditor/html/nsTableEditor.cpp
+++ b/editor/libeditor/html/nsTableEditor.cpp
@@ -2747,16 +2747,26 @@ nsHTMLEditor::GetCellDataAt(nsIDOMElemen
 
 // When all you want is the cell
 NS_IMETHODIMP 
 nsHTMLEditor::GetCellAt(nsIDOMElement* aTable, int32_t aRowIndex, int32_t aColIndex, nsIDOMElement **aCell)
 {
   NS_ENSURE_ARG_POINTER(aCell);
   *aCell = nullptr;
 
+  if (!aTable)
+  {
+    // Get the selected table or the table enclosing the selection anchor
+    nsCOMPtr<nsIDOMElement> table;
+    nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), nullptr, getter_AddRefs(table));
+    NS_ENSURE_SUCCESS(res, res);
+    NS_ENSURE_TRUE(table, NS_ERROR_FAILURE);
+    aTable = table;
+  }
+
   nsTableOuterFrame* tableFrame = GetTableFrame(aTable);
   if (!tableFrame)
     return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsIDOMElement> domCell =
     do_QueryInterface(tableFrame->GetCellAt(aRowIndex, aColIndex));
   domCell.forget(aCell);
 
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -115,21 +115,36 @@ DeprecatedTextureClientShmem::GetSurface
                     ? OPEN_READ_WRITE
                     : OPEN_READ_ONLY;
     mSurface = ShadowLayerForwarder::OpenDescriptor(mode, mDescriptor);
   }
 
   return mSurface.get();
 }
 
+
+gfx::DrawTarget*
+DeprecatedTextureClientShmem::LockDrawTarget()
+{
+  if (mDrawTarget) {
+    return mDrawTarget;
+  }
+
+  gfxASurface* surface = GetSurface();
+  mDrawTarget = gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surface, mSize);
+
+  return mDrawTarget;
+}
+
 void
 DeprecatedTextureClientShmem::Unlock()
 {
   mSurface = nullptr;
   mSurfaceAsImage = nullptr;
+  mDrawTarget = nullptr;
 
   ShadowLayerForwarder::CloseDescriptor(mDescriptor);
 }
 
 gfxImageSurface*
 DeprecatedTextureClientShmem::LockImageSurface()
 {
   if (!mSurfaceAsImage) {
--- a/gfx/layers/client/TextureClient.h
+++ b/gfx/layers/client/TextureClient.h
@@ -160,27 +160,29 @@ public:
   ~DeprecatedTextureClientShmem() { ReleaseResources(); }
 
   virtual bool SupportsType(DeprecatedTextureClientType aType) MOZ_OVERRIDE
   {
     return aType == TEXTURE_SHMEM || aType == TEXTURE_CONTENT;
   }
   virtual gfxImageSurface* LockImageSurface() MOZ_OVERRIDE;
   virtual gfxASurface* LockSurface() MOZ_OVERRIDE { return GetSurface(); }
+  virtual gfx::DrawTarget* LockDrawTarget();
   virtual void Unlock() MOZ_OVERRIDE;
   virtual void EnsureAllocated(gfx::IntSize aSize, gfxASurface::gfxContentType aType) MOZ_OVERRIDE;
 
   virtual void ReleaseResources() MOZ_OVERRIDE;
   virtual void SetDescriptor(const SurfaceDescriptor& aDescriptor) MOZ_OVERRIDE;
   virtual gfxASurface::gfxContentType GetContentType() MOZ_OVERRIDE { return mContentType; }
 private:
   gfxASurface* GetSurface();
 
   nsRefPtr<gfxASurface> mSurface;
   nsRefPtr<gfxImageSurface> mSurfaceAsImage;
+  RefPtr<gfx::DrawTarget> mDrawTarget;
 
   gfxASurface::gfxContentType mContentType;
   gfx::IntSize mSize;
 
   friend class CompositingFactory;
 };
 
 class DeprecatedTextureClientShmemYCbCr : public DeprecatedTextureClient
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -629,17 +629,18 @@ gfxPlatform::GetSourceSurfaceForSurface(
     format = FORMAT_B8G8R8X8;
   } else {
     format = FORMAT_B8G8R8A8;
   }
 
   RefPtr<SourceSurface> srcBuffer;
 
 #ifdef XP_WIN
-  if (aSurface->GetType() == gfxASurface::SurfaceTypeD2D) {
+  if (aSurface->GetType() == gfxASurface::SurfaceTypeD2D &&
+      format != FORMAT_A8) {
     NativeSurface surf;
     surf.mFormat = format;
     surf.mType = NATIVE_SURFACE_D3D10_TEXTURE;
     surf.mSurface = static_cast<gfxD2DSurface*>(aSurface)->GetTexture();
     mozilla::gfx::DrawTarget *dt = static_cast<mozilla::gfx::DrawTarget*>(aSurface->GetData(&kDrawTarget));
     if (dt) {
       dt->Flush();
     }
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -67,20 +67,16 @@ 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/builtin/BinaryData.cpp
+++ b/js/src/builtin/BinaryData.cpp
@@ -1,29 +1,30 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "mozilla/FloatingPoint.h"
+#include <vector>
 
 #include "jscompartment.h"
 #include "jsfun.h"
 #include "jsobj.h"
 #include "jsutil.h"
 
-#include "vm/TypedArrayObject.h"
+#include "gc/Marking.h"
+#include "vm/GlobalObject.h"
 #include "vm/String.h"
 #include "vm/StringBuffer.h"
-#include "vm/GlobalObject.h"
+#include "vm/TypedArrayObject.h"
 
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 
 using namespace js;
 
 /*
  * Reify() takes a complex binary data object `owner` and an offset and tries to
@@ -176,19 +177,26 @@ GetAlign(JSContext *cx, HandleObject typ
     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;
+    HeapId name;
+    HeapPtrObject type;
     size_t offset;
+
+    FieldInfo() : offset(0) {}
+
+    FieldInfo(const FieldInfo &o)
+        : name(o.name.get()), type(o.type), offset(o.offset)
+    {
+    }
 };
 
 Class js::DataClass = {
     "Data",
     JSCLASS_HAS_CACHED_PROTO(JSProto_Data),
     JS_PropertyStub,
     JS_DeletePropertyStub,
     JS_PropertyStub,
@@ -257,17 +265,17 @@ IsSameStructType(JSContext *cx, HandleOb
     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)
+        if (fieldInfo1.name.get() != fieldInfo2.name.get())
             return false;
 
         if (fieldInfo1.offset != fieldInfo2.offset)
             return false;
 
         RootedObject fieldType1(cx, fieldInfo1.type);
         RootedObject fieldType2(cx, fieldInfo2.type);
         if (!IsSameBinaryDataType(cx, fieldType1, fieldType2))
@@ -1422,21 +1430,21 @@ Class StructType::class_ = {
     JS_PropertyStub,
     JS_DeletePropertyStub,
     JS_PropertyStub,
     JS_StrictPropertyStub,
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     StructType::finalize,
-    NULL,
-    NULL,
-    NULL,
+    NULL, /* checkAccess */
+    NULL, /* call */
+    NULL, /* hasInstance */
     BinaryStruct::construct,
-    NULL
+    StructType::trace
 };
 
 Class BinaryStruct::class_ = {
     "BinaryStruct",
     Class::NON_NATIVE |
     JSCLASS_HAS_RESERVED_SLOTS(BLOCK_RESERVED_SLOTS) |
     JSCLASS_HAS_PRIVATE |
     JSCLASS_HAS_CACHED_PROTO(JSProto_StructType),
@@ -1445,18 +1453,18 @@ Class BinaryStruct::class_ = {
     JS_PropertyStub,
     JS_StrictPropertyStub,
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     BinaryStruct::finalize,
     NULL,           /* checkAccess */
     NULL,           /* call        */
+    NULL,           /* hasInstance */
     NULL,           /* construct   */
-    NULL,           /* hasInstance */
     BinaryStruct::obj_trace,
     JS_NULL_CLASS_EXT,
     {
         NULL, /* lookupGeneric */
         NULL, /* lookupProperty */
         NULL, /* lookupElement */
         NULL, /* lookupSpecial */
         NULL, /* defineGeneric */
@@ -1685,16 +1693,27 @@ StructType::construct(JSContext *cx, uns
 
 void
 StructType::finalize(FreeOp *op, JSObject *obj)
 {
     FieldList *list = static_cast<FieldList *>(obj->getPrivate());
     delete list;
 }
 
+void
+StructType::trace(JSTracer *tracer, JSObject *obj)
+{
+    FieldList *fieldList = static_cast<FieldList *>(obj->getPrivate());
+    JS_ASSERT(fieldList);
+    for (FieldList::iterator it = fieldList->begin(); it != fieldList->end(); ++it) {
+        gc::MarkId(tracer, &(it->name), "structtype.field.name");
+        MarkObject(tracer, &(it->type), "structtype.field.type");
+    }
+}
+
 JSBool
 StructType::toString(JSContext *cx, unsigned int argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedObject thisObj(cx, args.thisv().toObjectOrNull());
 
     if (!IsStructType(thisObj))
--- a/js/src/builtin/BinaryData.h
+++ b/js/src/builtin/BinaryData.h
@@ -3,18 +3,19 @@
  * 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 "jsfriendapi.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,
@@ -272,16 +273,17 @@ class StructType : public JSObject
 
     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);
+    static void trace(JSTracer *tracer, JSObject *obj);
 };
 
 class BinaryStruct : public JSObject
 {
   private:
     static JSObject *createEmpty(JSContext *cx, HandleObject type);
     static JSObject *create(JSContext *cx, HandleObject type);
 
--- a/js/src/ion/AsmJSSignalHandlers.cpp
+++ b/js/src/ion/AsmJSSignalHandlers.cpp
@@ -991,19 +991,8 @@ js::TriggerOperationCallbackForAsmJSCode
     DWORD oldProtect;
     if (!VirtualProtect(module.functionCode(), module.functionBytes(), PAGE_NOACCESS, &oldProtect))
         MOZ_CRASH();
 #else  // assume Unix
     if (mprotect(module.functionCode(), module.functionBytes(), PROT_NONE))
         MOZ_CRASH();
 #endif
 }
-
-#ifdef MOZ_ASAN
-// When running with asm.js under AddressSanitizer, we need to explicitely
-// tell AddressSanitizer to allow custom signal handlers because it will 
-// otherwise trigger ASan's SIGSEGV handler for the internal SIGSEGVs that 
-// asm.js would otherwise handle.
-extern "C" MOZ_ASAN_BLACKLIST
-const char* __asan_default_options() {
-    return "allow_user_segv_handler=1";
-}
-#endif
--- a/js/src/ion/BaselineCompiler.h
+++ b/js/src/ion/BaselineCompiler.h
@@ -8,18 +8,18 @@
 #define ion_BaselineCompiler_h
 
 #ifdef JS_ION
 
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jsinfer.h"
 
+#include "ion/BaselineIC.h"
 #include "ion/BaselineJIT.h"
-#include "ion/BaselineIC.h"
 #include "ion/BytecodeAnalysis.h"
 #include "ion/FixedList.h"
 #include "ion/IonAllocPolicy.h"
 #include "ion/IonCode.h"
 #if defined(JS_CPU_X86)
 # include "ion/x86/BaselineCompiler-x86.h"
 #elif defined(JS_CPU_X64)
 # include "ion/x64/BaselineCompiler-x64.h"
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -4661,16 +4661,17 @@ IonBuilder::jsop_funapplyarguments(uint3
     RootedFunction target(cx, getSingleCallTarget(funTypes));
 
     // When this script isn't inlined, use MApplyArgs,
     // to copy the arguments from the stack and call the function
     if (inliningDepth_ == 0) {
 
         // Vp
         MPassArg *passVp = current->pop()->toPassArg();
+        passVp->getArgument()->setFoldedUnchecked();
         passVp->replaceAllUsesWith(passVp->getArgument());
         passVp->block()->discard(passVp);
 
         // This
         MPassArg *passThis = current->pop()->toPassArg();
         MDefinition *argThis = passThis->getArgument();
         passThis->replaceAllUsesWith(argThis);
         passThis->block()->discard(passThis);
@@ -4700,16 +4701,17 @@ IonBuilder::jsop_funapplyarguments(uint3
     // When inlining we have the arguments the function gets called with
     // and can optimize even more, by just calling the functions with the args.
     JS_ASSERT(inliningDepth_ > 0);
 
     CallInfo callInfo(cx, false);
 
     // Vp
     MPassArg *passVp = current->pop()->toPassArg();
+    passVp->getArgument()->setFoldedUnchecked();
     passVp->replaceAllUsesWith(passVp->getArgument());
     passVp->block()->discard(passVp);
 
     // Arguments
     Vector<MDefinition *> args(cx);
     if (!args.append(inlineCallInfo_->argv().begin(), inlineCallInfo_->argv().end()))
         return false;
     callInfo.setArgs(&args);
--- a/js/src/ion/arm/Architecture-arm.cpp
+++ b/js/src/ion/arm/Architecture-arm.cpp
@@ -1,39 +1,40 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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/. */
 
-#define HWCAP_ARMv7 (1 << 31)
+#include "ion/arm/Architecture-arm.h"
+
 #include "mozilla/StandardInteger.h"
 
-#include <sys/types.h>
+#include <elf.h>
+#include <fcntl.h>
 #include <sys/stat.h>
-#include <fcntl.h>
+#include <sys/types.h>
 #include <unistd.h>
-#include <elf.h>
+
+#include "ion/arm/Assembler-arm.h"
 
-// lame check for kernel version
-// see bug 586550
 #if !(defined(ANDROID) || defined(MOZ_B2G))
+#define HWCAP_ARMv7 (1 << 31)
 #include <asm/hwcap.h>
 #else
 #define HWCAP_VFP      (1<<0)
 #define HWCAP_VFPv3    (1<<1)
 #define HWCAP_VFPv3D16 (1<<2)
 #define HWCAP_VFPv4    (1<<3)
 #define HWCAP_IDIVA    (1<<4)
 #define HWCAP_IDIVT    (1<<5)
 #define HWCAP_NEON     (1<<6)
 #define HWCAP_ARMv7    (1<<7)
 #endif
-#include "ion/arm/Architecture-arm.h"
-#include "ion/arm/Assembler-arm.h"
+
 namespace js {
 namespace ion {
 
 uint32_t getFlags()
 {
     static bool isSet = false;
     static uint32_t flags = 0;
     if (isSet)
--- a/js/src/ion/arm/Architecture-arm.h
+++ b/js/src/ion/arm/Architecture-arm.h
@@ -2,17 +2,20 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 ion_arm_Architecture_arm_h
 #define ion_arm_Architecture_arm_h
 
+#include "mozilla/StandardInteger.h"
+
 #include <limits.h>
+
 // gcc appears to use __ARM_PCS_VFP to denote that the target is a hard-float target.
 #ifdef __ARM_PCS_VFP
 #define JS_CPU_ARM_HARDFP
 #endif
 namespace js {
 namespace ion {
 
 static const uint32_t STACK_SLOT_SIZE       = 4;
--- a/js/src/ion/arm/Assembler-arm.cpp
+++ b/js/src/ion/arm/Assembler-arm.cpp
@@ -1,24 +1,26 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "ion/arm/Assembler-arm.h"
+
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MathAlgorithms.h"
 
-#include "ion/arm/Assembler-arm.h"
-#include "ion/arm/MacroAssembler-arm.h"
-#include "gc/Marking.h"
+#include "jscompartment.h"
 #include "jsutil.h"
+
 #include "assembler/jit/ExecutableAllocator.h"
-#include "jscompartment.h"
+#include "gc/Marking.h"
 #include "ion/IonCompartment.h"
+#include "ion/arm/MacroAssembler-arm.h"
 
 using namespace js;
 using namespace js::ion;
 
 using mozilla::CountLeadingZeroes32;
 
 ABIArgGenerator::ABIArgGenerator() :
 #if defined(JS_CPU_ARM_HARDFP)
--- a/js/src/ion/arm/Assembler-arm.h
+++ b/js/src/ion/arm/Assembler-arm.h
@@ -6,21 +6,21 @@
 
 #ifndef ion_arm_Assembler_arm_h
 #define ion_arm_Assembler_arm_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Util.h"
 
-#include "ion/shared/Assembler-shared.h"
 #include "assembler/assembler/AssemblerBufferWithConstantPool.h"
+#include "ion/arm/Architecture-arm.h"
 #include "ion/CompactBuffer.h"
 #include "ion/IonCode.h"
-#include "ion/arm/Architecture-arm.h"
+#include "ion/shared/Assembler-shared.h"
 #include "ion/shared/IonAssemblerBufferWithConstantPools.h"
 
 namespace js {
 namespace ion {
 
 //NOTE: there are duplicates in this list!
 // sometimes we want to specifically refer to the
 // link register as a link register (bl lr is much
--- a/js/src/ion/arm/Bailouts-arm.cpp
+++ b/js/src/ion/arm/Bailouts-arm.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "jscntxt.h"
 #include "jscompartment.h"
+
 #include "ion/Bailouts.h"
 #include "ion/IonCompartment.h"
 
 using namespace js;
 using namespace js::ion;
 
 #if 0
 // no clue what these asserts should be.
--- a/js/src/ion/arm/BaselineHelpers-arm.h
+++ b/js/src/ion/arm/BaselineHelpers-arm.h
@@ -3,21 +3,20 @@
  * 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 ion_arm_BaselineHelpers_arm_h
 #define ion_arm_BaselineHelpers_arm_h
 
 #ifdef JS_ION
-
+#include "ion/BaselineFrame.h"
+#include "ion/BaselineIC.h"
+#include "ion/BaselineRegisters.h"
 #include "ion/IonMacroAssembler.h"
-#include "ion/BaselineFrame.h"
-#include "ion/BaselineRegisters.h"
-#include "ion/BaselineIC.h"
 
 namespace js {
 namespace ion {
 
 // Distance from sp to the top Value inside an IC stub (no return address on the stack on ARM).
 static const size_t ICStackValueOffset = 0;
 
 inline void
--- a/js/src/ion/arm/BaselineIC-arm.cpp
+++ b/js/src/ion/arm/BaselineIC-arm.cpp
@@ -1,18 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "ion/BaselineJIT.h"
-#include "ion/BaselineIC.h"
 #include "ion/BaselineCompiler.h"
 #include "ion/BaselineHelpers.h"
+#include "ion/BaselineIC.h"
+#include "ion/BaselineJIT.h"
 #include "ion/IonLinker.h"
 
 using namespace js;
 using namespace js::ion;
 
 namespace js {
 namespace ion {
 
--- a/js/src/ion/arm/CodeGenerator-arm.cpp
+++ b/js/src/ion/arm/CodeGenerator-arm.cpp
@@ -1,32 +1,33 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "ion/arm/CodeGenerator-arm.h"
+
 #include "mozilla/MathAlgorithms.h"
 
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jsnum.h"
 
-#include "ion/arm/CodeGenerator-arm.h"
-#include "ion/PerfSpewer.h"
 #include "ion/CodeGenerator.h"
 #include "ion/IonCompartment.h"
 #include "ion/IonFrames.h"
 #include "ion/MIR.h"
 #include "ion/MIRGraph.h"
-#include "ion/shared/CodeGenerator-shared-inl.h"
+#include "ion/PerfSpewer.h"
 #include "vm/Shape.h"
 
 #include "jsscriptinlines.h"
 
+#include "ion/shared/CodeGenerator-shared-inl.h"
 #include "vm/Shape-inl.h"
 
 using namespace js;
 using namespace js::ion;
 
 using mozilla::FloorLog2;
 
 // shared
--- a/js/src/ion/arm/IonFrames-arm.h
+++ b/js/src/ion/arm/IonFrames-arm.h
@@ -3,17 +3,16 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef ion_arm_IonFrames_arm_h
 #define ion_arm_IonFrames_arm_h
 
 #include "ion/shared/IonFrames-shared.h"
-//#include "ion/arm/Assembler-arm.h"
 
 namespace js {
 namespace ion {
 
 class IonFramePrefix;
 // Layout of the frame prefix. This assumes the stack architecture grows down.
 // If this is ever not the case, we'll have to refactor.
 class IonCommonFrameLayout
--- a/js/src/ion/arm/Lowering-arm.cpp
+++ b/js/src/ion/arm/Lowering-arm.cpp
@@ -1,19 +1,20 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/MathAlgorithms.h"
 
-#include "ion/MIR.h"
+#include "ion/arm/Assembler-arm.h"
 #include "ion/Lowering.h"
-#include "ion/arm/Assembler-arm.h"
+#include "ion/MIR.h"
+
 #include "ion/shared/Lowering-shared-inl.h"
 
 using namespace js;
 using namespace js::ion;
 
 using mozilla::FloorLog2;
 
 bool
--- a/js/src/ion/arm/MacroAssembler-arm.cpp
+++ b/js/src/ion/arm/MacroAssembler-arm.cpp
@@ -1,18 +1,19 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "ion/arm/MacroAssembler-arm.h"
+
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MathAlgorithms.h"
 
-#include "ion/arm/MacroAssembler-arm.h"
 #include "ion/BaselineFrame.h"
 #include "ion/MoveEmitter.h"
 
 using namespace js;
 using namespace ion;
 
 using mozilla::Abs;
 
--- a/js/src/ion/arm/MacroAssembler-arm.h
+++ b/js/src/ion/arm/MacroAssembler-arm.h
@@ -4,21 +4,22 @@
  * 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 ion_arm_MacroAssembler_arm_h
 #define ion_arm_MacroAssembler_arm_h
 
 #include "mozilla/DebugOnly.h"
 
+#include "jsopcode.h"
+
 #include "ion/arm/Assembler-arm.h"
 #include "ion/IonCaches.h"
 #include "ion/IonFrames.h"
 #include "ion/MoveResolver.h"
-#include "jsopcode.h"
 
 using mozilla::DebugOnly;
 
 namespace js {
 namespace ion {
 
 static Register CallReg = ip;
 static const int defaultShift = 3;
--- a/js/src/ion/arm/MoveEmitter-arm.h
+++ b/js/src/ion/arm/MoveEmitter-arm.h
@@ -2,18 +2,18 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 ion_arm_MoveEmitter_arm_h
 #define ion_arm_MoveEmitter_arm_h
 
+#include "ion/IonMacroAssembler.h"
 #include "ion/MoveResolver.h"
-#include "ion/IonMacroAssembler.h"
 
 namespace js {
 namespace ion {
 
 class CodeGenerator;
 
 class MoveEmitterARM
 {
--- a/js/src/ion/arm/Trampoline-arm.cpp
+++ b/js/src/ion/arm/Trampoline-arm.cpp
@@ -1,24 +1,25 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "jscompartment.h"
+
 #include "assembler/assembler/MacroAssembler.h"
+#include "ion/arm/BaselineHelpers-arm.h"
+#include "ion/Bailouts.h"
+#include "ion/ExecutionModeInlines.h"
 #include "ion/IonCompartment.h"
+#include "ion/IonFrames.h"
 #include "ion/IonLinker.h"
-#include "ion/IonFrames.h"
 #include "ion/IonSpewer.h"
-#include "ion/Bailouts.h"
 #include "ion/VMFunctions.h"
-#include "ion/arm/BaselineHelpers-arm.h"
-#include "ion/ExecutionModeInlines.h"
 
 using namespace js;
 using namespace js::ion;
 
 static void
 GenerateReturn(MacroAssembler &masm, int returnCode)
 {
     // Restore non-volatile registers
--- a/js/src/ion/shared/MacroAssembler-x86-shared.h
+++ b/js/src/ion/shared/MacroAssembler-x86-shared.h
@@ -9,19 +9,19 @@
 
 #include "mozilla/Casting.h"
 #include "mozilla/DebugOnly.h"
 
 #include "jsopcode.h"
 
 #include "ion/IonCaches.h"
 #include "ion/IonFrames.h"
-#ifdef JS_CPU_X86
+#if defined(JS_CPU_X86)
 # include "ion/x86/Assembler-x86.h"
-#elif JS_CPU_X64
+#elif defined(JS_CPU_X64)
 # include "ion/x64/Assembler-x64.h"
 #endif
 
 namespace js {
 namespace ion {
 
 class MacroAssemblerX86Shared : public Assembler
 {
--- a/js/src/ion/x64/Lowering-x64.cpp
+++ b/js/src/ion/x64/Lowering-x64.cpp
@@ -2,18 +2,19 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "ion/x64/Lowering-x64.h"
 
 #include "ion/MIR.h"
+#include "ion/x64/Assembler-x64.h"
+
 #include "ion/shared/Lowering-shared-inl.h"
-#include "ion/x64/Assembler-x64.h"
 
 using namespace js;
 using namespace js::ion;
 
 bool
 LIRGeneratorX64::useBox(LInstruction *lir, size_t n, MDefinition *mir,
                         LUse::Policy policy, bool useAtStart)
 {
--- a/js/src/ion/x86/Lowering-x86.cpp
+++ b/js/src/ion/x86/Lowering-x86.cpp
@@ -2,18 +2,19 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "ion/x86/Lowering-x86.h"
 
 #include "ion/MIR.h"
+#include "ion/x86/Assembler-x86.h"
+
 #include "ion/shared/Lowering-shared-inl.h"
-#include "ion/x86/Assembler-x86.h"
 
 using namespace js;
 using namespace js::ion;
 
 bool
 LIRGeneratorX86::useBox(LInstruction *lir, size_t n, MDefinition *mir,
                         LUse::Policy policy, bool useAtStart)
 {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug898047.js
@@ -0,0 +1,23 @@
+function g(aa) {
+    assertEq(aa, 123);
+}
+function f(x, yy) {
+    if (yy < 0) {
+	for (var j=0; j<100; j++) {}
+    }
+    var o = yy < 2000 ? o1 : o2;
+    o.fun.apply(22, arguments);
+}
+
+function test() {
+    o1 = {};
+    o1.fun = g;
+
+    o2 = {};
+    o2.x = 3;
+    o2.fun = g;
+
+    for (var i=0; i<3000; i++)
+	f(123, i);
+}
+test();
--- a/js/src/jsapi-tests/tests.cpp
+++ b/js/src/jsapi-tests/tests.cpp
@@ -72,16 +72,21 @@ JSObject * JSAPITest::createGlobal(JSPri
 }
 
 int main(int argc, char *argv[])
 {
     int total = 0;
     int failures = 0;
     const char *filter = (argc == 2) ? argv[1] : NULL;
 
+    if (!JS_Init()) {
+        printf("TEST-UNEXPECTED-FAIL | jsapi-tests | JS_Init() failed.\n");
+        return 1;
+    }
+
     for (JSAPITest *test = JSAPITest::list; test; test = test->next) {
         const char *name = test->name();
         if (filter && strstr(name, filter) == NULL)
             continue;
 
         total += 1;
 
         printf("%s\n", name);
@@ -100,15 +105,17 @@ int main(int argc, char *argv[])
                    (test->knownFail ? "TEST-KNOWN-FAIL" : "TEST-UNEXPECTED-FAIL"),
                    name, (int) messages.length(), messages.begin());
             if (!test->knownFail)
                 failures++;
         }
         test->uninit();
     }
 
+    JS_ShutDown();
+
     if (failures) {
         printf("\n%d unexpected failure%s.\n", failures, (failures == 1 ? "" : "s"));
         return 1;
     }
     printf("\nPassed: ran %d tests.\n", total);
     return 0;
 }
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -7,38 +7,35 @@
 /*
  * JavaScript API.
  */
 
 #include "jsapi.h"
 
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/PodOperations.h"
-#include "mozilla/ThreadLocal.h"
 
 #include <ctype.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h>
 
 #include "jsarray.h"
 #include "jsatom.h"
 #include "jsbool.h"
 #include "jsclone.h"
 #include "jscntxt.h"
 #include "jsdate.h"
-#include "jsdtoa.h"
 #include "jsexn.h"
 #include "jsfun.h"
 #include "jsgc.h"
 #include "jsiter.h"
 #include "jslock.h"
 #include "jsmath.h"
-#include "jsnativestack.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "json.h"
 #include "jsprf.h"
 #include "jsproxy.h"
 #include "jsscript.h"
 #include "jsstr.h"
 #include "jstypes.h"
@@ -60,30 +57,27 @@
 #include "builtin/ParallelArray.h"
 #include "builtin/RegExp.h"
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/FullParseHandler.h"  // for JS_BufferIsCompileableUnit
 #include "frontend/Parser.h" // for JS_BufferIsCompileableUnit
 #include "gc/Marking.h"
 #include "gc/Memory.h"
 #include "ion/AsmJS.h"
-#ifdef JS_ION
-#include "ion/Ion.h"
-#endif
-#include "ion/PcScriptCache.h"
 #include "js/CharacterEncoding.h"
 #if ENABLE_INTL_API
 #include "unicode/uclean.h"
 #include "unicode/utypes.h"
-#endif
+#endif // ENABLE_INTL_API
 #include "vm/DateObject.h"
 #include "vm/Debugger.h"
 #include "vm/ErrorObject.h"
 #include "vm/Interpreter.h"
 #include "vm/NumericConversions.h"
+#include "vm/Runtime.h"
 #include "vm/Shape.h"
 #include "vm/StopIterationObject.h"
 #include "vm/StringBuffer.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/WeakMapObject.h"
 #include "vm/WrapperObject.h"
 #include "vm/Xdr.h"
 #include "yarr/BumpPointerAllocator.h"
@@ -627,547 +621,128 @@ JS_PUBLIC_API(JSBool)
 JS_IsBuiltinFunctionConstructor(JSFunction *fun)
 {
     return fun->isBuiltinFunctionConstructor();
 }
 
 /************************************************************************/
 
 /*
- * Has a new runtime ever been created?  This flag is used to control things
- * that should happen only once across all runtimes.
+ * SpiderMonkey's initialization status is tracked here, and it controls things
+ * that should happen only once across all runtimes.  It's an API requirement
+ * that JS_Init (and JS_ShutDown, if called) be called in a thread-aware
+ * manner, so this variable doesn't need to be atomic.
+ *
+ * The only reason at present for the restriction that you can't call
+ * JS_Init/stuff/JS_ShutDown multiple times is the Windows PRMJ NowInit
+ * initialization code, which uses PR_CallOnce to initialize the PRMJ_Now
+ * subsystem.  (For reinitialization to be permitted, we'd need to "reset" the
+ * called-once status -- doable, but more trouble than it's worth now.)
+ * Initializing that subsystem from JS_Init eliminates the problem, but
+ * initialization can take a comparatively long time (15ms or so), so we
+ * really don't want to do it in JS_Init, and we really do want to do it only
+ * when PRMJ_Now is eventually called.
  */
-static JSBool js_NewRuntimeWasCalled = JS_FALSE;
-
-/*
- * Thread Local Storage slot for storing the runtime for a thread.
- */
-mozilla::ThreadLocal<PerThreadData *> js::TlsPerThreadData;
+enum InitState { Uninitialized, Running, ShutDown };
+static InitState jsInitState = Uninitialized;
+
+JS_PUBLIC_API(JSBool)
+JS_Init(void)
+{
+    MOZ_ASSERT(jsInitState == Uninitialized,
+               "must call JS_Init once before any JSAPI operation except "
+               "JS_SetICUMemoryFunctions");
+    MOZ_ASSERT(!JSRuntime::hasLiveRuntimes(),
+               "how do we have live runtimes before JS_Init?");
+
+#ifdef DEBUG
+    // Assert that the numbers associated with the error names in js.msg are
+    // monotonically increasing.  It's not a compile-time check, but it's
+    // better than nothing.
+    int errorNumber = 0;
+#define MSG_DEF(name, number, count, exception, format)                       \
+    JS_ASSERT(name == errorNumber++);
+#include "js.msg"
+#undef MSG_DEF
+
+    // Assert that each message format has the correct number of braced
+    // parameters.
+#define MSG_DEF(name, number, count, exception, format)                       \
+    JS_BEGIN_MACRO                                                            \
+        unsigned numfmtspecs = 0;                                             \
+        for (const char *fmt = format; *fmt != '\0'; fmt++) {                 \
+            if (*fmt == '{' && isdigit(fmt[1]))                               \
+                ++numfmtspecs;                                                \
+        }                                                                     \
+        JS_ASSERT(count == numfmtspecs);                                      \
+    JS_END_MACRO;
+#include "js.msg"
+#undef MSG_DEF
+#endif /* DEBUG */
+
+    using js::TlsPerThreadData;
+    if (!TlsPerThreadData.initialized() && !TlsPerThreadData.init())
+        return false;
+
+#if defined(JS_ION)
+    if (!ion::InitializeIon())
+        return false;
+#endif
+
+    if (!ForkJoinSlice::InitializeTLS())
+        return false;
+
+#if ENABLE_INTL_API
+    UErrorCode err = U_ZERO_ERROR;
+    u_init(&err);
+    if (U_FAILURE(err))
+        return false;
+#endif // ENABLE_INTL_API
+
+    jsInitState = Running;
+    return true;
+}
+
+JS_PUBLIC_API(void)
+JS_ShutDown(void)
+{
+    MOZ_ASSERT(jsInitState == Running,
+               "JS_ShutDown must only be called after JS_Init and can't race with it");
+    MOZ_ASSERT(!JSRuntime::hasLiveRuntimes(),
+               "forgot to destroy a runtime before shutting down");
+
+    PRMJ_NowShutdown();
+
+#if ENABLE_INTL_API
+    u_cleanup();
+#endif // ENABLE_INTL_API
+
+    jsInitState = ShutDown;
+}
 
 #ifdef DEBUG
 JS_FRIEND_API(bool)
 JS::isGCEnabled()
 {
     return !TlsPerThreadData.get()->suppressGC;
 }
 #else
 JS_FRIEND_API(bool) JS::isGCEnabled() { return true; }
 #endif
 
-static const JSSecurityCallbacks NullSecurityCallbacks = { };
-
-static bool
-JitSupportsFloatingPoint()
-{
-#if defined(JS_ION)
-    if (!JSC::MacroAssembler::supportsFloatingPoint())
-        return false;
-
-#if defined(JS_ION) && WTF_ARM_ARCH_VERSION == 6
-    if (!js::ion::hasVFP())
-        return false;
-#endif
-
-    return true;
-#else
-    return false;
-#endif
-}
-
-PerThreadData::PerThreadData(JSRuntime *runtime)
-  : PerThreadDataFriendFields(),
-    runtime_(runtime),
-    ionTop(NULL),
-    ionJSContext(NULL),
-    ionStackLimit(0),
-    activation_(NULL),
-    asmJSActivationStack_(NULL),
-    dtoaState(NULL),
-    suppressGC(0),
-    gcKeepAtoms(0),
-    activeCompilations(0)
-{}
-
-PerThreadData::~PerThreadData()
-{
-    if (dtoaState)
-        js_DestroyDtoaState(dtoaState);
-
-    if (isInList())
-        removeFromThreadList();
-}
-
-bool
-PerThreadData::init()
-{
-    dtoaState = js_NewDtoaState();
-    if (!dtoaState)
-        return false;
-
-    return true;
-}
-
-void
-PerThreadData::addToThreadList()
-{
-    // PerThreadData which are created/destroyed off the main thread do not
-    // show up in the runtime's thread list.
-    runtime_->assertValidThread();
-    runtime_->threadList.insertBack(this);
-}
-
-void
-PerThreadData::removeFromThreadList()
-{
-    runtime_->assertValidThread();
-    removeFrom(runtime_->threadList);
-}
-
-JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
-  : mainThread(this),
-    interrupt(0),
-#ifdef JS_THREADSAFE
-    operationCallbackLock(NULL),
-#ifdef DEBUG
-    operationCallbackOwner(NULL),
-#endif
-    exclusiveAccessLock(NULL),
-    exclusiveAccessOwner(NULL),
-    mainThreadHasExclusiveAccess(false),
-    numExclusiveThreads(0),
-#endif
-    atomsCompartment(NULL),
-    systemZone(NULL),
-    numCompartments(0),
-    localeCallbacks(NULL),
-    defaultLocale(NULL),
-    defaultVersion_(JSVERSION_DEFAULT),
-#ifdef JS_THREADSAFE
-    ownerThread_(NULL),
-#endif
-    tempLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
-    freeLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
-    execAlloc_(NULL),
-    bumpAlloc_(NULL),
-    ionRuntime_(NULL),
-    selfHostingGlobal_(NULL),
-    nativeStackBase(0),
-    nativeStackQuota(0),
-    interpreterFrames(NULL),
-    cxCallback(NULL),
-    destroyCompartmentCallback(NULL),
-    compartmentNameCallback(NULL),
-    activityCallback(NULL),
-    activityCallbackArg(NULL),
-#ifdef JS_THREADSAFE
-    requestDepth(0),
-# ifdef DEBUG
-    checkRequestDepth(0),
-# endif
-#endif
-    gcSystemAvailableChunkListHead(NULL),
-    gcUserAvailableChunkListHead(NULL),
-    gcBytes(0),
-    gcMaxBytes(0),
-    gcMaxMallocBytes(0),
-    gcNumArenasFreeCommitted(0),
-    gcMarker(this),
-    gcVerifyPreData(NULL),
-    gcVerifyPostData(NULL),
-    gcChunkAllocationSinceLastGC(false),
-    gcNextFullGCTime(0),
-    gcLastGCTime(0),
-    gcJitReleaseTime(0),
-    gcMode(JSGC_MODE_GLOBAL),
-    gcAllocationThreshold(30 * 1024 * 1024),
-    gcHighFrequencyGC(false),
-    gcHighFrequencyTimeThreshold(1000),
-    gcHighFrequencyLowLimitBytes(100 * 1024 * 1024),
-    gcHighFrequencyHighLimitBytes(500 * 1024 * 1024),
-    gcHighFrequencyHeapGrowthMax(3.0),
-    gcHighFrequencyHeapGrowthMin(1.5),
-    gcLowFrequencyHeapGrowth(1.5),
-    gcDynamicHeapGrowth(false),
-    gcDynamicMarkSlice(false),
-    gcDecommitThreshold(32 * 1024 * 1024),
-    gcShouldCleanUpEverything(false),
-    gcGrayBitsValid(false),
-    gcIsNeeded(0),
-    gcStats(thisFromCtor()),
-    gcNumber(0),
-    gcStartNumber(0),
-    gcIsFull(false),
-    gcTriggerReason(JS::gcreason::NO_REASON),
-    gcStrictCompartmentChecking(false),
-#ifdef DEBUG
-    gcDisableStrictProxyCheckingCount(0),
-#endif
-    gcIncrementalState(gc::NO_INCREMENTAL),
-    gcLastMarkSlice(false),
-    gcSweepOnBackgroundThread(false),
-    gcFoundBlackGrayEdges(false),
-    gcSweepingZones(NULL),
-    gcZoneGroupIndex(0),
-    gcZoneGroups(NULL),
-    gcCurrentZoneGroup(NULL),
-    gcSweepPhase(0),
-    gcSweepZone(NULL),
-    gcSweepKindIndex(0),
-    gcAbortSweepAfterCurrentGroup(false),
-    gcArenasAllocatedDuringSweep(NULL),
-#ifdef DEBUG
-    gcMarkingValidator(NULL),
-#endif
-    gcInterFrameGC(0),
-    gcSliceBudget(SliceBudget::Unlimited),
-    gcIncrementalEnabled(true),
-    gcGenerationalEnabled(true),
-    gcManipulatingDeadZones(false),
-    gcObjectsMarkedInDeadZones(0),
-    gcPoke(false),
-    heapState(Idle),
-#ifdef JSGC_GENERATIONAL
-    gcNursery(thisFromCtor()),
-    gcStoreBuffer(thisFromCtor(), gcNursery),
-#endif
-#ifdef JS_GC_ZEAL
-    gcZeal_(0),
-    gcZealFrequency(0),
-    gcNextScheduled(0),
-    gcDeterministicOnly(false),
-    gcIncrementalLimit(0),
-#endif
-    gcValidate(true),
-    gcFullCompartmentChecks(false),
-    gcCallback(NULL),
-    gcSliceCallback(NULL),
-    gcFinalizeCallback(NULL),
-    analysisPurgeCallback(NULL),
-    analysisPurgeTriggerBytes(0),
-    gcMallocBytes(0),
-    scriptAndCountsVector(NULL),
-    NaNValue(UndefinedValue()),
-    negativeInfinityValue(UndefinedValue()),
-    positiveInfinityValue(UndefinedValue()),
-    emptyString(NULL),
-    sourceHook(NULL),
-    debugMode(false),
-    spsProfiler(thisFromCtor()),
-    profilingScripts(false),
-    alwaysPreserveCode(false),
-    hadOutOfMemory(false),
-    data(NULL),
-    gcLock(NULL),
-    gcHelperThread(thisFromCtor()),
-#ifdef JS_THREADSAFE
-#ifdef JS_ION
-    workerThreadState(NULL),
-#endif
-    sourceCompressorThread(),
-#endif
-    defaultFreeOp_(thisFromCtor(), false),
-    debuggerMutations(0),
-    securityCallbacks(const_cast<JSSecurityCallbacks *>(&NullSecurityCallbacks)),
-    DOMcallbacks(NULL),
-    destroyPrincipals(NULL),
-    structuredCloneCallbacks(NULL),
-    telemetryCallback(NULL),
-    propertyRemovals(0),
-#if !ENABLE_INTL_API
-    thousandsSeparator(0),
-    decimalSeparator(0),
-    numGrouping(0),
-#endif
-    mathCache_(NULL),
-    trustedPrincipals_(NULL),
-    wrapObjectCallback(TransparentObjectWrapper),
-    sameCompartmentWrapObjectCallback(NULL),
-    preWrapObjectCallback(NULL),
-    preserveWrapperCallback(NULL),
-#ifdef DEBUG
-    noGCOrAllocationCheck(0),
-#endif
-    jitHardening(false),
-    jitSupportsFloatingPoint(false),
-    ionPcScriptCache(NULL),
-    threadPool(this),
-    ctypesActivityCallback(NULL),
-    parallelWarmup(0),
-    ionReturnOverride_(MagicValue(JS_ARG_POISON)),
-    useHelperThreads_(useHelperThreads),
-    requestedHelperThreadCount(-1)
-#ifdef DEBUG
-    , enteredPolicy(NULL)
-#endif
-{
-    /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
-    JS_INIT_CLIST(&onNewGlobalObjectWatchers);
-
-    PodZero(&debugHooks);
-    PodZero(&atomState);
-
-#if JS_STACK_GROWTH_DIRECTION > 0
-    nativeStackLimit = UINTPTR_MAX;
-#endif
-}
-
-bool
-JSRuntime::init(uint32_t maxbytes)
-{
-#ifdef JS_THREADSAFE
-    ownerThread_ = PR_GetCurrentThread();
-
-    operationCallbackLock = PR_NewLock();
-    if (!operationCallbackLock)
-        return false;
-
-    exclusiveAccessLock = PR_NewLock();
-    if (!exclusiveAccessLock)
-        return false;
-#endif
-
-    if (!mainThread.init())
-        return false;
-
-    js::TlsPerThreadData.set(&mainThread);
-    mainThread.addToThreadList();
-
-    if (!js_InitGC(this, maxbytes))
-        return false;
-
-    if (!gcMarker.init())
-        return false;
-
-    const char *size = getenv("JSGC_MARK_STACK_LIMIT");
-    if (size)
-        SetMarkStackLimit(this, atoi(size));
-
-    ScopedJSDeletePtr<Zone> atomsZone(new_<Zone>(this));
-    if (!atomsZone)
-        return false;
-
-    JS::CompartmentOptions options;
-    ScopedJSDeletePtr<JSCompartment> atomsCompartment(new_<JSCompartment>(atomsZone.get(), options));
-    if (!atomsCompartment || !atomsCompartment->init(NULL))
-        return false;
-
-    zones.append(atomsZone.get());
-    atomsZone->compartments.append(atomsCompartment.get());
-
-    atomsCompartment->isSystem = true;
-    atomsZone->isSystem = true;
-    atomsZone->setGCLastBytes(8192, GC_NORMAL);
-
-    atomsZone.forget();
-    this->atomsCompartment = atomsCompartment.forget();
-
-    if (!InitAtoms(this))
-        return false;
-
-    if (!InitRuntimeNumberState(this))
-        return false;
-
-    dateTimeInfo.updateTimeZoneAdjustment();
-
-    if (!scriptDataTable.init())
-        return false;
-
-    if (!threadPool.init())
-        return false;
-
-#ifdef JS_THREADSAFE
-    if (useHelperThreads() && !sourceCompressorThread.init())
-        return false;
-#endif
-
-    if (!evalCache.init())
-        return false;
-
-    nativeStackBase = GetNativeStackBase();
-
-    jitSupportsFloatingPoint = JitSupportsFloatingPoint();
-    return true;
-}
-
-JSRuntime::~JSRuntime()
-{
-    mainThread.removeFromThreadList();
-
-#ifdef JS_THREADSAFE
-# ifdef JS_ION
-    if (workerThreadState)
-        js_delete(workerThreadState);
-# endif
-    sourceCompressorThread.finish();
-
-    clearOwnerThread();
-
-    JS_ASSERT(!operationCallbackOwner);
-    if (operationCallbackLock)
-        PR_DestroyLock(operationCallbackLock);
-
-    JS_ASSERT(!exclusiveAccessOwner);
-    if (exclusiveAccessLock)
-        PR_DestroyLock(exclusiveAccessLock);
-#endif
-
-    /*
-     * Even though all objects in the compartment are dead, we may have keep
-     * some filenames around because of gcKeepAtoms.
-     */
-    FreeScriptData(this);
-
-#ifdef DEBUG
-    /* Don't hurt everyone in leaky ol' Mozilla with a fatal JS_ASSERT! */
-    if (hasContexts()) {
-        unsigned cxcount = 0;
-        for (ContextIter acx(this); !acx.done(); acx.next()) {
-            fprintf(stderr,
-"JS API usage error: found live context at %p\n",
-                    (void *) acx.get());
-            cxcount++;
-        }
-        fprintf(stderr,
-"JS API usage error: %u context%s left in runtime upon JS_DestroyRuntime.\n",
-                cxcount, (cxcount == 1) ? "" : "s");
-    }
-#endif
-
-#if !ENABLE_INTL_API
-    FinishRuntimeNumberState(this);
-#endif
-    FinishAtoms(this);
-
-    js_FinishGC(this);
-#ifdef JS_THREADSAFE
-    if (gcLock)
-        PR_DestroyLock(gcLock);
-#endif
-
-    js_delete(bumpAlloc_);
-    js_delete(mathCache_);
-#ifdef JS_ION
-    js_delete(ionRuntime_);
-#endif
-    js_delete(execAlloc_);  /* Delete after ionRuntime_. */
-
-    if (ionPcScriptCache)
-        js_delete(ionPcScriptCache);
-
-#ifdef JSGC_GENERATIONAL
-    gcStoreBuffer.disable();
-    gcNursery.disable();
-#endif
-}
-
-#ifdef JS_THREADSAFE
-void
-JSRuntime::setOwnerThread()
-{
-    JS_ASSERT(ownerThread_ == (void *)0xc1ea12);  /* "clear" */
-    JS_ASSERT(requestDepth == 0);
-    JS_ASSERT(js_NewRuntimeWasCalled);
-    JS_ASSERT(js::TlsPerThreadData.get() == NULL);
-    ownerThread_ = PR_GetCurrentThread();
-    js::TlsPerThreadData.set(&mainThread);
-    nativeStackBase = GetNativeStackBase();
-    if (nativeStackQuota)
-        JS_SetNativeStackQuota(this, nativeStackQuota);
-#ifdef XP_MACOSX
-    asmJSMachExceptionHandler.setCurrentThread();
-#endif
-}
-
-void
-JSRuntime::clearOwnerThread()
-{
-    assertValidThread();
-    JS_ASSERT(requestDepth == 0);
-    JS_ASSERT(js_NewRuntimeWasCalled);
-    ownerThread_ = (void *)0xc1ea12;  /* "clear" */
-    js::TlsPerThreadData.set(NULL);
-    nativeStackBase = 0;
-#if JS_STACK_GROWTH_DIRECTION > 0
-    mainThread.nativeStackLimit = UINTPTR_MAX;
-#else
-    mainThread.nativeStackLimit = 0;
-#endif
-#ifdef XP_MACOSX
-    asmJSMachExceptionHandler.clearCurrentThread();
-#endif
-}
-
-JS_FRIEND_API(void)
-JSRuntime::abortIfWrongThread() const
-{
-    if (ownerThread_ != PR_GetCurrentThread())
-        MOZ_CRASH();
-    if (!js::TlsPerThreadData.get()->associatedWith(this))
-        MOZ_CRASH();
-}
-
-#ifdef DEBUG
-JS_FRIEND_API(void)
-JSRuntime::assertValidThread() const
-{
-    JS_ASSERT(ownerThread_ == PR_GetCurrentThread());
-    JS_ASSERT(js::TlsPerThreadData.get()->associatedWith(this));
-}
-#endif  /* DEBUG */
-#endif  /* JS_THREADSAFE */
-
 JS_PUBLIC_API(JSRuntime *)
 JS_NewRuntime(uint32_t maxbytes, JSUseHelperThreads useHelperThreads)
 {
-    if (!js_NewRuntimeWasCalled) {
-#ifdef DEBUG
-        /*
-         * This code asserts that the numbers associated with the error names
-         * in jsmsg.def are monotonically increasing.  It uses values for the
-         * error names enumerated in jscntxt.c.  It's not a compile-time check
-         * but it's better than nothing.
-         */
-        int errorNumber = 0;
-#define MSG_DEF(name, number, count, exception, format)                       \
-    JS_ASSERT(name == errorNumber++);
-#include "js.msg"
-#undef MSG_DEF
-
-#define MSG_DEF(name, number, count, exception, format)                       \
-    JS_BEGIN_MACRO                                                            \
-        unsigned numfmtspecs = 0;                                                \
-        const char *fmt;                                                      \
-        for (fmt = format; *fmt != '\0'; fmt++) {                             \
-            if (*fmt == '{' && isdigit(fmt[1]))                               \
-                ++numfmtspecs;                                                \
-        }                                                                     \
-        JS_ASSERT(count == numfmtspecs);                                      \
-    JS_END_MACRO;
-#include "js.msg"
-#undef MSG_DEF
-#endif /* DEBUG */
-
-        if (!js::TlsPerThreadData.init())
-            return NULL;
-
-        js_NewRuntimeWasCalled = JS_TRUE;
-    }
+    MOZ_ASSERT(jsInitState == Running,
+               "must call JS_Init prior to creating any JSRuntimes");
 
     JSRuntime *rt = js_new<JSRuntime>(useHelperThreads);
     if (!rt)
         return NULL;
 
-#if defined(JS_ION)
-    if (!ion::InitializeIon())
-        return NULL;
-#endif
-
-    if (!ForkJoinSlice::InitializeTLS())
-        return NULL;
-
     if (!rt->init(maxbytes)) {
         JS_DestroyRuntime(rt);
         return NULL;
     }
 
     return rt;
 }
 
@@ -1176,31 +751,29 @@ JS_DestroyRuntime(JSRuntime *rt)
 {
     js_free(rt->defaultLocale);
     js_delete(rt);
 }
 
 JS_PUBLIC_API(bool)
 JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn)
 {
+    MOZ_ASSERT(jsInitState == Uninitialized,
+               "must call JS_SetICUMemoryFunctions before any other JSAPI "
+               "operation (including JS_Init)");
+
 #if ENABLE_INTL_API
     UErrorCode status = U_ZERO_ERROR;
     u_setMemoryFunctions(/* context = */ NULL, allocFn, reallocFn, freeFn, &status);
     return U_SUCCESS(status);
 #else
     return true;
 #endif
 }
 
-JS_PUBLIC_API(void)
-JS_ShutDown(void)
-{
-    PRMJ_NowShutdown();
-}
-
 JS_PUBLIC_API(void *)
 JS_GetRuntimePrivate(JSRuntime *rt)
 {
     return rt->data;
 }
 
 JS_PUBLIC_API(void)
 JS_SetRuntimePrivate(JSRuntime *rt, void *data)
@@ -7006,25 +6579,27 @@ JS_GetCurrentThread()
     return 0;
 #endif
 }
 
 extern JS_PUBLIC_API(void)
 JS_ClearRuntimeThread(JSRuntime *rt)
 {
     AssertHeapIsIdle(rt);
+    JS_ASSERT(jsInitState == Running);
 #ifdef JS_THREADSAFE
     rt->clearOwnerThread();
 #endif
 }
 
 extern JS_PUBLIC_API(void)
 JS_SetRuntimeThread(JSRuntime *rt)
 {
     AssertHeapIsIdle(rt);
+    JS_ASSERT(jsInitState == Running);
 #ifdef JS_THREADSAFE
     rt->setOwnerThread();
 #endif
 }
 
 extern JS_NEVER_INLINE JS_PUBLIC_API(void)
 JS_AbortIfWrongThread(JSRuntime *rt)
 {
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1765,16 +1765,52 @@ JS_IsBuiltinFunctionConstructor(JSFuncti
  */
 
 typedef enum JSUseHelperThreads
 {
     JS_NO_HELPER_THREADS,
     JS_USE_HELPER_THREADS
 } JSUseHelperThreads;
 
+/**
+ * Initialize SpiderMonkey, returning true only if initialization succeeded.
+ * Once this method has succeeded, it is safe to call JS_NewRuntime and other
+ * JSAPI methods.
+ *
+ * This method must be called before any other JSAPI method is used on any
+ * thread.  Once it has been used, it is safe to call any JSAPI method, and it
+ * remains safe to do so until JS_ShutDown is correctly called.
+ *
+ * It is currently not possible to initialize SpiderMonkey multiple times (that
+ * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so
+ * again).  This restriction may eventually be lifted.
+ */
+extern JS_PUBLIC_API(JSBool)
+JS_Init(void);
+
+/**
+ * Destroy free-standing resources allocated by SpiderMonkey, not associated
+ * with any runtime, context, or other structure.
+ *
+ * This method should be called after all other JSAPI data has been properly
+ * cleaned up: every new runtime must have been destroyed, every new context
+ * must have been destroyed, and so on.  Calling this method before all other
+ * resources have been destroyed has undefined behavior.
+ *
+ * Failure to call this method, at present, has no adverse effects other than
+ * leaking memory.  This may not always be the case; it's recommended that all
+ * embedders call this method when all other JSAPI operations have completed.
+ *
+ * It is currently not possible to initialize SpiderMonkey multiple times (that
+ * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so
+ * again).  This restriction may eventually be lifted.
+ */
+extern JS_PUBLIC_API(void)
+JS_ShutDown(void);
+
 extern JS_PUBLIC_API(JSRuntime *)
 JS_NewRuntime(uint32_t maxbytes, JSUseHelperThreads useHelperThreads);
 
 extern JS_PUBLIC_API(void)
 JS_DestroyRuntime(JSRuntime *rt);
 
 // These are equivalent to ICU's |UMemAllocFn|, |UMemReallocFn|, and
 // |UMemFreeFn| types.  The first argument (called |context| in the ICU docs)
@@ -1783,19 +1819,16 @@ typedef void *(*JS_ICUAllocFn)(const voi
 typedef void *(*JS_ICUReallocFn)(const void *, void *p, size_t size);
 typedef void (*JS_ICUFreeFn)(const void *, void *p);
 
 // This function can be used to track memory used by ICU.
 // Do not use it unless you know what you are doing!
 extern JS_PUBLIC_API(bool)
 JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn);
 
-extern JS_PUBLIC_API(void)
-JS_ShutDown(void);
-
 JS_PUBLIC_API(void *)
 JS_GetRuntimePrivate(JSRuntime *rt);
 
 extern JS_PUBLIC_API(JSRuntime *)
 JS_GetRuntime(JSContext *cx);
 
 JS_PUBLIC_API(void)
 JS_SetRuntimePrivate(JSRuntime *rt, void *data);
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -199,18 +199,16 @@ JS_DefineFunctionsWithHelp(JSContext *cx
 typedef bool (* JS_SourceHook)(JSContext *cx, JS::Handle<JSScript*> script,
                                jschar **src, uint32_t *length);
 
 extern JS_FRIEND_API(void)
 JS_SetSourceHook(JSRuntime *rt, JS_SourceHook hook);
 
 namespace js {
 
-extern mozilla::ThreadLocal<PerThreadData *> TlsPerThreadData;
-
 inline JSRuntime *
 GetRuntime(const JSContext *cx)
 {
     return ContextFriendFields::get(cx)->runtime_;
 }
 
 inline JSCompartment *
 GetContextCompartment(const JSContext *cx)
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5417,16 +5417,20 @@ main(int argc, char **argv, char **envp)
         OOM_printAllocationCount = true;
 
 #if defined(JS_CPU_X86)
     if (op.getBoolOption("no-fpu"))
         JSC::MacroAssembler::SetFloatingPointDisabled();
 #endif
 #endif
 
+    // Start the engine.
+    if (!JS_Init())
+        return 1;
+
     /* Use the same parameters as the browser in xpcjsruntime.cpp. */
     rt = JS_NewRuntime(32L * 1024L * 1024L, JS_USE_HELPER_THREADS);
     if (!rt)
         return 1;
     gTimeoutFunc = NullValue();
     if (!JS_AddNamedValueRootRT(rt, &gTimeoutFunc, "gTimeoutFunc"))
         return 1;
 
--- a/js/src/vm/ForkJoin.cpp
+++ b/js/src/vm/ForkJoin.cpp
@@ -1707,19 +1707,19 @@ ForkJoinSlice::check()
     else
         return true;
 }
 
 bool
 ForkJoinSlice::InitializeTLS()
 {
     if (!TLSInitialized) {
+        if (PR_NewThreadPrivateIndex(&ThreadPrivateIndex, NULL) != PR_SUCCESS)
+            return false;
         TLSInitialized = true;
-        PRStatus status = PR_NewThreadPrivateIndex(&ThreadPrivateIndex, NULL);
-        return status == PR_SUCCESS;
     }
     return true;
 }
 
 void
 ForkJoinSlice::requestGC(JS::gcreason::Reason reason)
 {
     shared->requestGC(reason);
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -1,38 +1,518 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "vm/Runtime-inl.h"
 
+#include "mozilla/Atomics.h"
+#include "mozilla/DebugOnly.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/ThreadLocal.h"
 #include "mozilla/Util.h"
 
 #include <locale.h>
 #include <string.h>
 
 #include "jsatom.h"
+#include "jsdtoa.h"
 #include "jsgc.h"
 #include "jsmath.h"
+#include "jsnativestack.h"
 #include "jsobj.h"
 #include "jsscript.h"
+#include "jswrapper.h"
 
 #include "js/MemoryMetrics.h"
+#include "ion/IonCompartment.h"
+#include "ion/PcScriptCache.h"
 #include "yarr/BumpPointerAllocator.h"
 
 #include "jscntxtinlines.h"
 #include "jsgcinlines.h"
 
 using namespace js;
 using namespace js::gc;
 
+using mozilla::Atomic;
+using mozilla::DebugOnly;
 using mozilla::PodZero;
+using mozilla::ThreadLocal;
+
+/* static */ ThreadLocal<PerThreadData*> js::TlsPerThreadData;
+
+/* static */ Atomic<size_t> JSRuntime::liveRuntimesCount;
+
+const JSSecurityCallbacks js::NullSecurityCallbacks = { };
+
+PerThreadData::PerThreadData(JSRuntime *runtime)
+  : PerThreadDataFriendFields(),
+    runtime_(runtime),
+    ionTop(NULL),
+    ionJSContext(NULL),
+    ionStackLimit(0),
+    activation_(NULL),
+    asmJSActivationStack_(NULL),
+    dtoaState(NULL),
+    suppressGC(0),
+    gcKeepAtoms(0),
+    activeCompilations(0)
+{}
+
+PerThreadData::~PerThreadData()
+{
+    if (dtoaState)
+        js_DestroyDtoaState(dtoaState);
+
+    if (isInList())
+        removeFromThreadList();
+}
+
+bool
+PerThreadData::init()
+{
+    dtoaState = js_NewDtoaState();
+    if (!dtoaState)
+        return false;
+
+    return true;
+}
+
+void
+PerThreadData::addToThreadList()
+{
+    // PerThreadData which are created/destroyed off the main thread do not
+    // show up in the runtime's thread list.
+    runtime_->assertValidThread();
+    runtime_->threadList.insertBack(this);
+}
+
+void
+PerThreadData::removeFromThreadList()
+{
+    runtime_->assertValidThread();
+    removeFrom(runtime_->threadList);
+}
+
+JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
+  : mainThread(this),
+    interrupt(0),
+#ifdef JS_THREADSAFE
+    operationCallbackLock(NULL),
+#ifdef DEBUG
+    operationCallbackOwner(NULL),
+#endif
+    exclusiveAccessLock(NULL),
+    exclusiveAccessOwner(NULL),
+    mainThreadHasExclusiveAccess(false),
+    numExclusiveThreads(0),
+#endif
+    atomsCompartment(NULL),
+    systemZone(NULL),
+    numCompartments(0),
+    localeCallbacks(NULL),
+    defaultLocale(NULL),
+    defaultVersion_(JSVERSION_DEFAULT),
+#ifdef JS_THREADSAFE
+    ownerThread_(NULL),
+#endif
+    tempLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
+    freeLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
+    execAlloc_(NULL),
+    bumpAlloc_(NULL),
+    ionRuntime_(NULL),
+    selfHostingGlobal_(NULL),
+    nativeStackBase(0),
+    nativeStackQuota(0),
+    interpreterFrames(NULL),
+    cxCallback(NULL),
+    destroyCompartmentCallback(NULL),
+    compartmentNameCallback(NULL),
+    activityCallback(NULL),
+    activityCallbackArg(NULL),
+#ifdef JS_THREADSAFE
+    requestDepth(0),
+# ifdef DEBUG
+    checkRequestDepth(0),
+# endif
+#endif
+    gcSystemAvailableChunkListHead(NULL),
+    gcUserAvailableChunkListHead(NULL),
+    gcBytes(0),
+    gcMaxBytes(0),
+    gcMaxMallocBytes(0),
+    gcNumArenasFreeCommitted(0),
+    gcMarker(this),
+    gcVerifyPreData(NULL),
+    gcVerifyPostData(NULL),
+    gcChunkAllocationSinceLastGC(false),
+    gcNextFullGCTime(0),
+    gcLastGCTime(0),
+    gcJitReleaseTime(0),
+    gcMode(JSGC_MODE_GLOBAL),
+    gcAllocationThreshold(30 * 1024 * 1024),
+    gcHighFrequencyGC(false),
+    gcHighFrequencyTimeThreshold(1000),
+    gcHighFrequencyLowLimitBytes(100 * 1024 * 1024),
+    gcHighFrequencyHighLimitBytes(500 * 1024 * 1024),
+    gcHighFrequencyHeapGrowthMax(3.0),
+    gcHighFrequencyHeapGrowthMin(1.5),
+    gcLowFrequencyHeapGrowth(1.5),
+    gcDynamicHeapGrowth(false),
+    gcDynamicMarkSlice(false),
+    gcDecommitThreshold(32 * 1024 * 1024),
+    gcShouldCleanUpEverything(false),
+    gcGrayBitsValid(false),
+    gcIsNeeded(0),
+    gcStats(thisFromCtor()),
+    gcNumber(0),
+    gcStartNumber(0),
+    gcIsFull(false),
+    gcTriggerReason(JS::gcreason::NO_REASON),
+    gcStrictCompartmentChecking(false),
+#ifdef DEBUG
+    gcDisableStrictProxyCheckingCount(0),
+#endif
+    gcIncrementalState(gc::NO_INCREMENTAL),
+    gcLastMarkSlice(false),
+    gcSweepOnBackgroundThread(false),
+    gcFoundBlackGrayEdges(false),
+    gcSweepingZones(NULL),
+    gcZoneGroupIndex(0),
+    gcZoneGroups(NULL),
+    gcCurrentZoneGroup(NULL),
+    gcSweepPhase(0),
+    gcSweepZone(NULL),
+    gcSweepKindIndex(0),
+    gcAbortSweepAfterCurrentGroup(false),
+    gcArenasAllocatedDuringSweep(NULL),
+#ifdef DEBUG
+    gcMarkingValidator(NULL),
+#endif
+    gcInterFrameGC(0),
+    gcSliceBudget(SliceBudget::Unlimited),
+    gcIncrementalEnabled(true),
+    gcGenerationalEnabled(true),
+    gcManipulatingDeadZones(false),
+    gcObjectsMarkedInDeadZones(0),
+    gcPoke(false),
+    heapState(Idle),
+#ifdef JSGC_GENERATIONAL
+    gcNursery(thisFromCtor()),
+    gcStoreBuffer(thisFromCtor(), gcNursery),
+#endif
+#ifdef JS_GC_ZEAL
+    gcZeal_(0),
+    gcZealFrequency(0),
+    gcNextScheduled(0),
+    gcDeterministicOnly(false),
+    gcIncrementalLimit(0),
+#endif
+    gcValidate(true),
+    gcFullCompartmentChecks(false),
+    gcCallback(NULL),
+    gcSliceCallback(NULL),
+    gcFinalizeCallback(NULL),
+    analysisPurgeCallback(NULL),
+    analysisPurgeTriggerBytes(0),
+    gcMallocBytes(0),
+    scriptAndCountsVector(NULL),
+    NaNValue(UndefinedValue()),
+    negativeInfinityValue(UndefinedValue()),
+    positiveInfinityValue(UndefinedValue()),
+    emptyString(NULL),
+    sourceHook(NULL),
+    debugMode(false),
+    spsProfiler(thisFromCtor()),
+    profilingScripts(false),
+    alwaysPreserveCode(false),
+    hadOutOfMemory(false),
+    data(NULL),
+    gcLock(NULL),
+    gcHelperThread(thisFromCtor()),
+#ifdef JS_THREADSAFE
+#ifdef JS_ION
+    workerThreadState(NULL),
+#endif
+    sourceCompressorThread(),
+#endif
+    defaultFreeOp_(thisFromCtor(), false),
+    debuggerMutations(0),
+    securityCallbacks(const_cast<JSSecurityCallbacks *>(&NullSecurityCallbacks)),
+    DOMcallbacks(NULL),
+    destroyPrincipals(NULL),
+    structuredCloneCallbacks(NULL),
+    telemetryCallback(NULL),
+    propertyRemovals(0),
+#if !ENABLE_INTL_API
+    thousandsSeparator(0),
+    decimalSeparator(0),
+    numGrouping(0),
+#endif
+    mathCache_(NULL),
+    trustedPrincipals_(NULL),
+    wrapObjectCallback(TransparentObjectWrapper),
+    sameCompartmentWrapObjectCallback(NULL),
+    preWrapObjectCallback(NULL),
+    preserveWrapperCallback(NULL),
+#ifdef DEBUG
+    noGCOrAllocationCheck(0),
+#endif
+    jitHardening(false),
+    jitSupportsFloatingPoint(false),
+    ionPcScriptCache(NULL),
+    threadPool(this),
+    ctypesActivityCallback(NULL),
+    parallelWarmup(0),
+    ionReturnOverride_(MagicValue(JS_ARG_POISON)),
+    useHelperThreads_(useHelperThreads),
+    requestedHelperThreadCount(-1)
+#ifdef DEBUG
+    , enteredPolicy(NULL)
+#endif
+{
+    liveRuntimesCount++;
+
+    /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
+    JS_INIT_CLIST(&onNewGlobalObjectWatchers);
+
+    PodZero(&debugHooks);
+    PodZero(&atomState);
+
+#if JS_STACK_GROWTH_DIRECTION > 0
+    nativeStackLimit = UINTPTR_MAX;
+#endif
+}
+
+static bool
+JitSupportsFloatingPoint()
+{
+#if defined(JS_ION)
+    if (!JSC::MacroAssembler::supportsFloatingPoint())
+        return false;
+
+#if defined(JS_ION) && WTF_ARM_ARCH_VERSION == 6
+    if (!js::ion::hasVFP())
+        return false;
+#endif
+
+    return true;
+#else
+    return false;
+#endif
+}
+
+bool
+JSRuntime::init(uint32_t maxbytes)
+{
+#ifdef JS_THREADSAFE
+    ownerThread_ = PR_GetCurrentThread();
+
+    operationCallbackLock = PR_NewLock();
+    if (!operationCallbackLock)
+        return false;
+
+    exclusiveAccessLock = PR_NewLock();
+    if (!exclusiveAccessLock)
+        return false;
+#endif
+
+    if (!mainThread.init())
+        return false;
+
+    js::TlsPerThreadData.set(&mainThread);
+    mainThread.addToThreadList();
+
+    if (!js_InitGC(this, maxbytes))
+        return false;
+
+    if (!gcMarker.init())
+        return false;
+
+    const char *size = getenv("JSGC_MARK_STACK_LIMIT");
+    if (size)
+        SetMarkStackLimit(this, atoi(size));
+
+    ScopedJSDeletePtr<Zone> atomsZone(new_<Zone>(this));
+    if (!atomsZone)
+        return false;
+
+    JS::CompartmentOptions options;
+    ScopedJSDeletePtr<JSCompartment> atomsCompartment(new_<JSCompartment>(atomsZone.get(), options));
+    if (!atomsCompartment || !atomsCompartment->init(NULL))
+        return false;
+
+    zones.append(atomsZone.get());
+    atomsZone->compartments.append(atomsCompartment.get());
+
+    atomsCompartment->isSystem = true;
+    atomsZone->isSystem = true;
+    atomsZone->setGCLastBytes(8192, GC_NORMAL);
+
+    atomsZone.forget();
+    this->atomsCompartment = atomsCompartment.forget();
+
+    if (!InitAtoms(this))
+        return false;
+
+    if (!InitRuntimeNumberState(this))
+        return false;
+
+    dateTimeInfo.updateTimeZoneAdjustment();
+
+    if (!scriptDataTable.init())
+        return false;
+
+    if (!threadPool.init())
+        return false;
+
+#ifdef JS_THREADSAFE
+    if (useHelperThreads() && !sourceCompressorThread.init())
+        return false;
+#endif
+
+    if (!evalCache.init())
+        return false;
+
+    nativeStackBase = GetNativeStackBase();
+
+    jitSupportsFloatingPoint = JitSupportsFloatingPoint();
+    return true;
+}
+
+JSRuntime::~JSRuntime()
+{
+    mainThread.removeFromThreadList();
+
+#ifdef JS_THREADSAFE
+# ifdef JS_ION
+    if (workerThreadState)
+        js_delete(workerThreadState);
+# endif
+    sourceCompressorThread.finish();
+
+    clearOwnerThread();
+
+    JS_ASSERT(!operationCallbackOwner);
+    if (operationCallbackLock)
+        PR_DestroyLock(operationCallbackLock);
+
+    JS_ASSERT(!exclusiveAccessOwner);
+    if (exclusiveAccessLock)
+        PR_DestroyLock(exclusiveAccessLock);
+#endif
+
+    /*
+     * Even though all objects in the compartment are dead, we may have keep
+     * some filenames around because of gcKeepAtoms.
+     */
+    FreeScriptData(this);
+
+#ifdef DEBUG
+    /* Don't hurt everyone in leaky ol' Mozilla with a fatal JS_ASSERT! */
+    if (hasContexts()) {
+        unsigned cxcount = 0;
+        for (ContextIter acx(this); !acx.done(); acx.next()) {
+            fprintf(stderr,
+"JS API usage error: found live context at %p\n",
+                    (void *) acx.get());
+            cxcount++;
+        }
+        fprintf(stderr,
+"JS API usage error: %u context%s left in runtime upon JS_DestroyRuntime.\n",
+                cxcount, (cxcount == 1) ? "" : "s");
+    }
+#endif
+
+#if !ENABLE_INTL_API
+    FinishRuntimeNumberState(this);
+#endif
+    FinishAtoms(this);
+
+    js_FinishGC(this);
+#ifdef JS_THREADSAFE
+    if (gcLock)
+        PR_DestroyLock(gcLock);
+#endif
+
+    js_delete(bumpAlloc_);
+    js_delete(mathCache_);
+#ifdef JS_ION
+    js_delete(ionRuntime_);
+#endif
+    js_delete(execAlloc_);  /* Delete after ionRuntime_. */
+
+    if (ionPcScriptCache)
+        js_delete(ionPcScriptCache);
+
+#ifdef JSGC_GENERATIONAL
+    gcStoreBuffer.disable();
+    gcNursery.disable();
+#endif
+
+    DebugOnly<size_t> oldCount = liveRuntimesCount--;
+    JS_ASSERT(oldCount > 0);
+}
+
+#ifdef JS_THREADSAFE
+void
+JSRuntime::setOwnerThread()
+{
+    JS_ASSERT(ownerThread_ == (void *)0xc1ea12);  /* "clear" */
+    JS_ASSERT(requestDepth == 0);
+    JS_ASSERT(js::TlsPerThreadData.get() == NULL);
+    ownerThread_ = PR_GetCurrentThread();
+    js::TlsPerThreadData.set(&mainThread);
+    nativeStackBase = GetNativeStackBase();
+    if (nativeStackQuota)
+        JS_SetNativeStackQuota(this, nativeStackQuota);
+#ifdef XP_MACOSX
+    asmJSMachExceptionHandler.setCurrentThread();
+#endif
+}
+
+void
+JSRuntime::clearOwnerThread()
+{
+    assertValidThread();
+    JS_ASSERT(requestDepth == 0);
+    ownerThread_ = (void *)0xc1ea12;  /* "clear" */
+    js::TlsPerThreadData.set(NULL);
+    nativeStackBase = 0;
+#if JS_STACK_GROWTH_DIRECTION > 0
+    mainThread.nativeStackLimit = UINTPTR_MAX;
+#else
+    mainThread.nativeStackLimit = 0;
+#endif
+#ifdef XP_MACOSX
+    asmJSMachExceptionHandler.clearCurrentThread();
+#endif
+}
+
+JS_FRIEND_API(void)
+JSRuntime::abortIfWrongThread() const
+{
+    if (ownerThread_ != PR_GetCurrentThread())
+        MOZ_CRASH();
+    if (!js::TlsPerThreadData.get()->associatedWith(this))
+        MOZ_CRASH();
+}
+
+#ifdef DEBUG
+JS_FRIEND_API(void)
+JSRuntime::assertValidThread() const
+{
+    JS_ASSERT(ownerThread_ == PR_GetCurrentThread());
+    JS_ASSERT(js::TlsPerThreadData.get()->associatedWith(this));
+}
+#endif  /* DEBUG */
+#endif  /* JS_THREADSAFE */
 
 void
 NewObjectCache::clearNurseryObjects(JSRuntime *rt)
 {
     for (unsigned i = 0; i < mozilla::ArrayLength(entries); ++i) {
         Entry &e = entries[i];
         JSObject *obj = reinterpret_cast<JSObject *>(&e.templateObject);
         if (IsInsideNursery(rt, e.key) ||
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -2,20 +2,22 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 vm_Runtime_h
 #define vm_Runtime_h
 
+#include "mozilla/Atomics.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/TemplateLib.h"
+#include "mozilla/ThreadLocal.h"
 
 #include <setjmp.h>
 #include <string.h>
 
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jsclist.h"
 #include "jsfriendapi.h"
@@ -37,16 +39,23 @@
 
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4100) /* Silence unreferenced formal parameter warnings */
 #pragma warning(push)
 #pragma warning(disable:4355) /* Silence warning about "this" used in base member initializer list */
 #endif
 
+namespace js {
+
+/* Thread Local Storage slot for storing the runtime for a thread. */
+extern mozilla::ThreadLocal<PerThreadData*> TlsPerThreadData;
+
+} // namespace js
+
 struct DtoaState;
 
 extern void
 js_ReportOutOfMemory(js::ThreadSafeContext *cx);
 
 extern void
 js_ReportAllocationOverflow(js::ThreadSafeContext *cx);
 
@@ -1416,17 +1425,23 @@ struct JSRuntime : public JS::shadow::Ru
     //
     // To allow the GetPropertyCacheT optimization, we allow the ability for
     // GetPropertyCache to override the return value at the top of the stack - the
     // value that will be temporarily corrupt. This special override value is set
     // only in callVM() targets that are about to return *and* have invalidated
     // their callee.
     js::Value            ionReturnOverride_;
 
+    static mozilla::Atomic<size_t> liveRuntimesCount;
+
   public:
+    static bool hasLiveRuntimes() {
+        return liveRuntimesCount > 0;
+    }
+
     bool hasIonReturnOverride() const {
         return !ionReturnOverride_.isMagic();
     }
     js::Value takeIonReturnOverride() {
         js::Value v = ionReturnOverride_;
         ionReturnOverride_ = js::MagicValue(JS_ARG_POISON);
         return v;
     }
@@ -1783,16 +1798,18 @@ public:
         return get();
     }
 
     PerThreadData *operator ->() const {
         return get();
     }
 };
 
+extern const JSSecurityCallbacks NullSecurityCallbacks;
+
 } /* namespace js */
 
 #ifdef _MSC_VER
 #pragma warning(pop)
 #pragma warning(pop)
 #endif
 
 #endif /* vm_Runtime_h */
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1414,17 +1414,16 @@ XPCJSRuntime::~XPCJSRuntime()
 #ifdef XPC_DUMP_AT_SHUTDOWN
         uint32_t count = mDetachedWrappedNativeProtoMap->Count();
         if (count)
             printf("deleting XPCJSRuntime with %d live detached XPCWrappedNativeProto\n", (int)count);
 #endif
         delete mDetachedWrappedNativeProtoMap;
     }
 
-    JS_ShutDown();
 #ifdef MOZ_ENABLE_PROFILER_SPS
     // Tell the profiler that the runtime is gone
     if (PseudoStack *stack = mozilla_get_pseudo_stack())
         stack->sampleRuntime(nullptr);
 #endif
 
 #ifdef DEBUG
     for (uint32_t i = 0; i < XPCCCX_STRING_CACHE_SIZE; ++i) {
@@ -2681,66 +2680,16 @@ SourceHook(JSContext *cx, JS::Handle<JSS
   if (NS_FAILED(rv)) {
     xpc::Throw(cx, rv);
     return false;
   }
 
   return true;
 }
 
-// |sSizeOfICU| can be accessed by multiple JSRuntimes, so it must be
-// thread-safe.
-static Atomic<size_t> sSizeOfICU;
-
-static int64_t
-GetICUSize()
-{
-    return sSizeOfICU;
-}
-
-NS_MEMORY_REPORTER_IMPLEMENT(ICU,
-    "explicit/icu",
-    KIND_HEAP,
-    UNITS_BYTES,
-    GetICUSize,
-    "Memory used by ICU, a Unicode and globalization support library."
-)
-
-NS_MEMORY_REPORTER_MALLOC_SIZEOF_ON_ALLOC_FUN(ICUMallocSizeOfOnAlloc)
-NS_MEMORY_REPORTER_MALLOC_SIZEOF_ON_FREE_FUN(ICUMallocSizeOfOnFree)
-
-static void *
-ICUAlloc(const void *, size_t size)
-{
-    void *p = malloc(size);
-    sSizeOfICU += ICUMallocSizeOfOnAlloc(p);
-    return p;
-}
-
-static void *
-ICURealloc(const void *, void *p, size_t size)
-{
-    sSizeOfICU -= ICUMallocSizeOfOnFree(p);
-    void *pnew = realloc(p, size);
-    if (pnew) {
-        sSizeOfICU += ICUMallocSizeOfOnAlloc(pnew);
-    } else {
-        // realloc failed;  undo the decrement from above
-        sSizeOfICU += ICUMallocSizeOfOnAlloc(p);
-    }
-    return pnew;
-}
-
-static void
-ICUFree(const void *, void *p)
-{
-    sSizeOfICU -= ICUMallocSizeOfOnFree(p);
-    free(p);
-}
-
 XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
    : CycleCollectedJSRuntime(32L * 1024L * 1024L, JS_USE_HELPER_THREADS, true),
    mJSContextStack(new XPCJSContextStack()),
    mCallContext(nullptr),
    mAutoRoots(nullptr),
    mResolveName(JSID_VOID),
    mResolvingWrapper(nullptr),
    mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_SIZE)),
@@ -2833,30 +2782,26 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
     // function compiled with LAZY_SOURCE, it calls SourceHook to load it.
     ///
     // Note we do have to retain the source code in memory for scripts compiled in
     // compileAndGo mode and compiled function bodies (from
     // JS_CompileFunction*). In practice, this means content scripts and event
     // handlers.
     JS_SetSourceHook(runtime, SourceHook);
 
-    if (!JS_SetICUMemoryFunctions(ICUAlloc, ICURealloc, ICUFree))
-        NS_RUNTIMEABORT("JS_SetICUMemoryFunctions failed.");
-
     // Set up locale information and callbacks for the newly-created runtime so
     // that the various toLocaleString() methods, localeCompare(), and other
     // internationalization APIs work as desired.
     if (!xpc_LocalizeRuntime(runtime))
         NS_RUNTIMEABORT("xpc_LocalizeRuntime failed.");
 
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSGCHeap));
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSSystemCompartmentCount));
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSUserCompartmentCount));
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(JSMainRuntimeTemporaryPeak));
-    NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(ICU));
     NS_RegisterMemoryMultiReporter(new JSCompartmentsMultiReporter);
 
     // Install a JavaScript 'debugger' keyword handler in debug builds only
 #ifdef DEBUG
     if (!JS_GetGlobalDebugHooks(runtime)->debuggerHandler)
         xpc_InstallJSDebuggerKeywordHandler(runtime);
 #endif
 }
--- a/layout/base/PositionedEventTargeting.cpp
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -244,20 +244,20 @@ GetClosest(nsIFrame* aRoot, const nsPoin
 }
 
 nsIFrame*
 FindFrameTargetedByInputEvent(const nsGUIEvent *aEvent,
                               nsIFrame* aRootFrame,
                               const nsPoint& aPointRelativeToRootFrame,
                               uint32_t aFlags)
 {
-  bool ignoreRootScrollFrame = (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME) != 0;
+  uint32_t flags = (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME) ?
+     nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0;
   nsIFrame* target =
-    nsLayoutUtils::GetFrameForPoint(aRootFrame, aPointRelativeToRootFrame,
-                                    false, ignoreRootScrollFrame);
+    nsLayoutUtils::GetFrameForPoint(aRootFrame, aPointRelativeToRootFrame, flags);
 
   const EventRadiusPrefs* prefs = GetPrefsFor(aEvent->eventStructType);
   if (!prefs || !prefs->mEnabled || (target && IsElementClickable(target))) {
     return target;
   }
 
   // Do not modify targeting for actual mouse hardware; only for mouse
   // events generated by touch-screen hardware.
@@ -265,18 +265,17 @@ FindFrameTargetedByInputEvent(const nsGU
       prefs->mTouchOnly &&
       static_cast<const nsMouseEvent*>(aEvent)->inputSource !=
           nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) {
       return target;
   }
 
   nsRect targetRect = GetTargetRect(aRootFrame, aPointRelativeToRootFrame, prefs);
   nsAutoTArray<nsIFrame*,8> candidates;
-  nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect, candidates,
-                                                false, ignoreRootScrollFrame);
+  nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect, candidates, flags);
   if (NS_FAILED(rv)) {
     return target;
   }
 
   // If the exact target is non-null, only consider candidate targets in the same
   // document as the exact target. Otherwise, if an ancestor document has
   // a mouse event handler for example, targets that are !IsElementClickable can
   // never be targeted --- something nsSubDocumentFrame in an ancestor document
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1797,46 +1797,42 @@ nsLayoutUtils::GetRemoteContentIds(nsIFr
   nsDisplayItem::HitTestState hitTestState(&aOutIDs);
   list.HitTest(&builder, aTarget, &hitTestState, &outFrames);
   list.DeleteAll();
 
   return NS_OK;
 }
 
 nsIFrame*
-nsLayoutUtils::GetFrameForPoint(nsIFrame* aFrame, nsPoint aPt,
-                                bool aShouldIgnoreSuppression,
-                                bool aIgnoreRootScrollFrame)
+nsLayoutUtils::GetFrameForPoint(nsIFrame* aFrame, nsPoint aPt, uint32_t aFlags)
 {
   PROFILER_LABEL("nsLayoutUtils", "GetFrameForPoint");
   nsresult rv;
   nsAutoTArray<nsIFrame*,8> outFrames;
-  rv = GetFramesForArea(aFrame, nsRect(aPt, nsSize(1, 1)), outFrames,
-                        aShouldIgnoreSuppression, aIgnoreRootScrollFrame);
+  rv = GetFramesForArea(aFrame, nsRect(aPt, nsSize(1, 1)), outFrames, aFlags);
   NS_ENSURE_SUCCESS(rv, nullptr);
   return outFrames.Length() ? outFrames.ElementAt(0) : nullptr;
 }
 
 nsresult
 nsLayoutUtils::GetFramesForArea(nsIFrame* aFrame, const nsRect& aRect,
                                 nsTArray<nsIFrame*> &aOutFrames,
-                                bool aShouldIgnoreSuppression,
-                                bool aIgnoreRootScrollFrame)
+                                uint32_t aFlags)
 {
   PROFILER_LABEL("nsLayoutUtils","GetFramesForArea");
   nsDisplayListBuilder builder(aFrame, nsDisplayListBuilder::EVENT_DELIVERY,
-		                       false);
+                               false);
   nsDisplayList list;
   nsRect target(aRect);
 
-  if (aShouldIgnoreSuppression) {
+  if (aFlags & IGNORE_PAINT_SUPPRESSION) {
     builder.IgnorePaintSuppression();
   }
 
-  if (aIgnoreRootScrollFrame) {
+  if (aFlags & IGNORE_ROOT_SCROLL_FRAME) {
     nsIFrame* rootScrollFrame =
       aFrame->PresContext()->PresShell()->GetRootScrollFrame();
     if (rootScrollFrame) {
       builder.SetIgnoreScrollFrame(rootScrollFrame);
     }
   }
 
   builder.EnterPresShell(aFrame, target);
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -521,45 +521,50 @@ public:
    * @param aIgnoreRootScrollFrame a boolean to control if the display list
    *        builder should ignore the root scroll frame
    */
   static nsresult GetRemoteContentIds(nsIFrame* aFrame,
                                      const nsRect& aTarget,
                                      nsTArray<ViewID> &aOutIDs,
                                      bool aIgnoreRootScrollFrame);
 
+  enum FrameForPointFlags {
+    /**
+     * When set, paint suppression is ignored, so we'll return non-root page
+     * elements even if paint suppression is stopping them from painting.
+     */
+    IGNORE_PAINT_SUPPRESSION = 0x01,
+    /**
+     * When set, clipping due to the root scroll frame (and any other viewport-
+     * related clipping) is ignored.
+     */
+    IGNORE_ROOT_SCROLL_FRAME = 0x02
+  };
+
   /**
    * Given aFrame, the root frame of a stacking context, find its descendant
    * frame under the point aPt that receives a mouse event at that location,
    * or nullptr if there is no such frame.
    * @param aPt the point, relative to the frame origin
-   * @param aShouldIgnoreSuppression a boolean to control if the display
-   * list builder should ignore paint suppression or not
-   * @param aIgnoreRootScrollFrame whether or not the display list builder
-   * should ignore the root scroll frame.
+   * @param aFlags some combination of FrameForPointFlags
    */
   static nsIFrame* GetFrameForPoint(nsIFrame* aFrame, nsPoint aPt,
-                                    bool aShouldIgnoreSuppression = false,
-                                    bool aIgnoreRootScrollFrame = false);
+                                    uint32_t aFlags = 0);
 
   /**
    * Given aFrame, the root frame of a stacking context, find all descendant
    * frames under the area of a rectangle that receives a mouse event,
    * or nullptr if there is no such frame.
    * @param aRect the rect, relative to the frame origin
    * @param aOutFrames an array to add all the frames found
-   * @param aShouldIgnoreSuppression a boolean to control if the display
-   * list builder should ignore paint suppression or not
-   * @param aIgnoreRootScrollFrame whether or not the display list builder
-   * should ignore the root scroll frame.
+   * @param aFlags some combination of FrameForPointFlags
    */
   static nsresult GetFramesForArea(nsIFrame* aFrame, const nsRect& aRect,
                                    nsTArray<nsIFrame*> &aOutFrames,
-                                   bool aShouldIgnoreSuppression = false,
-                                   bool aIgnoreRootScrollFrame = false);
+                                   uint32_t aFlags = 0);
 
   /**
    * Transform aRect relative to aAncestor down to the coordinate system of
    * aFrame. Computes the bounding-box of the true quadrilateral.
    */
   static nsRect TransformAncestorRectToFrame(nsIFrame* aFrame,
                                              const nsRect& aRect,
                                              const nsIFrame* aAncestor);
--- a/mfbt/MathAlgorithms.h
+++ b/mfbt/MathAlgorithms.h
@@ -409,21 +409,22 @@ FloorLog2(const T t)
 /** A FloorLog2 variant that accepts only size_t. */
 inline uint_fast8_t
 FloorLog2Size(size_t n)
 {
   return FloorLog2(n);
 }
 
 /*
- * Round x up to the nearest power of 2.  This function assumes that the most
- * significant bit of x is not set, which would lead to overflow.
+ * Compute the smallest power of 2 greater than or equal to |x|.  |x| must not
+ * be so great that the computed value would overflow |size_t|.
  */
 inline size_t
 RoundUpPow2(size_t x)
 {
-  MOZ_ASSERT(~x > x, "can't round up -- will overflow!");
+  MOZ_ASSERT(x <= (size_t(1) << (sizeof(size_t) * CHAR_BIT - 1)),
+             "can't round up -- will overflow!");
   return size_t(1) << CeilingLog2(x);
 }
 
 } /* namespace mozilla */
 
 #endif /* mozilla_MathAlgorithms_h */
--- a/mfbt/Vector.h
+++ b/mfbt/Vector.h
@@ -16,16 +16,18 @@
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Move.h"
 #include "mozilla/NullPtr.h"
 #include "mozilla/ReentrancyGuard.h"
 #include "mozilla/TemplateLib.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/Util.h"
 
+#include <new> // for placement new
+
 /* Silence dire "bugs in previous versions of MSVC have been fixed" warnings */
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable:4345)
 #endif
 
 namespace mozilla {
 
--- a/mfbt/tests/TestCeilingFloor.cpp
+++ b/mfbt/tests/TestCeilingFloor.cpp
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/MathAlgorithms.h"
 
 using mozilla::CeilingLog2;
 using mozilla::FloorLog2;
+using mozilla::RoundUpPow2;
 
 static void
 TestCeiling()
 {
   for (uint32_t i = 0; i <= 1; i++)
     MOZ_ASSERT(CeilingLog2(i) == 0);
 
   for (uint32_t i = 2; i <= 2; i++)
@@ -41,14 +42,44 @@ TestFloor()
 
   for (uint32_t i = 8; i <= 15; i++)
     MOZ_ASSERT(FloorLog2(i) == 3);
 
   for (uint32_t i = 16; i <= 31; i++)
     MOZ_ASSERT(FloorLog2(i) == 4);
 }
 
+static void
+TestRoundUpPow2()
+{
+  MOZ_ASSERT(RoundUpPow2(0) == 1);
+  MOZ_ASSERT(RoundUpPow2(1) == 1);
+  MOZ_ASSERT(RoundUpPow2(2) == 2);
+  MOZ_ASSERT(RoundUpPow2(3) == 4);
+  MOZ_ASSERT(RoundUpPow2(4) == 4);
+  MOZ_ASSERT(RoundUpPow2(5) == 8);
+  MOZ_ASSERT(RoundUpPow2(6) == 8);
+  MOZ_ASSERT(RoundUpPow2(7) == 8);
+  MOZ_ASSERT(RoundUpPow2(8) == 8);
+  MOZ_ASSERT(RoundUpPow2(9) == 16);
+
+  MOZ_ASSERT(RoundUpPow2(15) == 16);
+  MOZ_ASSERT(RoundUpPow2(16) == 16);
+  MOZ_ASSERT(RoundUpPow2(17) == 32);
+
+  MOZ_ASSERT(RoundUpPow2(31) == 32);
+  MOZ_ASSERT(RoundUpPow2(32) == 32);
+  MOZ_ASSERT(RoundUpPow2(33) == 64);
+
+  size_t MaxPow2 = size_t(1) << (sizeof(size_t) * CHAR_BIT - 1);
+  MOZ_ASSERT(RoundUpPow2(MaxPow2 - 1) == MaxPow2);
+  MOZ_ASSERT(RoundUpPow2(MaxPow2) == MaxPow2);
+  // not valid to round up when past the max power of two
+}
+
 int main()
 {
   TestCeiling();
   TestFloor();
+
+  TestRoundUpPow2();
   return 0;
 }
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -708,16 +708,21 @@ abstract public class BrowserApp extends
     public void onDestroy() {
         if (mPrefObserverId != null) {
             PrefsHelper.removeObserver(mPrefObserverId);
             mPrefObserverId = null;
         }
         if (mBrowserToolbar != null)
             mBrowserToolbar.onDestroy();
 
+        if (mFindInPageBar != null) {
+            mFindInPageBar.onDestroy();
+            mFindInPageBar = null;
+        }
+
         if (mSharedPreferencesHelper != null) {
             mSharedPreferencesHelper.uninit();
             mSharedPreferencesHelper = null;
         }
 
         if (mOrderedBroadcastHelper != null) {
             mOrderedBroadcastHelper.uninit();
             mOrderedBroadcastHelper = null;
--- a/mobile/android/base/FindInPageBar.java
+++ b/mobile/android/base/FindInPageBar.java
@@ -1,26 +1,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import org.json.JSONObject;
+
 import android.content.Context;
 import android.text.Editable;
+import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.LinearLayout;
 
-public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnClickListener {
-    private static final String LOGTAG = "GeckoFindInPagePopup";
+public class FindInPageBar extends LinearLayout implements TextWatcher, View.OnClickListener, GeckoEventListener  {
+    private static final String REQUEST_ID = "FindInPageBar";
 
     private final Context mContext;
     private CustomEditText mFindText;
     private boolean mInflated = false;
 
     public FindInPageBar(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
@@ -48,53 +54,45 @@ public class FindInPageBar extends Linea
                     hide();
                     return true;
                 }
                 return false;
             }
         });
 
         mInflated = true;
+        GeckoAppShell.getEventDispatcher().registerEventListener("SelectedText:Data", this);
     }
 
     public void show() {
         if (!mInflated)
             inflateContent();
 
         setVisibility(VISIBLE);
         mFindText.requestFocus();
 
-        // Show the virtual keyboard.
-        if (mFindText.hasWindowFocus()) {
-            getInputMethodManager(mFindText).showSoftInput(mFindText, 0);
-        } else {
-            // showSoftInput won't work until after the window is focused.
-            mFindText.setOnWindowFocusChangeListener(new CustomEditText.OnWindowFocusChangeListener() {
-                @Override
-                public void onWindowFocusChanged(boolean hasFocus) {
-                   if (!hasFocus)
-                       return;
-                   mFindText.setOnWindowFocusChangeListener(null);
-                   getInputMethodManager(mFindText).showSoftInput(mFindText, 0);
-               }
-            });
-        }
+        // handleMessage() receives response message and determines initial state of softInput
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SelectedText:Get", REQUEST_ID));
     }
 
     public void hide() {
         setVisibility(GONE);
         getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Closed", null));
     }
 
     private InputMethodManager getInputMethodManager(View view) {
         Context context = view.getContext();
         return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
      }
 
+    public void onDestroy() {
+        GeckoAppShell.getEventDispatcher().unregisterEventListener("SelectedText:Data", this);
+    }
+
     // TextWatcher implementation
 
     @Override
     public void afterTextChanged(Editable s) {
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Find", s.toString()));
     }
 
     @Override
@@ -120,9 +118,49 @@ public class FindInPageBar extends Linea
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("FindInPage:Next", mFindText.getText().toString()));
                 getInputMethodManager(mFindText).hideSoftInputFromWindow(mFindText.getWindowToken(), 0);
                 break;
             case R.id.find_close:
                 hide();
                 break;
         }
     }
+
+    // GeckoEventListener implementation
+
+    @Override
+    public void handleMessage(String event, JSONObject message) {
+        if (!event.equals("SelectedText:Data") || !REQUEST_ID.equals(message.optString("requestId"))) {
+            return;
+        }
+
+        final String text = message.optString("text");
+
+        // Populate an initial find string, virtual keyboard not required.
+        if (!TextUtils.isEmpty(text)) {
+            // Populate initial selection
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    mFindText.setText(text);
+                }
+            });
+            return;
+        }
+
+        // Show the virtual keyboard.
+        if (mFindText.hasWindowFocus()) {
+            getInputMethodManager(mFindText).showSoftInput(mFindText, 0);
+        } else {
+            // showSoftInput won't work until after the window is focused.
+            mFindText.setOnWindowFocusChangeListener(new CustomEditText.OnWindowFocusChangeListener() {
+                @Override
+                public void onWindowFocusChanged(boolean hasFocus) {
+                    if (!hasFocus)
+                        return;
+
+                    mFindText.setOnWindowFocusChangeListener(null);
+                    getInputMethodManager(mFindText).showSoftInput(mFindText, 0);
+               }
+            });
+        }
+    }
 }
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -232,17 +232,20 @@ var SelectionHandler = {
 
   _getSelection: function sh_getSelection() {
     if (this._targetElement instanceof Ci.nsIDOMNSEditableElement)
       return this._targetElement.QueryInterface(Ci.nsIDOMNSEditableElement).editor.selection;
     else
       return this._contentWindow.getSelection();
   },
 
-  _getSelectedText: function sh_getSelectedText() {
+  getSelectedText: function sh_getSelectedText() {
+    if (!this._contentWindow)
+      return "";
+
     let selection = this._getSelection();
     if (selection)
       return selection.toString().trim();
     return "";
   },
 
   _getSelectionController: function sh_getSelectionController() {
     if (this._targetElement instanceof Ci.nsIDOMNSEditableElement)
@@ -368,27 +371,27 @@ var SelectionHandler = {
       aY -= 1;
     }
 
     this._domWinUtils.sendMouseEventToWindow("mousedown", aX, aY, 0, 0, useShift ? Ci.nsIDOMNSEvent.SHIFT_MASK : 0, true);
     this._domWinUtils.sendMouseEventToWindow("mouseup", aX, aY, 0, 0, useShift ? Ci.nsIDOMNSEvent.SHIFT_MASK : 0, true);
   },
 
   copySelection: function sh_copySelection() {
-    let selectedText = this._getSelectedText();
+    let selectedText = this.getSelectedText();
     if (selectedText.length) {
       let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
       clipboard.copyString(selectedText, this._contentWindow.document);
       NativeWindow.toast.show(Strings.browser.GetStringFromName("selectionHelper.textCopied"), "short");
     }
     this._closeSelection();
   },
 
   shareSelection: function sh_shareSelection() {
-    let selectedText = this._getSelectedText();
+    let selectedText = this.getSelectedText();
     if (selectedText.length) {
       sendMessageToJava({
         type: "Share:Text",
         text: selectedText
       });
     }
     this._closeSelection();
   },
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -290,16 +290,17 @@ var BrowserApp = {
     Services.obs.addObserver(this, "FullScreen:Exit", false);
     Services.obs.addObserver(this, "Viewport:Change", false);
     Services.obs.addObserver(this, "Viewport:Flush", false);
     Services.obs.addObserver(this, "Viewport:FixedMarginsChanged", false);
     Services.obs.addObserver(this, "Passwords:Init", false);
     Services.obs.addObserver(this, "FormHistory:Init", false);
     Services.obs.addObserver(this, "gather-telemetry", false);
     Services.obs.addObserver(this, "keyword-search", false);
+    Services.obs.addObserver(this, "SelectedText:Get", false);
 
     Services.obs.addObserver(this, "sessionstore-state-purge-complete", false);
 
     function showFullScreenWarning() {
       NativeWindow.toast.show(Strings.browser.GetStringFromName("alertFullScreenToast"), "short");
     }
 
     window.addEventListener("fullscreen", function() {
@@ -1437,16 +1438,24 @@ var BrowserApp = {
         let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
         sendMessageToJava({
           type: "Search:Keyword",
           identifier: engine.identifier,
           name: engine.name,
         });
         break;
 
+      case "SelectedText:Get":
+        sendMessageToJava({
+          type: "SelectedText:Data",
+          requestId: aData,
+          text: SelectionHandler.getSelectedText()
+        });
+        break;
+
       case "Browser:Quit":
         this.quit();
         break;
 
       case "SaveAs:PDF":
         this.saveAsPDF(browser);
         break;
 
new file mode 100644
--- /dev/null
+++ b/mozglue/build/AsanOptions.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+
+// When running with AddressSanitizer, we need to explicitely set some
+// options specific to our codebase to prevent errors during runtime.
+// 
+// Currently, these are:
+//
+//   allow_user_segv_handler=1 - Tell ASan to allow our code to use its
+//   own SIGSEGV handlers. This is required by ASM.js internally.
+//
+//   alloc_dealloc_mismatch=0 - Disable alloc-dealloc mismatch checking
+//   in ASan. This is required because we define our own new/delete
+//   operators that are backed by malloc/free. If one of them gets inlined
+//   while the other doesn't, ASan will report false positives.
+//
+extern "C" MOZ_ASAN_BLACKLIST
+const char* __asan_default_options() {
+    return "allow_user_segv_handler=1:alloc_dealloc_mismatch=0";
+}
--- a/mozglue/build/moz.build
+++ b/mozglue/build/moz.build
@@ -40,12 +40,16 @@ if CONFIG['CPU_ARCH'].startswith('x86'):
         'SSE.cpp',
     ]
 
 if CONFIG['CPU_ARCH'] == 'arm':
     CPP_SOURCES += [
         'arm.cpp',
     ]
 
+if CONFIG['MOZ_ASAN']:
+    CPP_SOURCES += [
+        'AsanOptions.cpp',
+    ]
 
 
 LIBRARY_NAME = 'mozglue'
 
--- a/testing/mochitest/b2g.json
+++ b/testing/mochitest/b2g.json
@@ -238,20 +238,16 @@
     "dom/browser-element/mochitest/test_browserElement_inproc_AppFramePermission.html":"",
     "dom/browser-element/mochitest/test_browserElement_inproc_AppWindowNamespace.html":"",
     "dom/browser-element/mochitest/test_browserElement_inproc_Auth.html":"",
     "dom/browser-element/mochitest/test_browserElement_inproc_BrowserWindowNamespace.html":"",
     "dom/browser-element/mochitest/test_browserElement_inproc_CloseApp.html":"",
     "dom/browser-element/mochitest/test_browserElement_inproc_CloseFromOpener.html":"",
     "dom/browser-element/":"",
 
-    "dom/devicestorage/test/test_basic.html":"",
-    "dom/devicestorage/test/test_lastModificationFilter.html":"",
-    "dom/devicestorage/test/test_overwrite.html":"",
-    "dom/devicestorage/test/test_stat.html":"",
     "dom/file/test/test_append_read_data.html":"",
     "dom/file/test/test_archivereader.html":"",
     "dom/file/test/test_archivereader_nonUnicode.html":"",
     "dom/file/test/test_archivereader_zip_in_zip.html":"",
     "dom/file/test/test_location.html":"",
     "dom/file/test/test_lockedfile_lifetimes.html":"",
     "dom/file/test/test_lockedfile_lifetimes_nested.html":"",
     "dom/file/test/test_lockedfile_ordering.html":"",
@@ -359,19 +355,16 @@
     "dom/tests/mochitest/sessionstorage/test_cookieSession.html":"",
     "dom/tests/mochitest/sessionstorage/test_sessionStorageBase.html":"",
     "dom/tests/mochitest/sessionstorage/test_sessionStorageBaseSessionOnly.html":"",
     "dom/tests/mochitest/sessionstorage/test_sessionStorageClone.html":"",
     "dom/tests/mochitest/sessionstorage/test_sessionStorageHttpHttps.html":"",
     "dom/tests/mochitest/sessionstorage/test_sessionStorageReplace.html":"",
     "dom/tests/mochitest/webapps/test_bug_779982.html":"",
     "dom/tests/mochitest/whatwg/test_postMessage_closed.html":"",
-    "dom/devicestorage/test/test_available.html":"",
-    "dom/devicestorage/test/test_freeSpace.html":"",
-    "dom/devicestorage/test/test_usedSpace.html":"",
     "dom/workers/test/test_suspend.html":"",
     "dom/workers/test/test_csp.html":"",
     "dom/workers/test/test_workersDisabled.html":"",
     "dom/workers/test/test_xhr_parameters.html":"",
     "dom/workers/test/test_xhr_system.html":"",
     "dom/workers/test/test_closeOnGC.html": "Stack scanner keeps things alive",
     "dom/workers/test/test_errorPropagation.html":"",
     "dom/workers/test/test_errorwarning.html":"",
--- a/xpcom/build/nsXPComInit.cpp
+++ b/xpcom/build/nsXPComInit.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim:set ts=4 sw=4 sts=4 ci et: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "base/basictypes.h"
 
+#include "mozilla/Atomics.h"
 #include "mozilla/Poison.h"
 #include "mozilla/XPCOM.h"
 #include "nsXULAppAPI.h"
 
 #include "nsXPCOMPrivate.h"
 #include "nsXPCOMCIDInternal.h"
 
 #include "prlink.h"
@@ -124,16 +125,18 @@ extern nsresult nsStringInputStreamConst
 #include "mozilla/ClearOnShutdown.h"
 
 #ifdef MOZ_VISUAL_EVENT_TRACER
 #include "mozilla/VisualEventTracer.h"
 #endif
 
 #include "GeckoProfiler.h"
 
+#include "jsapi.h"
+
 using namespace mozilla;
 using base::AtExitManager;
 using mozilla::ipc::BrowserProcessSubThread;
 #ifdef MOZ_VISUAL_EVENT_TRACER
 using mozilla::eventtracer::VisualEventTracer;
 #endif
 
 namespace {
@@ -327,16 +330,62 @@ NS_GetTraceRefcnt(nsITraceRefcnt** resul
 
 EXPORT_XPCOM_API(nsresult)
 NS_InitXPCOM(nsIServiceManager* *result,
                              nsIFile* binDirectory)
 {
     return NS_InitXPCOM2(result, binDirectory, nullptr);
 }
 
+// |sSizeOfICU| can be accessed by multiple JSRuntimes, so it must be
+// thread-safe.
+static Atomic<size_t> sSizeOfICU;
+
+static int64_t
+GetICUSize()
+{
+    return sSizeOfICU;
+}
+
+NS_MEMORY_REPORTER_IMPLEMENT(ICU,
+    "explicit/icu",
+    KIND_HEAP,
+    UNITS_BYTES,
+    GetICUSize,
+    "Memory used by ICU, a Unicode and globalization support library."
+)
+
+NS_MEMORY_REPORTER_MALLOC_SIZEOF_ON_ALLOC_FUN(ICUMallocSizeOfOnAlloc)
+NS_MEMORY_REPORTER_MALLOC_SIZEOF_ON_FREE_FUN(ICUMallocSizeOfOnFree)
+
+static void*
+ICUAlloc(const void*, size_t size)
+{
+    void* p = malloc(size);
+    sSizeOfICU += ICUMallocSizeOfOnAlloc(p);
+    return p;
+}
+
+static void*
+ICURealloc(const void*, void* p, size_t size)
+{
+    size_t delta = 0 - ICUMallocSizeOfOnFree(p);
+    void* pnew = realloc(p, size);
+    delta += pnew ? ICUMallocSizeOfOnAlloc(pnew) : ICUMallocSizeOfOnAlloc(p);
+    sSizeOfICU += delta;
+    return pnew;
+}
+
+static void
+ICUFree(const void*, void* p)
+{
+    sSizeOfICU -= ICUMallocSizeOfOnFree(p);
+    free(p);
+}
+
 EXPORT_XPCOM_API(nsresult)
 NS_InitXPCOM2(nsIServiceManager* *result,
               nsIFile* binDirectory,
               nsIDirectoryServiceProvider* appFileLocationProvider)
 {
     mozPoisonValueInit();
 
     char aLocal;
@@ -466,16 +515,31 @@ NS_InitXPCOM2(nsIServiceManager* *result
     if (!nsCycleCollector_init()) {
         return NS_ERROR_UNEXPECTED;
     }
 
     // And start it up for this thread too.
     rv = nsCycleCollector_startup(CCSingleThread);
     if (NS_FAILED(rv)) return rv;
 
+    // Register ICU memory functions.  This really shouldn't be necessary: the
+    // JS engine should do this on its own inside JS_Init, and memory-reporting
+    // code should call a JSAPI function to observe ICU memory usage.  But we
+    // can't define the alloc/free functions in the JS engine, because it can't
+    // depend on the XPCOM-based memory reporting goop.  So for now, we have
+    // this oddness.
+    if (!JS_SetICUMemoryFunctions(ICUAlloc, ICURealloc, ICUFree)) {
+        NS_RUNTIMEABORT("JS_SetICUMemoryFunctions failed.");
+    }
+
+    // Initialize the JS engine.
+    if (!JS_Init()) {
+        NS_RUNTIMEABORT("JS_Init failed");
+    }
+
     rv = nsComponentManagerImpl::gComponentManager->Init();
     if (NS_FAILED(rv))
     {
         NS_RELEASE(nsComponentManagerImpl::gComponentManager);
         return rv;
     }
 
     if (result) {
@@ -503,16 +567,20 @@ NS_InitXPCOM2(nsIServiceManager* *result
                                   nullptr,
                                   NS_XPCOM_STARTUP_OBSERVER_ID);
 #ifdef XP_WIN
     CreateAnonTempFileRemover();
 #endif
 
     mozilla::MapsMemoryReporter::Init();
 
+    // The memory reporter manager is up and running -- register a reporter for
+    // ICU's memory usage.
+    NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(ICU));
+
     mozilla::Telemetry::Init();
 
     mozilla::HangMonitor::Startup();
 
 #ifdef MOZ_VISUAL_EVENT_TRACER
     mozilla::eventtracer::Init();
 #endif
 
@@ -695,18 +763,22 @@ ShutdownXPCOM(nsIServiceManager* servMgr
     NS_ShutdownNativeCharsetUtils();
 #endif
 
     // Shutdown xpcom. This will release all loaders and cause others holding
     // a refcount to the component manager to release it.
     if (nsComponentManagerImpl::gComponentManager) {
         rv = (nsComponentManagerImpl::gComponentManager)->Shutdown();
         NS_ASSERTION(NS_SUCCEEDED(rv), "Component Manager shutdown failed.");
-    } else
+    } else {
         NS_WARNING("Component Manager was never created ...");
+    }
+
+    // Shut down the JS engine.
+    JS_ShutDown();
 
     // Release our own singletons
     // Do this _after_ shutting down the component manager, because the
     // JS component loader will use XPConnect to call nsIModule::canUnload,
     // and that will spin up the InterfaceInfoManager again -- bad mojo
     XPTInterfaceInfoManager::FreeInterfaceInfoManager();
 
     // Finally, release the component manager last because it unloads the