Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 05 Jun 2017 17:34:09 -0700
changeset 412885 2c6289f56812c30254acfdddabcfec1e149c0336
parent 412857 3cfe81678881ada0245215c8863bc9722925bb44 (current diff)
parent 412884 1032d46e20fdf193727be2d3cbd91a71944be35e (diff)
child 412910 5e0d7d672b014f9d24e93c1fee9e56d5cc295a04
child 412940 2128f5860eb4774a5e3ab85eda1a0383e652afe0
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.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 inbound to central, a=merge MozReview-Commit-ID: Cb3YlCdVhQr
mobile/android/chrome/content/browser.js
testing/web-platform/meta/intersection-observer/cross-origin-iframe.html.ini
testing/web-platform/meta/intersection-observer/disconnect.html.ini
testing/web-platform/meta/intersection-observer/observer-without-js-reference.html.ini
testing/web-platform/meta/intersection-observer/timestamp.html.ini
testing/web-platform/meta/intersection-observer/zero-area-element-hidden.html.ini
testing/web-platform/meta/intersection-observer/zero-area-element-visible.html.ini
--- a/devtools/client/inspector/grids/components/GridOutline.js
+++ b/devtools/client/inspector/grids/components/GridOutline.js
@@ -163,40 +163,39 @@ module.exports = createClass({
 
     if (fragmentIndex && rowNumber && columnNumber) {
       onShowGridCellHighlight(grids[id].nodeFront, color, fragmentIndex,
         rowNumber, columnNumber);
     }
   },
 
   /**
-    * Displays a message text "Cannot show outline for this grid".
-    *
-    */
+   * Displays a message text "Cannot show outline for this grid".
+   */
   renderCannotShowOutlineText() {
     return dom.div(
       {
         className: "grid-outline-text"
       },
       dom.span(
         {
           className: "grid-outline-text-icon",
           title: getStr("layout.cannotShowGridOutline.title")
         }
       ),
       getStr("layout.cannotShowGridOutline")
     );
   },
 
   /**
-    * Renders the grid outline for the given grid container object.
-    *
-    * @param  {Object} grid
-    *         A single grid container in the document.
-    */
+   * Renders the grid outline for the given grid container object.
+   *
+   * @param  {Object} grid
+   *         A single grid container in the document.
+   */
   renderGrid(grid) {
     // TODO: We are drawing the first fragment since only one is currently being stored.
     // In the future we will need to iterate over all fragments of a grid.
     let gridFragmentIndex = 0;
     const { id, color, gridFragments } = grid;
     const { rows, cols, areas } = gridFragments[gridFragmentIndex];
 
     const numberOfColumns = cols.lines.length - 1;
--- a/devtools/client/locales/en-US/layout.properties
+++ b/devtools/client/locales/en-US/layout.properties
@@ -40,9 +40,9 @@ layout.noGrids=No grids
 layout.overlayMultipleGrids=Overlay Multiple Grids
 
 # LOCALIZATION NOTE (layout.overlayGrid): Alternate header for the list of grid container
 # elements if only one item can be selected.
 layout.overlayGrid=Overlay Grid
 
 # LOCALIZATION NOTE (layout.rowColumnPositions): The row and column position of a grid
 # cell shown in the grid cell infobar when hovering over the CSS grid outline.
-layout.rowColumnPositions=Row %S \/ Column %S
+layout.rowColumnPositions=Row %S / Column %S
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -227,17 +227,17 @@
 }
 
 :-moz-native-anonymous .css-grid-regions {
   opacity: 0.6;
 }
 
 :-moz-native-anonymous .css-grid-areas,
 :-moz-native-anonymous .css-grid-cells {
-  fill: #CEC0ED;
+  opacity: 0.5;
   stroke: none;
 }
 
 :-moz-native-anonymous .css-grid-area-infobar-name,
 :-moz-native-anonymous .css-grid-cell-infobar-position,
 :-moz-native-anonymous .css-grid-line-infobar-number {
   color: hsl(285, 100%, 75%);
 }
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -741,21 +741,28 @@ CssGridHighlighter.prototype = extend(Au
    * Update the highlighter on the current highlighted node (the one that was
    * passed as an argument to show(node)).
    * Should be called whenever node's geometry or grid changes.
    */
   _update() {
     setIgnoreLayoutChanges(true);
 
     let root = this.getElement("root");
+    let cells = this.getElement("cells");
+    let areas = this.getElement("areas");
+
     // Hide the root element and force the reflow in order to get the proper window's
     // dimensions without increasing them.
     root.setAttribute("style", "display: none");
     this.win.document.documentElement.offsetWidth;
 
+    // Set the grid cells and areas fill to the current grid colour.
+    cells.setAttribute("style", `fill: ${this.color}`);
+    areas.setAttribute("style", `fill: ${this.color}`);
+
     let { width, height } = this._winDimensions;
 
     // Updates the <canvas> element's position and size.
     // It also clear the <canvas>'s drawing context.
     this.updateCanvasElement();
 
     // Clear the grid area highlights.
     this.clearGridAreas();
@@ -1144,50 +1151,71 @@ CssGridHighlighter.prototype = extend(Au
    *         The grid fragment of the grid container.
    * @param  {Object} area
    *         The area overlay to render on the CSS highlighter canvas.
    */
   renderGridAreaName(fragment, area) {
     let { rowStart, rowEnd, columnStart, columnEnd } = area;
     let { devicePixelRatio } = this.win;
     let displayPixelRatio = getDisplayPixelRatio(this.win);
-    let currentZoom = getCurrentZoom(this.win);
     let offset = (displayPixelRatio / 2) % 1;
     let fontSize = (GRID_AREA_NAME_FONT_SIZE * displayPixelRatio);
 
     this.ctx.save();
 
     let canvasX = Math.round(this._canvasPosition.x * devicePixelRatio);
     let canvasY = Math.round(this._canvasPosition.y * devicePixelRatio);
     this.ctx.translate(offset - canvasX, offset - canvasY);
 
-    this.ctx.font = (fontSize * currentZoom) + "px " + GRID_FONT_FAMILY;
+    this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY;
     this.ctx.strokeStyle = this.color;
-    this.ctx.fillStyle = this.color;
     this.ctx.textAlign = "center";
     this.ctx.textBaseline = "middle";
 
     // Draw the text for the grid area name.
     for (let rowNumber = rowStart; rowNumber < rowEnd; rowNumber++) {
       for (let columnNumber = columnStart; columnNumber < columnEnd; columnNumber++) {
         let row = fragment.rows.tracks[rowNumber - 1];
         let column = fragment.cols.tracks[columnNumber - 1];
 
         // Check if the font size is exceeds the bounds of the containing grid cell.
         if (fontSize > column.breadth || fontSize > row.breadth) {
           fontSize = (column.breadth + row.breadth) / 2;
-          this.ctx.font = (fontSize * currentZoom) + "px " + GRID_FONT_FAMILY;
+          this.ctx.font = fontSize + "px " + GRID_FONT_FAMILY;
         }
 
+        let textWidth = this.ctx.measureText(area.name).width;
+
+        // The width of the character 'm' approximates the height of the text.
+        let textHeight = this.ctx.measureText("m").width;
+
+        // Padding in pixels for the line number text inside of the line number container.
+        let padding = 3 * displayPixelRatio;
+
+        let boxWidth = textWidth + 2 * padding;
+        let boxHeight = textHeight + 2 * padding;
+
         let x = column.start + column.breadth / 2;
         let y = row.start + row.breadth / 2;
 
         [x, y] = apply(this.currentMatrix, [x, y]);
 
-        this.ctx.fillText(area.name, x, y);
+        let rectXPos = x - boxWidth / 2;
+        let rectYPos = y - boxHeight / 2;
+
+        // Draw a rounded rectangle with a border width of 1 pixel,
+        // a border color matching the grid color, and a white background
+        this.ctx.lineWidth = 1 * displayPixelRatio;
+        this.ctx.strokeStyle = this.color;
+        this.ctx.fillStyle = "white";
+        let radius = 2 * displayPixelRatio;
+        drawRoundedRect(this.ctx, rectXPos, rectYPos, boxWidth, boxHeight, radius);
+
+        this.ctx.fillStyle = this.color;
+        this.ctx.fillText(area.name, x, y + padding);
       }
     }
 
     this.ctx.restore();
   },
 
   /**
    * Render the grid lines given the grid dimension information of the
--- a/devtools/server/tests/unit/test_promises_actor_list_promises.js
+++ b/devtools/server/tests/unit/test_promises_actor_list_promises.js
@@ -42,18 +42,20 @@ function* testListPromises(client, form,
   let promises = yield front.listPromises();
 
   let found = false;
   for (let p of promises) {
     equal(p.type, "object", "Expect type to be Object");
     equal(p.class, "Promise", "Expect class to be Promise");
     equal(typeof p.promiseState.creationTimestamp, "number",
       "Expect creation timestamp to be a number");
-    equal(typeof p.promiseState.timeToSettle, "number",
-      "Expect time to settle to be a number");
+    if (p.promiseState.state !== "pending") {
+      equal(typeof p.promiseState.timeToSettle, "number",
+        "Expect time to settle to be a number");
+    }
 
     if (p.promiseState.state === "fulfilled" &&
         p.promiseState.value === resolution) {
       found = true;
     }
   }
 
   ok(found, "Found our promise");
--- a/dom/base/Timeout.cpp
+++ b/dom/base/Timeout.cpp
@@ -40,17 +40,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Timeout, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Timeout, Release)
 
 void
 Timeout::SetWhenOrTimeRemaining(const TimeStamp& aBaseTime,
                                 const TimeDuration& aDelay)
 {
-  // This must not be called on dummy timeouts.  Instead use SetDummyWhen().
   MOZ_DIAGNOSTIC_ASSERT(mWindow);
 
   // If we are frozen simply set mTimeRemaining to be the "time remaining" in
   // the timeout (i.e., the interval itself).  This will be used to create a
   // new mWhen time when the window is thawed.  The end effect is that time does
   // not appear to pass for frozen windows.
   if (mWindow->IsFrozen()) {
     mWhen = TimeStamp();
@@ -62,23 +61,16 @@ Timeout::SetWhenOrTimeRemaining(const Ti
   // Since we are not frozen we must set a precise mWhen target wakeup
   // time.  Even if we are suspended we want to use this target time so
   // that it appears time passes while suspended.
   mWhen = aBaseTime + aDelay;
   mTimeRemaining = TimeDuration(0);
   mScheduledDelay = aDelay;
 }
 
-void
-Timeout::SetDummyWhen(const TimeStamp& aWhen)
-{
-  MOZ_DIAGNOSTIC_ASSERT(!mWindow);
-  mWhen = aWhen;
-}
-
 const TimeStamp&
 Timeout::When() const
 {
   MOZ_DIAGNOSTIC_ASSERT(!mWhen.IsNull());
   // Note, mWindow->IsFrozen() can be true here.  The Freeze() method calls
   // When() to calculate the delay to populate mTimeRemaining.
   return mWhen;
 }
--- a/dom/base/Timeout.h
+++ b/dom/base/Timeout.h
@@ -39,18 +39,16 @@ public:
   {
     eTimeoutOrInterval,
     eIdleCallbackTimeout,
   };
 
   void SetWhenOrTimeRemaining(const TimeStamp& aBaseTime,
                               const TimeDuration& aDelay);
 
-  void SetDummyWhen(const TimeStamp& aWhen);
-
   // Can only be called when not frozen.
   const TimeStamp& When() const;
 
   // Can only be called when frozen.
   const TimeDuration& TimeRemaining() const;
 
   // Can only be called when not frozen.
   const TimeDuration& ScheduledDelay() const;
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -167,26 +167,35 @@ void
 TimeoutManager::DestroyFiringId(uint32_t aFiringId)
 {
   MOZ_DIAGNOSTIC_ASSERT(!mFiringIdStack.IsEmpty());
   MOZ_DIAGNOSTIC_ASSERT(mFiringIdStack.LastElement() == aFiringId);
   mFiringIdStack.RemoveElementAt(mFiringIdStack.Length() - 1);
 }
 
 bool
+TimeoutManager::IsValidFiringId(uint32_t aFiringId) const
+{
+  return !IsInvalidFiringId(aFiringId);
+}
+
+bool
 TimeoutManager::IsInvalidFiringId(uint32_t aFiringId) const
 {
   // Check the most common ways to invalidate a firing id first.
   // These should be quite fast.
   if (aFiringId == InvalidFiringId ||
-      mFiringIdStack.IsEmpty() ||
-      (mFiringIdStack.Length() == 1 && mFiringIdStack[0] != aFiringId)) {
+      mFiringIdStack.IsEmpty()) {
     return true;
   }
 
+  if (mFiringIdStack.Length() == 1) {
+    return mFiringIdStack[0] != aFiringId;
+  }
+
   // Next do a range check on the first and last items in the stack
   // of active firing ids.  This is a bit slower.
   uint32_t low = mFiringIdStack[0];
   uint32_t high = mFiringIdStack.LastElement();
   MOZ_DIAGNOSTIC_ASSERT(low != high);
   if (low > high) {
     // If the first element is bigger than the last element in the
     // stack, that means mNextFiringId wrapped around to zero at
@@ -249,16 +258,18 @@ namespace {
 #define DEFAULT_MAX_CONSECUTIVE_CALLBACKS_MILLISECONDS 4
 uint32_t gMaxConsecutiveCallbacksMilliseconds;
 
 } // anonymous namespace
 
 TimeoutManager::TimeoutManager(nsGlobalWindow& aWindow)
   : mWindow(aWindow),
     mExecutor(new TimeoutExecutor(this)),
+    mNormalTimeouts(*this),
+    mTrackingTimeouts(*this),
     mTimeoutIdCounter(1),
     mNextFiringId(InvalidFiringId + 1),
     mRunningTimeout(nullptr),
     mIdleCallbackTimeoutCounter(1),
     mThrottleTrackingTimeouts(false)
 {
   MOZ_DIAGNOSTIC_ASSERT(aWindow.IsInnerWindow());
 
@@ -552,18 +563,16 @@ TimeoutManager::RunTimeout(const TimeSta
   // Start measuring elapsed time immediately.  We won't potentially expire
   // the time budget until at least one Timeout has run, though.
   TimeStamp now(aNow);
   TimeStamp start = now;
 
   Timeout* last_expired_normal_timeout = nullptr;
   Timeout* last_expired_tracking_timeout = nullptr;
   bool     last_expired_timeout_is_normal = false;
-  Timeout* last_normal_insertion_point = nullptr;
-  Timeout* last_tracking_insertion_point = nullptr;
 
   uint32_t firingId = CreateFiringId();
   auto guard = MakeScopeExit([&] {
     DestroyFiringId(firingId);
   });
 
   // Make sure that the window and the script context don't go away as
   // a result of running timeouts
@@ -656,74 +665,36 @@ TimeoutManager::RunTimeout(const TimeSta
 
   // Maybe the timeout that the event was fired for has been deleted
   // and there are no others timeouts with deadlines that make them
   // eligible for execution yet. Go away.
   if (!last_expired_normal_timeout && !last_expired_tracking_timeout) {
     return;
   }
 
-  // Insert a dummy timeout into the list of timeouts between the
-  // portion of the list that we are about to process now and those
-  // timeouts that will be processed in a future call to
-  // win_run_timeout(). This dummy timeout serves as the head of the
-  // list for any timeouts inserted as a result of running a timeout.
-  RefPtr<Timeout> dummy_normal_timeout = new Timeout();
-  dummy_normal_timeout->mFiringId = firingId;
-  dummy_normal_timeout->SetDummyWhen(now);
-  if (last_expired_timeout_is_normal) {
-    last_expired_normal_timeout->setNext(dummy_normal_timeout);
-  }
-
-  RefPtr<Timeout> dummy_tracking_timeout = new Timeout();
-  dummy_tracking_timeout->mFiringId = firingId;
-  dummy_tracking_timeout->SetDummyWhen(now);
-  if (!last_expired_timeout_is_normal) {
-    last_expired_tracking_timeout->setNext(dummy_tracking_timeout);
-  }
-
   // Now we need to search the normal and tracking timer list at the same
   // time to run the timers in the scheduled order.
 
-  last_normal_insertion_point = mNormalTimeouts.InsertionPoint();
-  if (last_expired_timeout_is_normal) {
-    // If we ever start setting insertion point to a non-dummy timeout, the logic
-    // in ResetTimersForThrottleReduction will need to change.
-    mNormalTimeouts.SetInsertionPoint(dummy_normal_timeout);
-  }
-
-  last_tracking_insertion_point = mTrackingTimeouts.InsertionPoint();
-  if (!last_expired_timeout_is_normal) {
-    // If we ever start setting mTrackingTimeoutInsertionPoint to a non-dummy timeout,
-    // the logic in ResetTimersForThrottleReduction will need to change.
-    mTrackingTimeouts.SetInsertionPoint(dummy_tracking_timeout);
-  }
-
   // We stop iterating each list when we go past the last expired timeout from
   // that list that we have observed above.  That timeout will either be the
-  // dummy timeout for the list that the last expired timeout came from, or it
-  // will be the next item after the last timeout we looked at (or nullptr if
-  // we have exhausted the entire list while looking for the last expired
-  // timeout).
+  // next item after the last timeout we looked at or nullptr if we have
+  // exhausted the entire list while looking for the last expired timeout.
   {
     // Use a nested scope in order to make sure the strong references held by
     // the iterator are freed after the loop.
     OrderedTimeoutIterator runIter(mNormalTimeouts,
                                    mTrackingTimeouts,
                                    last_expired_normal_timeout ?
                                      last_expired_normal_timeout->getNext() :
                                      nullptr,
                                    last_expired_tracking_timeout ?
                                      last_expired_tracking_timeout->getNext() :
                                      nullptr);
     while (true) {
       RefPtr<Timeout> timeout = runIter.Next();
-      MOZ_ASSERT(timeout != dummy_normal_timeout &&
-                 timeout != dummy_tracking_timeout,
-                 "We should have stopped iterating before getting to the dummy timeout");
       if (!timeout) {
         // We have run out of timeouts!
         break;
       }
       runIter.UpdateIterator();
 
       if (timeout->mFiringId != firingId) {
         // We skip the timeout since it's on the list to run at another
@@ -758,19 +729,16 @@ TimeoutManager::RunTimeout(const TimeSta
                this, timeout.get(),
                int(timeout->mIsTracking),
                !!timeout_was_cleared));
 
       if (timeout_was_cleared) {
         // Make sure the iterator isn't holding any Timeout objects alive.
         runIter.Clear();
 
-        mNormalTimeouts.SetInsertionPoint(last_normal_insertion_point);
-        mTrackingTimeouts.SetInsertionPoint(last_tracking_insertion_point);
-
         // Since ClearAllTimeouts() was called the lists should be empty.
         MOZ_DIAGNOSTIC_ASSERT(!HasTimeouts());
 
         return;
       }
 
       now = TimeStamp::Now();
 
@@ -807,27 +775,16 @@ TimeoutManager::RunTimeout(const TimeSta
         RefPtr<Timeout> timeout = runIter.Next();
         if (timeout) {
           MOZ_ALWAYS_SUCCEEDS(mExecutor->MaybeSchedule(timeout->When()));
         }
         break;
       }
     }
   }
-
-  // Take the dummy timeout off the head of the list
-  if (dummy_normal_timeout->isInList()) {
-    dummy_normal_timeout->remove();
-  }
-  if (dummy_tracking_timeout->isInList()) {
-    dummy_tracking_timeout->remove();
-  }
-
-  mNormalTimeouts.SetInsertionPoint(last_normal_insertion_point);
-  mTrackingTimeouts.SetInsertionPoint(last_tracking_insertion_point);
 }
 
 bool
 TimeoutManager::RescheduleTimeout(Timeout* aTimeout, const TimeStamp& now)
 {
   if (!aTimeout->mIsInterval) {
     return false;
   }
@@ -905,30 +862,24 @@ TimeoutManager::ResetTimersForThrottleRe
 
 nsresult
 TimeoutManager::Timeouts::ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS,
                                                           const TimeoutManager& aTimeoutManager,
                                                           SortBy aSortBy)
 {
   TimeStamp now = TimeStamp::Now();
 
-  // If insertion point is non-null, we're in the middle of firing timers and
-  // the timers we're planning to fire all come before insertion point;
-  // insertion point itself is a dummy timeout with an When() that may be
-  // semi-bogus.  In that case, we don't need to do anything with insertion
-  // point or anything before it, so should start at the timer after insertion
-  // point, if there is one.
-  // Otherwise, start at the beginning of the list.
-  for (RefPtr<Timeout> timeout = InsertionPoint() ?
-         InsertionPoint()->getNext() : GetFirst();
-       timeout; ) {
-    // It's important that this check be <= so that we guarantee that
-    // taking std::max with |now| won't make a quantity equal to
+  for (RefPtr<Timeout> timeout = GetFirst(); timeout; ) {
+    // Skip over any Timeout values with a valid FiringId.  These are in the
+    // middle of a RunTimeout and should not be modified.  Also, skip any
+    // timeouts in the past.  It's important that this check be <= so that we
+    // guarantee that taking std::max with |now| won't make a quantity equal to
     // timeout->When() below.
-    if (timeout->When() <= now) {
+    if (mManager.IsValidFiringId(timeout->mFiringId) ||
+        timeout->When() <= now) {
       timeout = timeout->getNext();
       continue;
     }
 
     if (timeout->When() - now >
         TimeDuration::FromMilliseconds(aPreviousThrottleDelayMS)) {
       // No need to loop further.  Timeouts are sorted in When() order
       // and the ones after this point were all set up for at least
@@ -1019,40 +970,39 @@ TimeoutManager::ClearAllTimeouts()
       seenRunningTimeout = true;
     }
 
     // Set timeout->mCleared to true to indicate that the timeout was
     // cleared and taken out of the list of timeouts
     aTimeout->mCleared = true;
   });
 
-  if (seenRunningTimeout) {
-    mNormalTimeouts.SetInsertionPoint(nullptr);
-    mTrackingTimeouts.SetInsertionPoint(nullptr);
-  }
-
   // Clear out our list
   mNormalTimeouts.Clear();
   mTrackingTimeouts.Clear();
 }
 
 void
 TimeoutManager::Timeouts::Insert(Timeout* aTimeout, SortBy aSortBy)
 {
 
-  // Start at mLastTimeout and go backwards.  Don't go further than insertion
-  // point, though.  This optimizes for the common case of insertion at the end.
+  // Start at mLastTimeout and go backwards.  Stop if we see a Timeout with a
+  // valid FiringId since those timers are currently being processed by
+  // RunTimeout.  This optimizes for the common case of insertion at the end.
   Timeout* prevSibling;
   for (prevSibling = GetLast();
-       prevSibling && prevSibling != InsertionPoint() &&
+       prevSibling &&
          // This condition needs to match the one in SetTimeoutOrInterval that
          // determines whether to set When() or TimeRemaining().
          (aSortBy == SortBy::TimeRemaining ?
           prevSibling->TimeRemaining() > aTimeout->TimeRemaining() :
-          prevSibling->When() > aTimeout->When());
+          prevSibling->When() > aTimeout->When()) &&
+         // Check the firing ID last since it will evaluate true in the vast
+         // majority of cases.
+         mManager.IsInvalidFiringId(prevSibling->mFiringId);
        prevSibling = prevSibling->getPrevious()) {
     /* Do nothing; just searching */
   }
 
   // Now link in aTimeout after prevSibling.
   if (prevSibling) {
     prevSibling->setNext(aTimeout);
   } else {
@@ -1138,30 +1088,19 @@ TimeoutManager::Resume()
   // When Suspend() has been called after IsDocumentLoaded(), but the
   // throttle tracking timer never managed to fire, start the timer
   // again.
   if (mWindow.AsInner()->IsDocumentLoaded() && !mThrottleTrackingTimeouts) {
     MaybeStartThrottleTrackingTimout();
   }
 
   TimeStamp now = TimeStamp::Now();
-  DebugOnly<bool> _seenDummyTimeout = false;
-
   TimeStamp nextWakeUp;
 
   ForEachUnorderedTimeout([&](Timeout* aTimeout) {
-    // There's a chance we're being called with RunTimeout on the stack in which
-    // case we have a dummy timeout in the list that *must not* be resumed. It
-    // can be identified by a null mWindow.
-    if (!aTimeout->mWindow) {
-      NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!");
-      _seenDummyTimeout = true;
-      return;
-    }
-
     // The timeout When() is set to the absolute time when the timer should
     // fire.  Recalculate the delay from now until that deadline.  If the
     // the deadline has already passed or falls within our minimum delay
     // deadline, then clamp the resulting value to the minimum delay.
     int32_t remaining = 0;
     if (aTimeout->When() > now) {
       remaining = static_cast<int32_t>((aTimeout->When() - now).ToMilliseconds());
     }
@@ -1179,26 +1118,18 @@ TimeoutManager::Resume()
 }
 
 void
 TimeoutManager::Freeze()
 {
   MOZ_LOG(gLog, LogLevel::Debug,
           ("Freeze(TimeoutManager=%p)\n", this));
 
-  DebugOnly<bool> _seenDummyTimeout = false;
-
   TimeStamp now = TimeStamp::Now();
   ForEachUnorderedTimeout([&](Timeout* aTimeout) {
-    if (!aTimeout->mWindow) {
-      NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!");
-      _seenDummyTimeout = true;
-      return;
-    }
-
     // Save the current remaining time for this timeout.  We will
     // re-apply it when the window is Thaw()'d.  This effectively
     // shifts timers to the right as if time does not pass while
     // the window is frozen.
     TimeDuration delta(0);
     if (aTimeout->When() > now) {
       delta = aTimeout->When() - now;
     }
@@ -1209,28 +1140,18 @@ TimeoutManager::Freeze()
 
 void
 TimeoutManager::Thaw()
 {
   MOZ_LOG(gLog, LogLevel::Debug,
           ("Thaw(TimeoutManager=%p)\n", this));
 
   TimeStamp now = TimeStamp::Now();
-  DebugOnly<bool> _seenDummyTimeout = false;
 
   ForEachUnorderedTimeout([&](Timeout* aTimeout) {
-    // There's a chance we're being called with RunTimeout on the stack in which
-    // case we have a dummy timeout in the list that *must not* be resumed. It
-    // can be identified by a null mWindow.
-    if (!aTimeout->mWindow) {
-      NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!");
-      _seenDummyTimeout = true;
-      return;
-    }
-
     // Set When() back to the time when the timer is supposed to fire.
     aTimeout->SetWhenOrTimeRemaining(now, aTimeout->TimeRemaining());
     MOZ_DIAGNOSTIC_ASSERT(!aTimeout->When().IsNull());
   });
 }
 
 bool
 TimeoutManager::IsTimeoutTracking(uint32_t aTimeoutId)
--- a/dom/base/TimeoutManager.h
+++ b/dom/base/TimeoutManager.h
@@ -123,27 +123,31 @@ private:
 
   uint32_t
   CreateFiringId();
 
   void
   DestroyFiringId(uint32_t aFiringId);
 
   bool
+  IsValidFiringId(uint32_t aFiringId) const;
+
+  bool
   IsInvalidFiringId(uint32_t aFiringId) const;
 
 private:
   struct Timeouts {
-    Timeouts()
-      : mTimeoutInsertionPoint(nullptr)
+    explicit Timeouts(const TimeoutManager& aManager)
+      : mManager(aManager)
     {
     }
 
     // Insert aTimeout into the list, before all timeouts that would
-    // fire after it, but no earlier than mTimeoutInsertionPoint, if any.
+    // fire after it, but no earlier than the last Timeout with a
+    // valid FiringId.
     enum class SortBy
     {
       TimeRemaining,
       TimeWhen
     };
     void Insert(mozilla::dom::Timeout* aTimeout, SortBy aSortBy);
     nsresult ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS,
                                              const TimeoutManager& aTimeoutManager,
@@ -152,25 +156,16 @@ private:
     const Timeout* GetFirst() const { return mTimeoutList.getFirst(); }
     Timeout* GetFirst() { return mTimeoutList.getFirst(); }
     const Timeout* GetLast() const { return mTimeoutList.getLast(); }
     Timeout* GetLast() { return mTimeoutList.getLast(); }
     bool IsEmpty() const { return mTimeoutList.isEmpty(); }
     void InsertFront(Timeout* aTimeout) { mTimeoutList.insertFront(aTimeout); }
     void Clear() { mTimeoutList.clear(); }
 
-    void SetInsertionPoint(Timeout* aTimeout)
-    {
-      mTimeoutInsertionPoint = aTimeout;
-    }
-    Timeout* InsertionPoint()
-    {
-      return mTimeoutInsertionPoint;
-    }
-
     template <class Callable>
     void ForEach(Callable c)
     {
       for (Timeout* timeout = GetFirst();
            timeout;
            timeout = timeout->getNext()) {
         c(timeout);
       }
@@ -188,27 +183,25 @@ private:
         }
       }
       return false;
     }
 
     friend class OrderedTimeoutIterator;
 
   private:
+    // The TimeoutManager that owns this Timeouts structure.  This is
+    // mainly used to call state inspecting methods like IsValidFiringId().
+    const TimeoutManager&     mManager;
+
     typedef mozilla::LinkedList<RefPtr<Timeout>> TimeoutList;
 
-    // mTimeoutList is generally sorted by mWhen, unless mTimeoutInsertionPoint is
-    // non-null.  In that case, the dummy timeout pointed to by
-    // mTimeoutInsertionPoint may have a later mWhen than some of the timeouts
-    // that come after it.
+    // mTimeoutList is generally sorted by mWhen, but new values are always
+    // inserted after any Timeouts with a valid FiringId.
     TimeoutList               mTimeoutList;
-    // If mTimeoutInsertionPoint is non-null, insertions should happen after it.
-    // This is a dummy timeout at the moment; if that ever changes, the logic in
-    // ResetTimersForThrottleReduction needs to change.
-    mozilla::dom::Timeout*    mTimeoutInsertionPoint;
   };
 
   friend class OrderedTimeoutIterator;
 
   // Each nsGlobalWindow object has a TimeoutManager member.  This reference
   // points to that holder object.
   nsGlobalWindow&             mWindow;
   // The executor is specific to the nsGlobalWindow/TimeoutManager, but it
--- a/dom/base/nsCopySupport.cpp
+++ b/dom/base/nsCopySupport.cpp
@@ -597,16 +597,23 @@ static nsresult AppendImagePromise(nsITr
   uint32_t imageStatus;
   rv = aImgRequest->GetImageStatus(&imageStatus);
   NS_ENSURE_SUCCESS(rv, rv);
   if (!(imageStatus & imgIRequest::STATUS_FRAME_COMPLETE) ||
       (imageStatus & imgIRequest::STATUS_ERROR)) {
     return NS_OK;
   }
 
+  bool isMultipart;
+  rv = aImgRequest->GetMultipart(&isMultipart);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (isMultipart) {
+    return NS_OK;
+  }
+
   nsCOMPtr<nsINode> node = do_QueryInterface(aImageElement, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Fix the file extension in the URL if necessary
   nsCOMPtr<nsIMIMEService> mimeService =
     do_GetService(NS_MIMESERVICE_CONTRACTID);
   NS_ENSURE_TRUE(mimeService, NS_OK);
 
--- a/ipc/app/Makefile.in
+++ b/ipc/app/Makefile.in
@@ -29,13 +29,13 @@ EXTRA_DEPS += plugin-container.exe.manif
 endif #}
 
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) #{
 
 libs::
 	$(NSINSTALL) -D $(DIST)/bin/$(PROGRAM).app
 	rsync -a -C --exclude '*.in' $(srcdir)/macbuild/Contents $(DIST)/bin/$(MOZ_CHILD_PROCESS_NAME).app 
 	sed -e 's/%PROGRAM%/$(MOZ_CHILD_PROCESS_NAME)/' $(srcdir)/macbuild/Contents/Info.plist.in > $(DIST)/bin/$(MOZ_CHILD_PROCESS_NAME).app/Contents/Info.plist
-	sed -e 's/%APP_NAME%/$(MOZ_APP_DISPLAYNAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \
+	sed -e 's/%APP_NAME%/$(MOZ_CHILD_PROCESS_BUNDLENAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | \
 	  iconv -f UTF-8 -t UTF-16 > $(DIST)/bin/$(MOZ_CHILD_PROCESS_NAME).app/Contents/Resources/English.lproj/InfoPlist.strings
 	$(NSINSTALL) -D $(DIST)/bin/$(MOZ_CHILD_PROCESS_NAME).app/Contents/MacOS
 	$(NSINSTALL) $(DIST)/bin/$(MOZ_CHILD_PROCESS_NAME) $(DIST)/bin/$(MOZ_CHILD_PROCESS_NAME).app/Contents/MacOS
 endif #}
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -105,17 +105,17 @@ function removeUnicodeExtensions(locale)
         pos = locale.length;
 
     var left = callFunction(String_substring, locale, 0, pos);
     var right = callFunction(String_substring, locale, pos);
 
     var extensions;
     var unicodeLocaleExtensionSequenceRE = getUnicodeLocaleExtensionSequenceRE();
     while ((extensions = regexp_exec_no_statics(unicodeLocaleExtensionSequenceRE, left)) !== null) {
-        left = callFunction(String_replace, left, extensions[0], "");
+        left = StringReplaceString(left, extensions[0], "");
         unicodeLocaleExtensionSequenceRE.lastIndex = 0;
     }
 
     var combined = left + right;
     assert(IsStructurallyValidLanguageTag(combined), "recombination produced an invalid language tag");
     assert(function() {
         var uindex = callFunction(std_String_indexOf, combined, "-u-");
         if (uindex < 0)
--- a/mobile/android/chrome/content/CastingApps.js
+++ b/mobile/android/chrome/content/CastingApps.js
@@ -567,17 +567,17 @@ var CastingApps = {
         title: Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
         icon: "drawable://casting",
         clickCallback: this.pageAction.click,
         important: true
       });
     }
   },
 
-  prompt: function(aCallback, aFilterFunc) {
+  prompt: function(aWindow, aCallback, aFilterFunc) {
     let items = [];
     let filteredServices = [];
     SimpleServiceDiscovery.services.forEach(function(aService) {
       let item = {
         label: aService.friendlyName,
         selected: false
       };
       if (!aFilterFunc || aFilterFunc(aService)) {
@@ -586,16 +586,17 @@ var CastingApps = {
       }
     });
 
     if (items.length == 0) {
       return;
     }
 
     let prompt = new Prompt({
+      window: aWindow,
       title: Strings.browser.GetStringFromName("casting.sendToDevice")
     }).setSingleChoiceItems(items).show(function(data) {
       let selected = data.button;
       let service = selected == -1 ? null : filteredServices[selected];
       if (aCallback)
         aCallback(service);
     });
   },
@@ -615,17 +616,17 @@ var CastingApps = {
     if (!aVideo) {
       return;
     }
 
     function filterFunc(aService) {
       return this.allowableExtension(aVideo.sourceURI, aService.extensions) || this.allowableMimeType(aVideo.type, aService.types);
     }
 
-    this.prompt(aService => {
+    this.prompt(aVideo.element.ownerGlobal, aService => {
       if (!aService)
         return;
 
       // Make sure we have a player app for the given service
       let app = SimpleServiceDiscovery.findAppForService(aService);
       if (!app)
         return;
 
--- a/mobile/android/chrome/content/InputWidgetHelper.js
+++ b/mobile/android/chrome/content/InputWidgetHelper.js
@@ -1,16 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 var InputWidgetHelper = {
   _uiBusy: false,
 
+  strings: function() {
+    if (!this._strings) {
+      this._strings = Services.strings.createBundle(
+          "chrome://browser/locale/browser.properties");
+    }
+    return this._strings;
+  },
+
   handleEvent: function(aEvent) {
     this.handleClick(aEvent.target);
   },
 
   handleClick: function(aTarget) {
     // if we're busy looking at a InputWidget we want to eat any clicks that
     // come to us, but not to process them
     if (this._uiBusy || !this.hasInputWidget(aTarget) || this._isDisabledElement(aTarget))
@@ -20,21 +28,21 @@ var InputWidgetHelper = {
     this.show(aTarget);
     this._uiBusy = false;
   },
 
   show: function(aElement) {
     let type = aElement.getAttribute('type');
     let p = new Prompt({
       window: aElement.ownerGlobal,
-      title: Strings.browser.GetStringFromName("inputWidgetHelper." + aElement.getAttribute('type')),
+      title: this.strings().GetStringFromName("inputWidgetHelper." + aElement.getAttribute('type')),
       buttons: [
-        Strings.browser.GetStringFromName("inputWidgetHelper.set"),
-        Strings.browser.GetStringFromName("inputWidgetHelper.clear"),
-        Strings.browser.GetStringFromName("inputWidgetHelper.cancel")
+        this.strings().GetStringFromName("inputWidgetHelper.set"),
+        this.strings().GetStringFromName("inputWidgetHelper.clear"),
+        this.strings().GetStringFromName("inputWidgetHelper.cancel")
       ],
     }).addDatePicker({
       value: aElement.value,
       type: type,
       min: aElement.min,
       max: aElement.max,
     }).show(data => {
       let changed = false;
@@ -58,34 +66,36 @@ var InputWidgetHelper = {
       // Else the user canceled the input.
 
       if (changed)
         this.fireOnChange(aElement);
     });
   },
 
   hasInputWidget: function(aElement) {
-    if (!(aElement instanceof HTMLInputElement))
+    let win = aElement.ownerGlobal;
+    if (!(aElement instanceof win.HTMLInputElement))
       return false;
 
     let type = aElement.getAttribute('type');
     if (type == "date" || type == "datetime" || type == "datetime-local" ||
         type == "week" || type == "month" || type == "time") {
       return true;
     }
 
     return false;
   },
 
   fireOnChange: function(aElement) {
+    let win = aElement.ownerGlobal;
     let evt = aElement.ownerDocument.createEvent("Events");
     evt.initEvent("change", true, true, aElement.defaultView, 0,
                   false, false,
                   false, false, null);
-    setTimeout(function() {
+    win.setTimeout(function() {
       aElement.dispatchEvent(evt);
     }, 0);
   },
 
   _isDisabledElement : function(aElement) {
     let currentElement = aElement;
     while (currentElement) {
       if (currentElement.disabled)
--- a/mobile/android/chrome/content/SelectHelper.js
+++ b/mobile/android/chrome/content/SelectHelper.js
@@ -1,16 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 var SelectHelper = {
   _uiBusy: false,
 
+  strings: function() {
+    if (!this._strings) {
+      this._strings = Services.strings.createBundle(
+          "chrome://browser/locale/browser.properties");
+    }
+    return this._strings;
+  },
+
   handleEvent: function(event) {
     this.handleClick(event.target);
   },
 
   handleClick: function(target) {
     // if we're busy looking at a select we want to eat any clicks that
     // come to us, but not to process them
     if (this._uiBusy || !this._isMenu(target) || this._isDisabledElement(target)) {
@@ -21,24 +29,25 @@ var SelectHelper = {
     this.show(target);
     this._uiBusy = false;
   },
 
   // This is a callback function to be provided to prompt.show(callBack).
   // It will update which Option elements in a Select have been selected
   // or unselected and fire the onChange event.
   _promptCallBack: function(data, element) {
+    let win = element.ownerGlobal;
     let selected = data.list;
 
     if (element instanceof Ci.nsIDOMXULMenuListElement) {
       if (element.selectedIndex != selected[0]) {
         element.selectedIndex = selected[0];
         this.fireOnCommand(element);
       }
-    } else if (element instanceof HTMLSelectElement) {
+    } else if (element instanceof win.HTMLSelectElement) {
       let changed = false;
       let i = 0; // The index for the element from `data.list` that we are currently examining.
       this.forVisibleOptions(element, function(node) {
         if (node.selected && selected.indexOf(i) == -1) {
           changed = true;
           node.selected = false;
         } else if (!node.selected && selected.indexOf(i) != -1) {
           changed = true;
@@ -56,29 +65,30 @@ var SelectHelper = {
   show: function(element) {
     let list = this.getListForElement(element);
     let p = new Prompt({
       window: element.ownerGlobal
     });
 
     if (element.multiple) {
       p.addButton({
-        label: Strings.browser.GetStringFromName("selectHelper.closeMultipleSelectDialog")
+        label: this.strings().GetStringFromName("selectHelper.closeMultipleSelectDialog")
       }).setMultiChoiceItems(list);
     } else {
       p.setSingleChoiceItems(list);
     }
 
     p.show((data) => {
       this._promptCallBack(data,element)
     });
   },
 
   _isMenu: function(element) {
-    return (element instanceof HTMLSelectElement || element instanceof Ci.nsIDOMXULMenuListElement);
+    let win = element.ownerGlobal;
+    return (element instanceof win.HTMLSelectElement || element instanceof Ci.nsIDOMXULMenuListElement);
   },
 
   // Return a list of Option elements within a Select excluding
   // any that were not visible.
   getListForElement: function(element) {
     let index = 0;
     let items = [];
     this.forVisibleOptions(element, function(node, options,parent) {
@@ -97,57 +107,60 @@ var SelectHelper = {
       items.push(item);
       index++;
     });
     return items;
   },
 
   // Apply a function to all visible Option elements in a Select
   forVisibleOptions: function(element, aFunction, parent = null) {
+    let win = element.ownerGlobal;
     if (element instanceof Ci.nsIDOMXULMenuListElement) {
       element = element.menupopup;
     }
     let children = element.children;
     let numChildren = children.length;
 
 
     // if there are no children in this select, we add a dummy row so that at least something appears
     if (numChildren == 0) {
       aFunction.call(this, {label: ""}, {isGroup: false}, parent);
     }
 
     for (let i = 0; i < numChildren; i++) {
       let child = children[i];
-      let style = window.getComputedStyle(child);
+      let style = win.getComputedStyle(child);
       if (style.display !== "none") {
-        if (child instanceof HTMLOptionElement ||
+        if (child instanceof win.HTMLOptionElement ||
             child instanceof Ci.nsIDOMXULSelectControlItemElement) {
           aFunction.call(this, child, {isGroup: false}, parent);
-        } else if (child instanceof HTMLOptGroupElement) {
+        } else if (child instanceof win.HTMLOptGroupElement) {
           aFunction.call(this, child, {isGroup: true});
           this.forVisibleOptions(child, aFunction, child);
         }
       }
     }
   },
 
   fireOnChange: function(element) {
+    let win = element.ownerGlobal;
     let event = element.ownerDocument.createEvent("Events");
     event.initEvent("change", true, true, element.defaultView, 0,
         false, false, false, false, null);
-    setTimeout(function() {
+    win.setTimeout(function() {
       element.dispatchEvent(event);
     }, 0);
   },
 
   fireOnCommand: function(element) {
+    let win = element.ownerGlobal;
     let event = element.ownerDocument.createEvent("XULCommandEvent");
     event.initCommandEvent("command", true, true, element.defaultView, 0,
         false, false, false, false, null);
-    setTimeout(function() {
+    win.setTimeout(function() {
       element.dispatchEvent(event);
     }, 0);
   },
 
   _isDisabledElement : function(element) {
     let currentElement = element;
     while (currentElement) {
       // Must test with === in case a form has a field named "disabled". See bug 1263589.
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -124,18 +124,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyServiceGetter(this, "FontEnumerator",
   "@mozilla.org/gfx/fontenumerator;1",
   "nsIFontEnumerator");
 
 var GlobalEventDispatcher = EventDispatcher.instance;
 var WindowEventDispatcher = EventDispatcher.for(window);
 
 var lazilyLoadedBrowserScripts = [
-  ["SelectHelper", "chrome://browser/content/SelectHelper.js"],
-  ["InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js"],
   ["MasterPassword", "chrome://browser/content/MasterPassword.js"],
   ["PluginHelper", "chrome://browser/content/PluginHelper.js"],
   ["OfflineApps", "chrome://browser/content/OfflineApps.js"],
   ["Linkifier", "chrome://browser/content/Linkify.js"],
   ["CastingApps", "chrome://browser/content/CastingApps.js"],
   ["RemoteDebugger", "chrome://browser/content/RemoteDebugger.js"],
 ];
 if (!AppConstants.RELEASE_OR_BETA) {
@@ -4721,19 +4719,16 @@ Tab.prototype = {
 var BrowserEventHandler = {
   init: function init() {
     BrowserApp.deck.addEventListener("touchend", this, true);
 
     BrowserApp.deck.addEventListener("DOMUpdatePageReport", PopupBlockerObserver.onUpdatePageReport);
     BrowserApp.deck.addEventListener("MozMouseHittest", this, true);
     BrowserApp.deck.addEventListener("OpenMediaWithExternalApp", this, true);
 
-    InitLater(() => BrowserApp.deck.addEventListener("click", InputWidgetHelper, true));
-    InitLater(() => BrowserApp.deck.addEventListener("click", SelectHelper, true));
-
     // ReaderViews support backPress listeners.
     WindowEventDispatcher.registerListener((event, data, callback) => {
       callback.onSuccess(Reader.onBackPress(BrowserApp.selectedTab.id));
     }, "Browser:OnBackPressed");
   },
 
   handleEvent: function(aEvent) {
     switch (aEvent.type) {
@@ -5354,16 +5349,17 @@ var XPInstallObserver = {
           callback: (data) => {
             if (data.button === 1) {
               Services.prefs.setBoolPref("xpinstall.enabled", true)
             }
           };
         }
 
         new Prompt({
+          window: window,
           title: Strings.browser.GetStringFromName("addonError.titleError"),
           message: message,
           buttons: buttons
         }).show(callback);
         break;
       }
       case "addon-install-blocked": {
         if (!tab)
@@ -5388,32 +5384,34 @@ var XPInstallObserver = {
           }
         }
 
         let buttons = [
             strings.GetStringFromName("xpinstallPromptAllowButton"),
             strings.GetStringFromName("unsignedAddonsDisabled.dismiss")
         ];
         new Prompt({
+          window: window,
           title: Strings.browser.GetStringFromName("addonError.titleBlocked"),
           message: message,
           buttons: buttons
         }).show((data) => {
           if (data.button === 0) {
             // Kick off the install
             installInfo.install();
           }
         });
         break;
       }
       case "addon-install-origin-blocked": {
         if (!tab)
           return;
 
         new Prompt({
+          window: window,
           title: Strings.browser.GetStringFromName("addonError.titleBlocked"),
           message: strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1),
           buttons: [strings.GetStringFromName("unsignedAddonsDisabled.dismiss")]
         }).show((data) => {});
         break;
       }
       case "xpi-signature-changed": {
         if (JSON.parse(aData).disabled.length) {
@@ -6629,16 +6627,17 @@ var ExternalApps = {
         let wasPlaying = mediaElement && !mediaElement.paused && !mediaElement.ended;
         if (wasPlaying) {
           mediaElement.pause();
         }
 
         if (apps.length > 1) {
           // Use the HelperApps prompt here to filter out any Http handlers
           HelperApps.prompt(apps, {
+            window: window,
             title: Strings.browser.GetStringFromName("openInApp.pageAction"),
             buttons: [
               Strings.browser.GetStringFromName("openInApp.ok"),
               Strings.browser.GetStringFromName("openInApp.cancel")
             ]
           }, (result) => {
             if (result.button != 0) {
               if (wasPlaying) {
--- a/mobile/android/components/ColorPicker.js
+++ b/mobile/android/components/ColorPicker.js
@@ -29,21 +29,24 @@ ColorPicker.prototype = {
 
   init: function(aParent, aTitle, aInitial) {
     this._domWin = aParent;
     this._initial = aInitial;
     this._title = aTitle;
   },
 
   open: function(aCallback) {
-    let p = new Prompt({ title: this._title,
+    let p = new Prompt({
+                         window: this._domWin,
+                         title: this._title,
                          buttons: [
-                            this.strings.GetStringFromName("inputWidgetHelper.set"),
-                            this.strings.GetStringFromName("inputWidgetHelper.cancel")
-                         ] })
+                           this.strings.GetStringFromName("inputWidgetHelper.set"),
+                           this.strings.GetStringFromName("inputWidgetHelper.cancel"),
+                         ],
+                       })
                       .addColorPicker({ value: this._initial })
                       .show((data) => {
       if (data.button == 0)
         aCallback.done(data.color0);
       else
         aCallback.done(this._initial);
     });
   },
--- a/mobile/android/components/FilePicker.js
+++ b/mobile/android/components/FilePicker.js
@@ -207,17 +207,17 @@ FilePicker.prototype = {
       guid: this.guid,
       title: this._title,
     };
 
     // Knowing the window lets us destroy any temp files when the tab is closed
     // Other consumers of the file picker may have to either wait for Android
     // to clean up the temp dir (not guaranteed) or clean up after themselves.
     let win = Services.wm.getMostRecentWindow('navigator:browser');
-    let tab = win.BrowserApp.getTabForWindow(this._domWin.top)
+    let tab = win && win.BrowserApp.getTabForWindow(this._domWin.top)
     if (tab) {
       msg.tabId = tab.id;
     }
 
     if (!this._extensionsFilter && !this._mimeTypeFilter) {
       // If neither filters is set show anything we can.
       msg.mode = "mimeType";
       msg.mimeType = "*/*";
--- a/mobile/android/components/MobileComponents.manifest
+++ b/mobile/android/components/MobileComponents.manifest
@@ -36,16 +36,17 @@ component {C6E8C44D-9F39-4AF7-BCC0-76E38
 contract @mozilla.org/content-permission/prompt;1 {C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5}
 
 # PromptService.js
 component {9a61149b-2276-4a0a-b79c-be994ad106cf} PromptService.js
 contract @mozilla.org/prompter;1 {9a61149b-2276-4a0a-b79c-be994ad106cf}
 contract @mozilla.org/embedcomp/prompt-service;1 {9a61149b-2276-4a0a-b79c-be994ad106cf}
 component {80dae1e9-e0d2-4974-915f-f97050fa8068} PromptService.js
 contract @mozilla.org/network/authprompt-adapter-factory;1 {80dae1e9-e0d2-4974-915f-f97050fa8068}
+category app-startup PromptService service,@mozilla.org/prompter;1
 
 # PresentationDevicePrompt.js
 component {388bd149-c919-4a43-b646-d7ec57877689} PresentationDevicePrompt.js
 contract @mozilla.org/presentation-device/prompt;1 {388bd149-c919-4a43-b646-d7ec57877689}
 
 # PresentationRequestUIGlue.js
 component {9c550ef7-3ff6-4bd1-9ad1-5a3735b90d21} PresentationRequestUIGlue.js
 contract @mozilla.org/presentation/requestuiglue;1 {9c550ef7-3ff6-4bd1-9ad1-5a3735b90d21}
--- a/mobile/android/components/PromptService.js
+++ b/mobile/android/components/PromptService.js
@@ -16,17 +16,57 @@ var gPromptService = null;
 
 function PromptService() {
   gPromptService = this;
 }
 
 PromptService.prototype = {
   classID: Components.ID("{9a61149b-2276-4a0a-b79c-be994ad106cf}"),
 
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),
+  QueryInterface: XPCOMUtils.generateQI([
+      Ci.nsIObserver, Ci.nsIPromptFactory, Ci.nsIPromptService, Ci.nsIPromptService2]),
+
+  loadSubscript: function(aName, aScript) {
+    let sandbox = {};
+    Services.scriptloader.loadSubScript(aScript, sandbox);
+    return sandbox[aName];
+  },
+
+  /* ----------  nsIObserver  ---------- */
+  observe: function(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "app-startup": {
+        Services.obs.addObserver(this, "chrome-document-global-created");
+        Services.obs.addObserver(this, "content-document-global-created");
+        break;
+      }
+      case "chrome-document-global-created":
+      case "content-document-global-created": {
+        let win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDocShell).QueryInterface(Ci.nsIDocShellTreeItem)
+                          .rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIDOMWindow);
+        if (win !== aSubject) {
+          // Only attach to top-level windows.
+          return;
+        }
+        if (!this.selectHelper) {
+          this.selectHelper = this.loadSubscript(
+              "SelectHelper", "chrome://browser/content/SelectHelper.js");
+        }
+        if (!this.inputWidgetHelper) {
+          this.inputWidgetHelper = this.loadSubscript(
+              "InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js");
+        }
+        win.addEventListener("click", this.selectHelper); // non-capture
+        win.addEventListener("click", this.inputWidgetHelper); // non-capture
+        break;
+      }
+    }
+  },
 
   /* ----------  nsIPromptFactory  ---------- */
   // XXX Copied from nsPrompter.js.
   getPrompt: function getPrompt(domWin, iid) {
     // This is still kind of dumb; the C++ code delegated to login manager
     // here, which in turn calls back into us via nsIPromptService2.
     if (iid.equals(Ci.nsIAuthPrompt2) || iid.equals(Ci.nsIAuthPrompt)) {
       try {
--- a/mobile/android/tests/browser/chrome/test_hidden_select_option.html
+++ b/mobile/android/tests/browser/chrome/test_hidden_select_option.html
@@ -15,18 +15,20 @@ https://bugzilla.mozilla.org/show_bug.cg
     <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
     <script type="application/javascript" src="head.js"></script>
     <script type="application/javascript">
     "use strict";
 
     const VISIBLE_OPTION_COUNT = 5;
     const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
     Cu.import("resource://gre/modules/Services.jsm");
-    let win = Services.wm.getMostRecentWindow("navigator:browser");
-    let SelectHelper = win.SelectHelper;
+
+    let sandbox = {};
+    Services.scriptloader.loadSubScript("chrome://browser/content/SelectHelper.js", sandbox);
+    let SelectHelper = sandbox.SelectHelper;
 
     // Returns whether an element should be visible according to its text content.
     function shouldBeVisible(e){
       return e.label.indexOf("visible") > 0;
     }
 
     // Returns an object for the callback method that would normally be created by Prompt.java's
     // addListResult(..) method.
--- a/mobile/android/tests/browser/chrome/test_select_disabled.html
+++ b/mobile/android/tests/browser/chrome/test_select_disabled.html
@@ -12,18 +12,20 @@ https://bugzilla.mozilla.org/show_bug.cg
     <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
     <script type="application/javascript" src="head.js"></script>
     <script type="application/javascript">
     "use strict";
 
     const VISIBLE_OPTION_COUNT = 5;
     const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
     Cu.import("resource://gre/modules/Services.jsm");
-    let win = Services.wm.getMostRecentWindow("navigator:browser");
-    let SelectHelper = win.SelectHelper;
+
+    let sandbox = {};
+    Services.scriptloader.loadSubScript("chrome://browser/content/SelectHelper.js", sandbox);
+    let SelectHelper = sandbox.SelectHelper;
 
     // Wait until the page has loaded so that we can access the DOM.
     SimpleTest.waitForExplicitFinish();
     window.onload = function () {
       // test options are not incorrectly disabled...
       let isEnabled1 = document.getElementById("is_enabled_1");
       let isEnabled2 = document.getElementById("is_enabled_2");
       ok(!SelectHelper._isDisabledElement(isEnabled1),"input with name=\"disabled\" should not disable options (bug 1263589)");
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5154,50 +5154,16 @@ pref("gfx.vr.osvr.clientLibPath", "");
 pref("gfx.vr.osvr.clientKitLibPath", "");
 // Puppet device, used for simulating VR hardware within tests and dev tools
 pref("dom.vr.puppet.enabled", false);
 // Allow displaying the result of vr submitframe (0: disable, 1: store the
 // result as a base64 image, 2: show it on the screen).
 pref("dom.vr.puppet.submitframe", 0);
 // VR test system.
 pref("dom.vr.test.enabled", false);
-// MMS UA Profile settings
-pref("wap.UAProf.url", "");
-pref("wap.UAProf.tagname", "x-wap-profile");
-
-// MMS version 1.1 = 0x11 (or decimal 17)
-// MMS version 1.3 = 0x13 (or decimal 19)
-// @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.34
-pref("dom.mms.version", 19);
-
-pref("dom.mms.requestStatusReport", true);
-
-// Retrieval mode for MMS
-// manual: Manual retrieval mode.
-// automatic: Automatic retrieval mode even in roaming.
-// automatic-home: Automatic retrieval mode in home network.
-// never: Never retrieval mode.
-pref("dom.mms.retrieval_mode", "manual");
-
-pref("dom.mms.sendRetryCount", 3);
-pref("dom.mms.sendRetryInterval", "10000,60000,180000");
-
-pref("dom.mms.retrievalRetryCount", 4);
-pref("dom.mms.retrievalRetryIntervals", "60000,300000,600000,1800000");
-// Numeric default service id for MMS API calls with |serviceId| parameter
-// omitted.
-pref("dom.mms.defaultServiceId", 0);
-// Debug enabler for MMS.
-pref("mms.debugging.enabled", false);
-
-// Request read report while sending MMS.
-pref("dom.mms.requestReadReport", true);
-
-// Number of RadioInterface instances to create.
-pref("ril.numRadioInterfaces", 0);
 
 // If the user puts a finger down on an element and we think the user
 // might be executing a pan gesture, how long do we wait before
 // tentatively deciding the gesture is actually a tap and activating
 // the target element?
 pref("ui.touch_activation.delay_ms", 100);
 
 // If the user has clicked an element, how long do we keep the
--- a/old-configure.in
+++ b/old-configure.in
@@ -5034,19 +5034,21 @@ else
   # so if the file is named libsomething.so. The lib/ path is also required
   # because the unpacked file will be under the lib/ subdirectory and will
   # need to be executed from that path.
   MOZ_CHILD_PROCESS_NAME="libplugin-container.so"
   MOZ_CHILD_PROCESS_NAME_PIE="libplugin-container-pie.so"
   AC_SUBST(MOZ_CHILD_PROCESS_NAME_PIE)
 fi
 MOZ_CHILD_PROCESS_BUNDLE="plugin-container.app/Contents/MacOS/"
+MOZ_CHILD_PROCESS_BUNDLENAME="${MOZ_APP_DISPLAYNAME}CP"
 
 AC_SUBST(MOZ_CHILD_PROCESS_NAME)
 AC_SUBST(MOZ_CHILD_PROCESS_BUNDLE)
+AC_SUBST(MOZ_CHILD_PROCESS_BUNDLENAME)
 
 # The following variables are available to branding and application
 # configuration ($BRANDING/configure.sh and $APPLICATION/confvars.sh):
 # - MOZ_APP_VENDOR: Used for application.ini's "Vendor" field, which also
 # impacts profile location and user-visible fields.
 # - MOZ_APP_BASENAME: Typically stays consistent for multiple branded
 # versions of a given application (e.g. Aurora and Firefox both use
 # "Firefox"), but may vary for full rebrandings (e.g. Iceweasel). Used
--- a/taskcluster/docker/funsize-balrog-submitter/scripts/funsize-balrog-submitter.py
+++ b/taskcluster/docker/funsize-balrog-submitter/scripts/funsize-balrog-submitter.py
@@ -79,17 +79,17 @@ def verify_copy_to_s3(bucket_name, aws_a
                 # key.make_public() may lead to race conditions, because
                 # it doesn't pass version_id, so it may not set permissions
                 bucket.set_canned_acl(acl_str='public-read', key_name=name,
                                       version_id=key.version_id)
                 # Use explicit version_id to avoid using "latest" version
                 return key.generate_url(expires_in=0, query_auth=False,
                                         version_id=key.version_id)
         else:
-            if get_hash(key.get_contents_as_string()) == \
+            if get_hash(retry(key.get_contents_as_string)) == \
                     get_hash(open(dest).read()):
                 log.info("%s has the same MD5 checksum, not uploading...",
                          name)
                 return key.generate_url(expires_in=0, query_auth=False,
                                         version_id=key.version_id)
             log.info("%s already exists with different checksum, "
                      "trying another one...", name)
 
--- a/testing/mozharness/mozharness/base/script.py
+++ b/testing/mozharness/mozharness/base/script.py
@@ -1565,28 +1565,29 @@ class ScriptMixin(PlatformMixin):
         # XXX: changed from self.debug to self.log due to this error:
         #      TypeError: debug() takes exactly 1 argument (2 given)
         self.log("Temporary files: %s and %s" % (tmp_stdout_filename, tmp_stderr_filename), level=DEBUG)
         p.wait()
         tmp_stdout.close()
         tmp_stderr.close()
         return_level = DEBUG
         output = None
-        if os.path.exists(tmp_stdout_filename) and os.path.getsize(tmp_stdout_filename):
-            output = self.read_from_file(tmp_stdout_filename,
-                                         verbose=False)
-            if not silent:
-                self.log("Output received:", level=log_level)
-                output_lines = output.rstrip().splitlines()
-                for line in output_lines:
-                    if not line or line.isspace():
-                        continue
-                    line = line.decode("utf-8")
-                    self.log(' %s' % line, level=log_level)
-                output = '\n'.join(output_lines)
+        if return_type == 'output' or not silent:
+            if os.path.exists(tmp_stdout_filename) and os.path.getsize(tmp_stdout_filename):
+                output = self.read_from_file(tmp_stdout_filename,
+                                             verbose=False)
+                if not silent:
+                    self.log("Output received:", level=log_level)
+                    output_lines = output.rstrip().splitlines()
+                    for line in output_lines:
+                        if not line or line.isspace():
+                            continue
+                        line = line.decode("utf-8")
+                        self.log(' %s' % line, level=log_level)
+                    output = '\n'.join(output_lines)
         if os.path.exists(tmp_stderr_filename) and os.path.getsize(tmp_stderr_filename):
             if not ignore_errors:
                 return_level = ERROR
             self.log("Errors received:", level=return_level)
             errors = self.read_from_file(tmp_stderr_filename,
                                          verbose=False)
             for line in errors.rstrip().splitlines():
                 if not line or line.isspace():
--- a/toolkit/components/extensions/ExtensionPolicyService.cpp
+++ b/toolkit/components/extensions/ExtensionPolicyService.cpp
@@ -239,17 +239,19 @@ ExtensionPolicyService::CheckRequest(nsI
 // Checks a document, just after the document element has been inserted, for
 // matching content scripts or extension principals, and loads them if
 // necessary.
 void
 ExtensionPolicyService::CheckDocument(nsIDocument* aDocument)
 {
   nsCOMPtr<nsPIDOMWindowOuter> win = aDocument->GetWindow();
   if (win) {
-    CheckContentScripts(win.get(), false);
+    if (win->GetDocumentURI()) {
+      CheckContentScripts(win.get(), false);
+    }
 
     nsIPrincipal* principal = aDocument->NodePrincipal();
 
     nsAutoString addonId;
     Unused << principal->GetAddonId(addonId);
 
     RefPtr<WebExtensionPolicy> policy = GetByID(addonId);
     if (policy) {
@@ -274,17 +276,17 @@ ExtensionPolicyService::CheckWindow(nsPI
   }
 
   nsCOMPtr<nsIURI> aboutBlank;
   NS_ENSURE_SUCCESS_VOID(NS_NewURI(getter_AddRefs(aboutBlank),
                                    "about:blank"));
 
   nsCOMPtr<nsIURI> uri = doc->GetDocumentURI();
   bool equal;
-  if (NS_FAILED(uri->EqualsExceptRef(aboutBlank, &equal)) || !equal) {
+  if (!uri || NS_FAILED(uri->EqualsExceptRef(aboutBlank, &equal)) || !equal) {
     return;
   }
 
   CheckContentScripts(aWindow, false);
 }
 
 void
 ExtensionPolicyService::CheckContentScripts(const DocInfo& aDocInfo, bool aIsPreload)
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -12391,24 +12391,16 @@
     "record_in_processes": ["main", "content"],
     "alert_emails": ["amccreight@mozilla.com"],
     "bug_numbers": [1272423],
     "expires_in_version": "55",
     "kind": "count",
     "keyed": true,
     "description": "Each key is the message name, with digits removed, from an async message manager message that was rejected for being over approximately 128MB, recorded in the sending process at the time of sending."
   },
-  "SANDBOX_BROKER_INITIALIZED": {
-    "record_in_processes": ["main", "content"],
-    "alert_emails": ["bowen@mozilla.com"],
-    "bug_numbers": [1256992],
-    "expires_in_version": "55",
-    "kind": "boolean",
-    "description": "Result of call to SandboxBroker::Initialize"
-  },
   "SANDBOX_HAS_SECCOMP_BPF": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["gcp@mozilla.com"],
     "bug_numbers": [1098428],
     "expires_in_version": "55",
     "kind": "boolean",
     "cpp_guard": "XP_LINUX",
     "description": "Whether the system has seccomp-bpf capability"
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -3360,19 +3360,17 @@ XREMain::XRE_mainInit(bool* aExitFlag)
       }
     }
   }
 #endif
 
 #if defined(MOZ_SANDBOX) && defined(XP_WIN)
   if (mAppData->sandboxBrokerServices) {
     SandboxBroker::Initialize(mAppData->sandboxBrokerServices);
-    Telemetry::Accumulate(Telemetry::SANDBOX_BROKER_INITIALIZED, true);
   } else {
-    Telemetry::Accumulate(Telemetry::SANDBOX_BROKER_INITIALIZED, false);
 #if defined(MOZ_CONTENT_SANDBOX)
     // If we're sandboxing content and we fail to initialize, then crashing here
     // seems like the sensible option.
     if (BrowserTabsRemoteAutostart()) {
       MOZ_CRASH("Failed to initialize broker services, can't continue.");
     }
 #endif
     // Otherwise just warn for the moment, as most things will work.
--- a/xpcom/tests/gtest/TestTimers.cpp
+++ b/xpcom/tests/gtest/TestTimers.cpp
@@ -335,17 +335,17 @@ TEST(Timers, FindExpirationTime)
     EXPECT_EQ(t, before) << "Found time should be equal to default";
 
     t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20);
     EXPECT_TRUE(t) << "We should find a time";
     EXPECT_EQ(t, before) << "Found time should be equal to default";
 
     t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0);
     EXPECT_TRUE(t) << "We should find a time";
-    EXPECT_EQ(t, middle) << "Found time should be equal to default";
+    EXPECT_LT(t, middle) << "Found time should be equal to default";
 
     t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10);
     EXPECT_TRUE(t) << "We should find a time";
     EXPECT_EQ(t, middle) << "Found time should be equal to default";
 
     t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20);
     EXPECT_TRUE(t) << "We should find a time";
     EXPECT_EQ(t, middle) << "Found time should be equal to default";
--- a/xpcom/threads/TimerThread.cpp
+++ b/xpcom/threads/TimerThread.cpp
@@ -592,52 +592,63 @@ TimerThread::RemoveTimer(nsTimerImpl* aT
 
 TimeStamp
 TimerThread::FindNextFireTimeForCurrentThread(TimeStamp aDefault, uint32_t aSearchBound)
 {
   MonitorAutoLock lock(mMonitor);
   TimeStamp timeStamp = aDefault;
   uint32_t index = 0;
 
-  for (auto timers = mTimers.begin(); timers != mTimers.end(); ++timers) {
-    nsTimerImpl* timer = (*timers)->Value();
+#ifdef DEBUG
+  TimeStamp firstTimeStamp;
+  if (!mTimers.IsEmpty()) {
+    firstTimeStamp = mTimers[0]->Timeout();
+  }
+#endif
 
-    if (!timer) {
-      continue;
-    }
+  auto end = mTimers.end();
+  while(end != mTimers.begin()) {
+    nsTimerImpl* timer = mTimers[0]->Value();
+    if (timer) {
+      if (timer->mTimeout > aDefault) {
+        timeStamp = aDefault;
+        break;
+      }
 
-    if (timer->mTimeout > aDefault) {
-      timeStamp = aDefault;
-      break;
-    }
+      // Don't yield to timers created with the *_LOW_PRIORITY type.
+      if (!timer->IsLowPriority()) {
+        bool isOnCurrentThread = false;
+        nsresult rv = timer->mEventTarget->IsOnCurrentThread(&isOnCurrentThread);
+        if (NS_SUCCEEDED(rv) && isOnCurrentThread) {
+          timeStamp = timer->mTimeout;
+          break;
+        }
+      }
 
-    // Don't yield to timers created with the *_LOW_PRIORITY type.
-    if (timer->IsLowPriority()) {
-      continue;
+      if (++index > aSearchBound) {
+        // Track the currently highest timeout so that we can bail out when we
+        // reach the bound or when we find a timer for the current thread.
+        // This won't give accurate information if we stop before finding
+        // any timer for the current thread, but at least won't report too
+        // long idle period.
+        timeStamp = timer->mTimeout;
+        break;
+      }
     }
 
-    // Track the currently highest timeout so that we can bail when we
-    // reach the bound or when we find a timer for the current thread.
-    timeStamp = timer->mTimeout;
-
-    bool isOnCurrentThread = false;
-    nsresult rv = timer->mEventTarget->IsOnCurrentThread(&isOnCurrentThread);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      continue;
-    }
+    std::pop_heap(mTimers.begin(), end, Entry::UniquePtrLessThan);
+    --end;
+  }
 
-    if (isOnCurrentThread) {
-
-      break;
-    }
+  while (end != mTimers.end()) {
+    ++end;
+    std::push_heap(mTimers.begin(), end, Entry::UniquePtrLessThan);
+  }
 
-    if (++index > aSearchBound) {
-      break;
-    }
-  }
+  MOZ_ASSERT_IF(!mTimers.IsEmpty(), firstTimeStamp == mTimers[0]->Timeout());
 
   return timeStamp;
 }
 
 // This function must be called from within a lock
 bool
 TimerThread::AddTimerInternal(nsTimerImpl* aTimer)
 {
--- a/xpcom/threads/TimerThread.h
+++ b/xpcom/threads/TimerThread.h
@@ -111,16 +111,21 @@ private:
 
     static bool
     UniquePtrLessThan(UniquePtr<Entry>& aLeft, UniquePtr<Entry>& aRight)
     {
       // This is reversed because std::push_heap() sorts the "largest" to
       // the front of the heap.  We want that to be the earliest timer.
       return aRight->mTimeout < aLeft->mTimeout;
     }
+
+    TimeStamp Timeout() const
+    {
+      return mTimeout;
+    }
   };
 
   nsTArray<UniquePtr<Entry>> mTimers;
   uint32_t mAllowedEarlyFiringMicroseconds;
 };
 
 struct TimerAdditionComparator
 {