Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 02 Jan 2015 14:15:38 +0100
changeset 247694 bf2de389e2455da80c267705706c4d29327b2225
parent 247693 02e04f0df478f6b0a66914a59d25d5d5ab71c597 (current diff)
parent 247668 57e4e9c33bef603482bcdbdab3e5910ac0f2d2d6 (diff)
child 247695 122af164cec7de98497b26166a8c461f787dabd2
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone37.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 mozilla-central to b2g-inbound
--- a/accessible/base/nsAccessibilityService.h
+++ b/accessible/base/nsAccessibilityService.h
@@ -246,18 +246,16 @@ GetAccService()
 }
 
 /**
  * Return true if we're in a content process and not B2G.
  */
 inline bool
 IPCAccessibilityActive()
 {
-  // XXX temporarily disable ipc accessibility because of crashes.
-return false;
 #ifdef MOZ_B2G
   return false;
 #else
   return XRE_GetProcessType() == GeckoProcessType_Content;
 #endif
 }
 
 /**
--- a/accessible/ipc/ProxyAccessible.h
+++ b/accessible/ipc/ProxyAccessible.h
@@ -18,17 +18,18 @@ class Attribute;
 class DocAccessibleParent;
 
 class ProxyAccessible
 {
 public:
 
   ProxyAccessible(uint64_t aID, ProxyAccessible* aParent,
                   DocAccessibleParent* aDoc, role aRole) :
-     mParent(aParent), mDoc(aDoc), mID(aID), mRole(aRole), mOuterDoc(false)
+     mParent(aParent), mDoc(aDoc), mWrapper(0), mID(aID), mRole(aRole),
+     mOuterDoc(false)
   {
     MOZ_COUNT_CTOR(ProxyAccessible);
   }
   ~ProxyAccessible()
   {
     MOZ_COUNT_DTOR(ProxyAccessible);
     MOZ_ASSERT(!mWrapper);
   }
--- a/browser/devtools/performance/modules/front.js
+++ b/browser/devtools/performance/modules/front.js
@@ -197,16 +197,17 @@ PerformanceActorsConnection.prototype = 
  */
 function PerformanceFront(connection) {
   EventEmitter.decorate(this);
 
   this._request = connection._request;
 
   // Pipe events from TimelineActor to the PerformanceFront
   connection._timeline.on("markers", markers => this.emit("markers", markers));
+  connection._timeline.on("frames", (delta, frames) => this.emit("frames", delta, frames));
   connection._timeline.on("memory", (delta, measurement) => this.emit("memory", delta, measurement));
   connection._timeline.on("ticks", (delta, timestamps) => this.emit("ticks", delta, timestamps));
 }
 
 PerformanceFront.prototype = {
   /**
    * Manually begins a recording session.
    *
--- a/browser/devtools/performance/performance-controller.js
+++ b/browser/devtools/performance/performance-controller.js
@@ -142,16 +142,17 @@ let PerformanceController = {
   /**
    * Permanent storage for the markers and the memory measurements streamed by
    * the backend, along with the start and end timestamps.
    */
   _localStartTime: RECORDING_UNAVAILABLE,
   _startTime: RECORDING_UNAVAILABLE,
   _endTime: RECORDING_UNAVAILABLE,
   _markers: [],
+  _frames: [],
   _memory: [],
   _ticks: [],
   _profilerData: {},
 
   /**
    * Listen for events emitted by the current tab target and
    * main UI events.
    */
@@ -164,30 +165,32 @@ let PerformanceController = {
 
     PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
     PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
     PerformanceView.on(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
     PerformanceView.on(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
 
     gFront.on("ticks", this._onTimelineData); // framerate
     gFront.on("markers", this._onTimelineData); // timeline markers
+    gFront.on("frames", this._onTimelineData); // stack frames
     gFront.on("memory", this._onTimelineData); // timeline memory
   },
 
   /**
    * Remove events handled by the PerformanceController
    */
   destroy: function() {
     PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
     PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
     PerformanceView.off(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
     PerformanceView.off(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
 
     gFront.off("ticks", this._onTimelineData);
     gFront.off("markers", this._onTimelineData);
+    gFront.off("frames", this._onTimelineData);
     gFront.off("memory", this._onTimelineData);
   },
 
   /**
    * Starts recording with the PerformanceFront. Emits `EVENTS.RECORDING_STARTED`
    * when the front has started to record.
    */
   startRecording: Task.async(function *() {
@@ -200,16 +203,17 @@ let PerformanceController = {
     let { startTime } = yield gFront.startRecording({
       withTicks: true,
       withMemory: true
     });
 
     this._startTime = startTime;
     this._endTime = RECORDING_IN_PROGRESS;
     this._markers = [];
+    this._frames = [];
     this._memory = [];
     this._ticks = [];
 
     this.emit(EVENTS.RECORDING_STARTED);
   }),
 
   /**
    * Stops recording with the PerformanceFront. Emits `EVENTS.RECORDING_STOPPED`
@@ -251,16 +255,17 @@ let PerformanceController = {
    *        The file to import the data from.
    */
   importRecording: Task.async(function*(_, file) {
     let recordingData = yield PerformanceIO.loadRecordingFromFile(file);
 
     this._startTime = recordingData.interval.startTime;
     this._endTime = recordingData.interval.endTime;
     this._markers = recordingData.markers;
+    this._frames = recordingData.frames;
     this._memory = recordingData.memory;
     this._ticks = recordingData.ticks;
     this._profilerData = recordingData.profilerData;
 
     this.emit(EVENTS.RECORDING_IMPORTED, recordingData);
 
     // Flush the current recording.
     this.emit(EVENTS.RECORDING_STARTED);
@@ -296,16 +301,24 @@ let PerformanceController = {
    * Gets the accumulated markers in the current recording.
    * @return array
    */
   getMarkers: function() {
     return this._markers;
   },
 
   /**
+   * Gets the accumulated stack frames in the current recording.
+   * @return array
+   */
+  getFrames: function() {
+    return this._frames;
+  },
+
+  /**
    * Gets the accumulated memory measurements in this recording.
    * @return array
    */
   getMemory: function() {
     return this._memory;
   },
 
   /**
@@ -325,31 +338,37 @@ let PerformanceController = {
   },
 
   /**
    * Gets all the data in this recording.
    */
   getAllData: function() {
     let interval = this.getInterval();
     let markers = this.getMarkers();
+    let frames = this.getFrames();
     let memory = this.getMemory();
     let ticks = this.getTicks();
     let profilerData = this.getProfilerData();
-    return { interval, markers, memory, ticks, profilerData };
+    return { interval, markers, frames, memory, ticks, profilerData };
   },
 
   /**
    * Fired whenever the PerformanceFront emits markers, memory or ticks.
    */
   _onTimelineData: function (eventName, ...data) {
     // Accumulate markers into an array.
     if (eventName == "markers") {
       let [markers] = data;
       Array.prototype.push.apply(this._markers, markers);
     }
+    // Accumulate stack frames into an array.
+    else if (eventName == "frames") {
+      let [delta, frames] = data;
+      Array.prototype.push.apply(this._frames, frames);
+    }
     // Accumulate memory measurements into an array.
     else if (eventName == "memory") {
       let [delta, measurement] = data;
       this._memory.push({ delta, value: measurement.total / 1024 / 1024 });
     }
     // Save the accumulated refresh driver ticks.
     else if (eventName == "ticks") {
       let [delta, timestamps] = data;
--- a/browser/devtools/performance/views/details-waterfall.js
+++ b/browser/devtools/performance/views/details-waterfall.js
@@ -67,17 +67,21 @@ let WaterfallView = {
   },
 
   /**
    * Called when a marker is selected in the waterfall view,
    * updating the markers detail view.
    */
   _onMarkerSelected: function (event, marker) {
     if (event === "selected") {
-      this.markerDetails.render(marker);
+      this.markerDetails.render({
+        toolbox: gToolbox,
+        marker: marker,
+        frames: PerformanceController.getFrames()
+      });
     }
     if (event === "unselected") {
       this.markerDetails.empty();
     }
   },
 
   /**
    * Called when the marker details view is resized.
--- a/browser/devtools/timeline/timeline.js
+++ b/browser/devtools/timeline/timeline.js
@@ -72,34 +72,38 @@ let TimelineController = {
   /**
    * Permanent storage for the markers and the memory measurements streamed by
    * the backend, along with the start and end timestamps.
    */
   _starTime: 0,
   _endTime: 0,
   _markers: [],
   _memory: [],
+  _frames: [],
 
   /**
    * Initialization function, called when the tool is started.
    */
   initialize: function() {
     this._onRecordingTick = this._onRecordingTick.bind(this);
     this._onMarkers = this._onMarkers.bind(this);
     this._onMemory = this._onMemory.bind(this);
+    this._onFrames = this._onFrames.bind(this);
     gFront.on("markers", this._onMarkers);
     gFront.on("memory", this._onMemory);
+    gFront.on("frames", this._onFrames);
   },
 
   /**
    * Destruction function, called when the tool is closed.
    */
   destroy: function() {
     gFront.off("markers", this._onMarkers);
     gFront.off("memory", this._onMemory);
+    gFront.off("frames", this._onFrames);
   },
 
   /**
    * Gets the { stat, end } time interval for this recording.
    * @return object
    */
   getInterval: function() {
     return { startTime: this._startTime, endTime: this._endTime };
@@ -117,16 +121,26 @@ let TimelineController = {
    * Gets the accumulated memory measurements in this recording.
    * @return array
    */
   getMemory: function() {
     return this._memory;
   },
 
   /**
+   * Gets stack frame array reported by the actor.  The marker "stack"
+   * and "endStack" properties are indices into this array.  See
+   * actors/utils/stack.js for more details.
+   * @return array
+   */
+  getFrames: function() {
+    return this._frames;
+  },
+
+  /**
    * Updates the views to show or hide the memory recording data.
    */
   updateMemoryRecording: Task.async(function*() {
     if ($("#memory-checkbox").checked) {
       yield TimelineView.showMemoryOverview();
     } else {
       yield TimelineView.hideMemoryOverview();
     }
@@ -158,16 +172,17 @@ let TimelineController = {
     // even when the actor is not generating data.  To do this we get
     // the local time and use it to compute a reasonable elapsed time.
     // See _onRecordingTick.
     this._localStartTime = performance.now();
     this._startTime = startTime;
     this._endTime = startTime;
     this._markers = [];
     this._memory = [];
+    this._frames = [];
     this._updateId = setInterval(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
   },
 
   /**
    * Stops the recording, updating the UI as needed.
    */
   _stopRecording: function*() {
     clearInterval(this._updateId);
@@ -222,16 +237,28 @@ let TimelineController = {
    * @param object measurement
    *        A detailed breakdown of the current memory usage.
    */
   _onMemory: function(delta, measurement) {
     this._memory.push({ delta, value: measurement.total / 1024 / 1024 });
   },
 
   /**
+   * Callback handling the "frames" event on the timeline front.
+   *
+   * @param number delta
+   *        The number of milliseconds elapsed since epoch.
+   * @param object frames
+   *        Newly generated frame objects.
+   */
+  _onFrames: function(delta, frames) {
+    Array.prototype.push.apply(this._frames, frames);
+  },
+
+  /**
    * Callback invoked at a fixed interval while recording.
    * Updates the current time and the timeline overview.
    */
   _onRecordingTick: function() {
     // Compute an approximate ending time for the view.  This is
     // needed to ensure that the view updates even when new data is
     // not being generated.
     let fakeTime = this._startTime + (performance.now() - this._localStartTime);
@@ -313,17 +340,21 @@ let TimelineView = {
     this.memoryOverview = null;
   },
 
   /**
    * A marker has been selected in the waterfall.
    */
   _onMarkerSelected: function(event, marker) {
     if (event == "selected") {
-      this.markerDetails.render(marker);
+      this.markerDetails.render({
+        toolbox: gToolbox,
+        marker: marker,
+        frames: TimelineController.getFrames()
+      });
     }
     if (event == "unselected") {
       this.markerDetails.empty();
     }
   },
 
   /**
    * Signals that a recording session has started and triggers the appropriate
--- a/browser/devtools/timeline/widgets/marker-details.js
+++ b/browser/devtools/timeline/widgets/marker-details.js
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 let { Ci } = require("chrome");
+let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
 
 /**
  * This file contains the rendering code for the marker sidebar.
  */
 
 loader.lazyRequireGetter(this, "L10N",
   "devtools/timeline/global", true);
 loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
@@ -98,20 +99,23 @@ MarkerDetails.prototype = {
     hbox.appendChild(labelName);
     hbox.appendChild(labelValue);
     return hbox;
   },
 
   /**
    * Populates view with marker's details.
    *
-   * @param object marker
-   *        The marker to display.
+   * @param object params
+   *        An options object holding:
+   *        toolbox - The toolbox.
+   *        marker - The marker to display.
+   *        frames - Array of stack frame information; see stack.js.
    */
-  render: function(marker) {
+  render: function({toolbox: toolbox, marker: marker, frames: frames}) {
     this.empty();
 
     // UI for any marker
 
     let title = this.buildMarkerTypeLabel(marker.name);
 
     let toMs = ms => L10N.getFormatStrWithNumbers("timeline.tick", ms);
 
@@ -134,16 +138,94 @@ MarkerDetails.prototype = {
       case "ConsoleTime":
         this.renderConsoleTimeMarker(this._parent, marker);
         break;
       case "DOMEvent":
         this.renderDOMEventMarker(this._parent, marker);
         break;
       default:
     }
+
+    if (marker.stack) {
+      let property = "timeline.markerDetail.stack";
+      if (marker.endStack) {
+        property = "timeline.markerDetail.startStack";
+      }
+      this.renderStackTrace({toolbox: toolbox, parent: this._parent, property: property,
+                             frameIndex: marker.stack, frames: frames});
+    }
+
+    if (marker.endStack) {
+      this.renderStackTrace({toolbox: toolbox, parent: this._parent, property: "timeline.markerDetail.endStack",
+                             frameIndex: marker.endStack, frames: frames});
+    }
+  },
+
+  /**
+   * Render a stack trace.
+   *
+   * @param object params
+   *        An options object with the following members:
+   *        object toolbox - The toolbox.
+   *        nsIDOMNode parent - The parent node holding the view.
+   *        string property - String identifier for label's name.
+   *        integer frameIndex - The index of the topmost stack frame.
+   *        array frames - Array of stack frames.
+   */
+  renderStackTrace: function({toolbox: toolbox, parent: parent,
+                              property: property, frameIndex: frameIndex,
+                              frames: frames}) {
+    let labelName = this._document.createElement("label");
+    labelName.className = "plain marker-details-labelname";
+    labelName.setAttribute("value", L10N.getStr(property));
+    parent.appendChild(labelName);
+
+    while (frameIndex > 0) {
+      let frame = frames[frameIndex];
+      let url = frame.source;
+      let displayName = frame.functionDisplayName;
+      let line = frame.line;
+
+      let hbox = this._document.createElement("hbox");
+
+      if (displayName) {
+        let functionLabel = this._document.createElement("label");
+        functionLabel.setAttribute("value", displayName);
+        hbox.appendChild(functionLabel);
+      }
+
+      if (url) {
+        let aNode = this._document.createElement("a");
+        aNode.className = "waterfall-marker-location theme-link devtools-monospace";
+        aNode.href = url;
+        aNode.draggable = false;
+        aNode.setAttribute("title", url);
+
+        let text = WebConsoleUtils.abbreviateSourceURL(url) + ":" + line;
+        let label = this._document.createElement("label");
+        label.setAttribute("value", text);
+        aNode.appendChild(label);
+        hbox.appendChild(aNode);
+
+        aNode.addEventListener("click", (event) => {
+          event.preventDefault();
+          viewSourceInDebugger(toolbox, url, line);
+        });
+      }
+
+      if (!displayName && !url) {
+        let label = this._document.createElement("label");
+        label.setAttribute("value", L10N.getStr("timeline.markerDetail.unknownFrame"));
+        hbox.appendChild(label);
+      }
+
+      parent.appendChild(hbox);
+
+      frameIndex = frame.parent;
+    }
   },
 
   /**
    * Render details of a console marker (console.time).
    *
    * @param nsIDOMNode parent
    *        The parent node holding the view.
    * @param object marker
@@ -180,11 +262,41 @@ MarkerDetails.prototype = {
       if (marker.eventPhase == Ci.nsIDOMEvent.BUBBLING_PHASE) {
         phaseL10NProp = "timeline.markerDetail.DOMEventBubblingPhase";
       }
       let phase = this.buildNameValueLabel("timeline.markerDetail.DOMEventPhase", L10N.getStr(phaseL10NProp));
       this._parent.appendChild(phase);
     }
   },
 
-}
+};
+
+/**
+ * Opens/selects the debugger in this toolbox and jumps to the specified
+ * file name and line number.
+ * @param object toolbox
+ *        The toolbox.
+ * @param string url
+ * @param number line
+ */
+let viewSourceInDebugger = Task.async(function *(toolbox, url, line) {
+  // If the Debugger was already open, switch to it and try to show the
+  // source immediately. Otherwise, initialize it and wait for the sources
+  // to be added first.
+  let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger");
+  let { panelWin: dbg } = yield toolbox.selectTool("jsdebugger");
+
+  if (!debuggerAlreadyOpen) {
+    yield dbg.once(dbg.EVENTS.SOURCES_ADDED);
+  }
+
+  let { DebuggerView } = dbg;
+  let { Sources } = DebuggerView;
+
+  let item = Sources.getItemForAttachment(a => a.source.url === url);
+  if (item) {
+    return DebuggerView.setEditorLocation(item.attachment.source.actor, line, { noDebug: true });
+  }
+
+  return Promise.reject("Couldn't find the specified source in the debugger.");
+});
 
 exports.MarkerDetails = MarkerDetails;
--- a/browser/devtools/webaudioeditor/test/browser_wa_destroy-node-01.js
+++ b/browser/devtools/webaudioeditor/test/browser_wa_destroy-node-01.js
@@ -5,16 +5,20 @@
  * Tests that the destruction node event is fired and that the nodes are no
  * longer stored internally in the tool, that the graph is updated properly, and
  * that selecting a soon-to-be dead node clears the inspector.
  *
  * All done in one test since this test takes a few seconds to clear GC.
  */
 
 add_task(function*() {
+  // Use a longer timeout as garbage collection event
+  // can be unpredictable.
+  requestLongerTimeout(2);
+
   let { target, panel } = yield initWebAudioEditor(DESTROY_NODES_URL);
   let { panelWin } = panel;
   let { gFront, $, $$, gAudioNodes } = panelWin;
 
   let started = once(gFront, "start-context");
 
   reload(target);
 
--- a/browser/devtools/webide/modules/app-manager.js
+++ b/browser/devtools/webide/modules/app-manager.js
@@ -18,16 +18,17 @@ const {ConnectionManager, Connection} = 
 const {AppActorFront} = require("devtools/app-actor-front");
 const {getDeviceFront} = require("devtools/server/actors/device");
 const {getPreferenceFront} = require("devtools/server/actors/preference");
 const {setTimeout} = require("sdk/timers");
 const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 const {RuntimeScanners, RuntimeTypes} = require("devtools/webide/runtimes");
 const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const Telemetry = require("devtools/shared/telemetry");
+const {ProjectBuilding} = require("./build");
 
 const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
 
 let AppManager = exports.AppManager = {
 
   // FIXME: will break when devtools/app-manager will be removed:
   DEFAULT_PROJECT_ICON: "chrome://browser/skin/devtools/app-manager/default-app-icon.png",
   DEFAULT_PROJECT_NAME: "--",
@@ -448,33 +449,38 @@ let AppManager = exports.AppManager = {
     if (!this._appsFront) {
       console.error("Runtime doesn't have a webappsActor");
       return promise.reject("Can't install");
     }
 
     return Task.spawn(function* () {
       let self = AppManager;
 
+      let packageDir = yield ProjectBuilding.build(project);
+
       yield self.validateProject(project);
 
       if (project.errorsCount > 0) {
         self.reportError("error_cantInstallValidationErrors");
         return;
       }
 
       let installPromise;
 
       if (project.type != "packaged" && project.type != "hosted") {
         return promise.reject("Don't know how to install project");
       }
 
       let response;
       if (project.type == "packaged") {
-        response = yield self._appsFront.installPackaged(project.location,
-                                                             project.packagedAppOrigin);
+        packageDir = packageDir || project.location;
+        console.log("Installing app from " + packageDir);
+
+        response = yield self._appsFront.installPackaged(packageDir,
+                                                         project.packagedAppOrigin);
 
         // If the packaged app specified a custom origin override,
         // we need to update the local project origin
         project.packagedAppOrigin = response.appId;
         // And ensure the indexed db on disk is also updated
         AppProjects.update(project);
       }
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/modules/build.js
@@ -0,0 +1,152 @@
+/* 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/. */
+
+const {Cu, Cc, Ci} = require("chrome");
+
+const promise = require("promise");
+const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+const { TextDecoder, OS }  = Cu.import("resource://gre/modules/osfile.jsm", {});
+const Subprocess = require("sdk/system/child_process/subprocess");
+
+const ProjectBuilding = exports.ProjectBuilding = {
+  fetchPackageManifest: Task.async(function * (project) {
+    let manifestPath = OS.Path.join(project.location, "package.json");
+    let exists = yield OS.File.exists(manifestPath);
+    if (!exists) {
+      return;
+    }
+
+    let Decoder = new TextDecoder();
+    let data = yield OS.File.read(manifestPath);
+    data = new TextDecoder().decode(data);
+    let manifest;
+    try {
+      manifest = JSON.parse(data);
+    } catch(e) {
+      throw new Error("Error while reading WebIDE manifest at: '" + manifestPath +
+                      "', invalid JSON: " + e.message);
+    }
+    return manifest;
+  }),
+
+  // If the app depends on some build step, run it before pushing the app
+  build: Task.async(function* (project) {
+    let manifest = yield ProjectBuilding.fetchPackageManifest(project);
+    if (!manifest || !manifest.webide || !manifest.webide.prepackage) {
+      return;
+    }
+
+    // Look for `webide` property
+    manifest = manifest.webide;
+
+    let command, cwd, args = [], env = [];
+
+    if (typeof(manifest.prepackage) === "string") {
+      command = manifest.prepackage.replace(/%project%/g, project.location);
+    } else if (manifest.prepackage.command) {
+      command = manifest.prepackage.command;
+
+      args = manifest.prepackage.args || [];
+      args = args.map(a => a.replace(/%project%/g, project.location));
+
+      env = manifest.prepackage.env || [];
+      env = env.map(a => a.replace(/%project%/g, project.location));
+
+      // Gaia build system crashes if HOME env variable isn't set...
+      let envService = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+      let home = envService.get("HOME");
+      if (home) {
+        env.push("HOME=" + home);
+      }
+
+      if (manifest.prepackage.cwd) {
+        // Normalize path for Windows support (converts / to \)
+        let path = OS.Path.normalize(manifest.prepackage.cwd);
+        // Note that Path.join also support absolute path and argument.
+        // So that if cwd is absolute, it will return cwd.
+        let rel = OS.Path.join(project.location, path);
+        let exists = yield OS.File.exists(rel);
+        if (exists) {
+          cwd = rel;
+        }
+      }
+    } else {
+      throw new Error("pre-package manifest is invalid, missing or invalid " +
+                      "`prepackage` attribute");
+    }
+
+    if (!cwd) {
+      cwd = project.location;
+    }
+
+    console.log("Running pre-package hook '" + command + "' " +
+                args.join(" ") +
+                " with ENV=[" + env.join(", ") + "]" +
+                " at " + cwd);
+
+    // Run the command through a shell command in order to support non absolute
+    // paths.
+    // On Windows `ComSpec` env variable is going to refer to cmd.exe,
+    // Otherwise, on Linux and Mac, SHELL env variable should refer to
+    // the user chosen shell program.
+    // (We do not check for OS, as on windows, with cygwin, ComSpec isn't set)
+    let envService = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+    let shell = envService.get("ComSpec") || envService.get("SHELL");
+    args.unshift(command);
+
+    // For cmd.exe, we have to pass the `/C` option,
+    // but for unix shells we need -c.
+    // That to interpret next argument as a shell command.
+    if (envService.exists("ComSpec")) {
+      args.unshift("/C");
+    } else {
+      args.unshift("-c");
+    }
+
+    // Subprocess changes CWD, we have to save and restore it.
+    let originalCwd = yield OS.File.getCurrentDirectory();
+    try {
+      let defer = promise.defer();
+      Subprocess.call({
+        command: shell,
+        arguments: args,
+        environment: env,
+        workdir: cwd,
+
+        stdout: data =>
+          console.log("pre-package: " + data),
+        stderr: data =>
+          console.error("pre-package: " + data),
+
+        done: result => {
+          console.log("pre-package: Terminated with error code: " + result.exitCode);
+          if (result.exitCode == 0) {
+            defer.resolve();
+          } else {
+            defer.reject("pre-package command failed with error code " + result.exitCode);
+          }
+        }
+      });
+      defer.promise.then(() => {
+        OS.File.setCurrentDirectory(originalCwd);
+      });
+      yield defer.promise;
+    } catch (e) {
+      throw new Error("Unable to run pre-package command '" + command + "' " +
+                      args.join(" ") + ":\n" + (e.message || e));
+    }
+
+    if (manifest.packageDir) {
+      let packageDir = OS.Path.join(project.location, manifest.packageDir);
+      // On Windows, replace / by \\
+      packageDir = OS.Path.normalize(packageDir);
+      let exists = yield OS.File.exists(packageDir);
+      if (exists) {
+        return packageDir;
+      }
+      throw new Error("Unable to resolve application package directory: '" + manifest.packageDir + "'");
+    }
+  }),
+};
+
--- a/browser/devtools/webide/moz.build
+++ b/browser/devtools/webide/moz.build
@@ -11,13 +11,14 @@ DIRS += [
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
 EXTRA_JS_MODULES.devtools.webide += [
     'modules/addons.js',
     'modules/app-manager.js',
+    'modules/build.js',
     'modules/remote-resources.js',
     'modules/runtimes.js',
     'modules/tab-store.js',
     'modules/utils.js',
 ]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/test/build_app1/package.json
@@ -0,0 +1,5 @@
+{
+  "webide": {
+    "prepackage": "echo \"{\\\"name\\\":\\\"hello\\\"}\" > manifest.webapp"
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/test/build_app2/manifest.webapp
@@ -0,0 +1,1 @@
+{}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/test/build_app2/package.json
@@ -0,0 +1,10 @@
+{
+  "webide": {
+    "prepackage": {
+      "command": "echo \"{\\\"name\\\":\\\"$NAME\\\"}\" > manifest.webapp",
+      "cwd": "./stage",
+      "env": ["NAME=world"]
+    },
+    "packageDir": "./stage"
+  }
+}
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/test/build_app_windows1/package.json
@@ -0,0 +1,5 @@
+{
+  "webide": {
+    "prepackage": "echo {\"name\":\"hello\"} > manifest.webapp"
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/test/build_app_windows2/manifest.webapp
@@ -0,0 +1,1 @@
+{}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/test/build_app_windows2/package.json
@@ -0,0 +1,10 @@
+{
+  "webide": {
+    "prepackage": {
+      "command": "echo {\"name\":\"%NAME%\"} > manifest.webapp",
+      "cwd": "./stage",
+      "env": ["NAME=world"]
+    },
+    "packageDir": "./stage"
+  }
+}
new file mode 100644
--- a/browser/devtools/webide/test/chrome.ini
+++ b/browser/devtools/webide/test/chrome.ini
@@ -19,16 +19,24 @@ support-files =
   addons/adbhelper-linux.xpi
   addons/adbhelper-linux64.xpi
   addons/adbhelper-win32.xpi
   addons/adbhelper-mac64.xpi
   addons/fxdt-adapters-linux32.xpi
   addons/fxdt-adapters-linux64.xpi
   addons/fxdt-adapters-win32.xpi
   addons/fxdt-adapters-mac64.xpi
+  build_app1/package.json
+  build_app2/manifest.webapp
+  build_app2/package.json
+  build_app2/stage/empty-directory
+  build_app_windows1/package.json
+  build_app_windows2/manifest.webapp
+  build_app_windows2/package.json
+  build_app_windows2/stage/empty-directory
   head.js
   hosted_app.manifest
   templates.json
 
 [test_basic.html]
 [test_newapp.html]
 [test_import.html]
 [test_duplicate_import.html]
@@ -36,8 +44,9 @@ support-files =
 [test_manifestUpdate.html]
 [test_addons.html]
 [test_deviceinfo.html]
 [test_autoconnect_runtime.html]
 [test_telemetry.html]
 [test_device_preferences.html]
 [test_fullscreenToolbox.html]
 [test_zoom.html]
+[test_build.html]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/test/test_build.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+
+<html>
+
+  <head>
+    <meta charset="utf8">
+    <title></title>
+
+    <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+    <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
+    <script type="application/javascript;version=1.8" src="head.js"></script>
+    <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  </head>
+
+  <body>
+
+    <script type="application/javascript;version=1.8">
+      window.onload = function() {
+        SimpleTest.waitForExplicitFinish();
+
+        let {TextDecoder, OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
+        let {ProjectBuilding} = require("devtools/webide/build");
+
+        Task.spawn(function* () {
+            let win = yield openWebIDE();
+            let AppManager = win.AppManager;
+
+            function isProjectMarkedAsValid() {
+              let details = win.UI.projecteditor.window.frames[0];
+              return !details.document.body.classList.contains("error");
+            }
+
+            // # Test first package.json like this: `{webide: {prepackage: "command line string"}}`
+            let platform = Services.appShell.hiddenDOMWindow.navigator.platform;
+            let testSuffix = "";
+            if (platform.indexOf("Win") != -1) {
+              testSuffix = "_windows";
+            }
+
+            let packagedAppLocation = getTestFilePath("build_app" + testSuffix + "1");
+
+            yield win.Cmds.importPackagedApp(packagedAppLocation);
+
+            let project = win.AppManager.selectedProject;
+
+            let deferred = promise.defer();
+            win.UI.projecteditor.once("onEditorCreated", deferred.resolve);
+            yield deferred.promise;
+
+            ok(!project.manifest, "manifest includes name");
+            is(project.name, "--", "Display name uses manifest name");
+
+            let packageDir = yield ProjectBuilding.build(project);
+            ok(!packageDir, "no custom packagedir");
+
+            // Trigger validation
+            yield AppManager.validateProject(AppManager.selectedProject);
+            yield nextTick();
+
+            ok("name" in project.manifest, "manifest includes name");
+            is(project.name, "hello", "Display name uses manifest name");
+            is(project.manifest.name, project.name, "Display name uses manifest name");
+
+            yield OS.File.remove(OS.Path.join(packagedAppLocation, "manifest.webapp"));
+
+            // # Now test a full featured package.json
+            packagedAppLocation = getTestFilePath("build_app" + testSuffix + "2");
+
+            yield win.Cmds.importPackagedApp(packagedAppLocation);
+
+            project = win.AppManager.selectedProject;
+
+            packageDir = yield ProjectBuilding.build(project);
+            is(OS.Path.normalize(packageDir),
+               OS.Path.join(packagedAppLocation, "stage"), "custom packagedir");
+
+            // Switch to the package dir in order to verify the generated webapp.manifest
+            yield win.Cmds.importPackagedApp(packageDir);
+
+            project = win.AppManager.selectedProject;
+
+            ok("name" in project.manifest, "manifest includes name");
+            is(project.name, "world", "Display name uses manifest name");
+            is(project.manifest.name, project.name, "Display name uses manifest name");
+
+            yield closeWebIDE(win);
+
+            yield removeAllProjects();
+
+            SimpleTest.finish();
+        });
+      }
+
+
+    </script>
+  </body>
+</html>
+
+
--- a/browser/locales/en-US/chrome/browser/devtools/timeline.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/timeline.properties
@@ -60,8 +60,12 @@ timeline.markerDetail.start=Start:
 timeline.markerDetail.end=End:
 timeline.markerDetail.duration=Duration:
 timeline.markerDetail.consoleTimerName=Timer Name:
 timeline.markerDetail.DOMEventType=Event Type:
 timeline.markerDetail.DOMEventPhase=Phase:
 timeline.markerDetail.DOMEventTargetPhase=Target
 timeline.markerDetail.DOMEventCapturingPhase=Capture
 timeline.markerDetail.DOMEventBubblingPhase=Bubbling
+timeline.markerDetail.stack=Stack:
+timeline.markerDetail.startStack=Stack at start:
+timeline.markerDetail.endStack=Stack at end:
+timeline.markerDetail.unknownFrame=<unknown location>
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.dtd
@@ -19,12 +19,10 @@
 <!ENTITY  paneContent.title       "Content">
 <!ENTITY  paneApplications.title  "Applications">
 <!ENTITY  panePrivacy.title       "Privacy">
 <!ENTITY  paneSecurity.title      "Security">
 <!ENTITY  paneAdvanced.title      "Advanced">
 
 <!-- LOCALIZATION NOTE (paneSync.title): This should match syncBrand.shortName.label in ../syncBrand.dtd -->
 <!ENTITY  paneSync.title          "Sync">
-<!ENTITY  buttonForward.tooltip   "Go forward one page">
-<!ENTITY  buttonBack.tooltip      "Go back one page">
 
 <!ENTITY  helpButton.label        "Help">
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -408,8 +408,10 @@ browser.jar:
   skin/classic/browser/devtools/tooltip/arrow-vertical-dark@2x.png   (../shared/devtools/tooltip/arrow-vertical-dark@2x.png)
   skin/classic/browser/devtools/tooltip/arrow-horizontal-light.png   (../shared/devtools/tooltip/arrow-horizontal-light.png)
   skin/classic/browser/devtools/tooltip/arrow-horizontal-light@2x.png   (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
   skin/classic/browser/devtools/tooltip/arrow-vertical-light.png   (../shared/devtools/tooltip/arrow-vertical-light.png)
   skin/classic/browser/devtools/tooltip/arrow-vertical-light@2x.png   (../shared/devtools/tooltip/arrow-vertical-light@2x.png)
 #ifdef E10S_TESTING_ONLY
   skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
 #endif
+  skin/classic/browser/warning16.png                        (../shared/warning16.png)
+  skin/classic/browser/warning16@2x.png                     (../shared/warning16@2x.png)
--- a/browser/themes/osx/customizableui/panelUIOverlay.css
+++ b/browser/themes/osx/customizableui/panelUIOverlay.css
@@ -29,16 +29,22 @@
   #PanelUI-fxa-status {
     list-style-image: url(chrome://browser/skin/sync-horizontalbar@2x.png);
   }
 
   #PanelUI-fxa-status[syncstatus="active"] {
     list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar@2x.png);
   }
 
+  #PanelUI-fxa-status[fxastatus="migrate-signup"],
+  #PanelUI-fxa-status[fxastatus="migrate-verify"] {
+    list-style-image: url(chrome://browser/skin/warning16@2x.png);
+    -moz-image-region: rect(0, 64px, 32px, 32px);
+  }
+
   #PanelUI-customize {
     list-style-image: url(chrome://browser/skin/menuPanel-customize@2x.png);
   }
 
   #main-window[customize-entered] #PanelUI-customize {
     list-style-image: url(chrome://browser/skin/customizableui/menuPanel-customizeFinish@2x.png);
   }
 
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -571,16 +571,18 @@ browser.jar:
   skin/classic/browser/devtools/tooltip/arrow-vertical-dark@2x.png   (../shared/devtools/tooltip/arrow-vertical-dark@2x.png)
   skin/classic/browser/devtools/tooltip/arrow-horizontal-light.png   (../shared/devtools/tooltip/arrow-horizontal-light.png)
   skin/classic/browser/devtools/tooltip/arrow-horizontal-light@2x.png   (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
   skin/classic/browser/devtools/tooltip/arrow-vertical-light.png   (../shared/devtools/tooltip/arrow-vertical-light.png)
   skin/classic/browser/devtools/tooltip/arrow-vertical-light@2x.png   (../shared/devtools/tooltip/arrow-vertical-light@2x.png)
 #ifdef E10S_TESTING_ONLY
   skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
 #endif
+  skin/classic/browser/warning16.png                        (../shared/warning16.png)
+  skin/classic/browser/warning16@2x.png                     (../shared/warning16@2x.png)
 
 % override chrome://browser/skin/toolbarbutton-dropmarker.png              chrome://browser/skin/lion/toolbarbutton-dropmarker.png                 os=Darwin osversion>=10.7
 % override chrome://browser/skin/tabbrowser/alltabs-box-bkgnd-icon.png     chrome://browser/skin/lion/tabbrowser/alltabs-box-bkgnd-icon.png        os=Darwin osversion>=10.7
 % override chrome://browser/skin/tabview/tabview.png                       chrome://browser/skin/lion/tabview/tabview.png                          os=Darwin osversion>=10.7
 % override chrome://browser/skin/places/toolbar.png                        chrome://browser/skin/lion/places/toolbar.png                           os=Darwin osversion>=10.7
 % override chrome://browser/skin/Toolbar.png                               chrome://browser/skin/yosemite/Toolbar.png                              os=Darwin osversion>=10.10
 % override chrome://browser/skin/Toolbar@2x.png                            chrome://browser/skin/yosemite/Toolbar@2x.png                           os=Darwin osversion>=10.10
 % override chrome://browser/skin/menuPanel.png                             chrome://browser/skin/yosemite/menuPanel.png                            os=Darwin osversion>=10.10
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -569,16 +569,22 @@ toolbarpaletteitem[place="palette"] > to
 #PanelUI-fxa-status {
   list-style-image: url(chrome://browser/skin/sync-horizontalbar.png);
 }
 
 #PanelUI-fxa-status[syncstatus="active"] {
   list-style-image: url(chrome://browser/skin/syncProgress-horizontalbar.png);
 }
 
+#PanelUI-fxa-status[fxastatus="migrate-signup"],
+#PanelUI-fxa-status[fxastatus="migrate-verify"] {
+  list-style-image: url(chrome://browser/skin/warning16.png);
+  -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
 #PanelUI-customize {
   list-style-image: url(chrome://browser/skin/menuPanel-customize.png);
 }
 
 #customization-panelHolder #PanelUI-customize {
   list-style-image: url(chrome://browser/skin/customizableui/menuPanel-customizeFinish.png);
 }
 
--- a/browser/themes/shared/devtools/performance.inc.css
+++ b/browser/themes/shared/devtools/performance.inc.css
@@ -314,16 +314,25 @@
   color: var(--theme-selection-color);
 }
 
 .waterfall-marker-container.selected .waterfall-marker-bullet,
 .waterfall-marker-container.selected .waterfall-marker-bar {
   border-color: initial !important;
 }
 
+.waterfall-marker-location {
+   color: -moz-nativehyperlinktext;
+}
+
+.waterfall-marker-location:hover,
+.waterfall-marker-location:focus {
+   text-decoration: underline;
+}
+
 #waterfall-details {
   -moz-padding-start: 8px;
   -moz-padding-end: 8px;
   padding-top: 8vh;
   overflow: auto;
 }
 
 .marker-details-bullet {
--- a/browser/themes/shared/devtools/timeline.inc.css
+++ b/browser/themes/shared/devtools/timeline.inc.css
@@ -170,16 +170,25 @@
   color: #f5f7fa; /* Light foreground text */
 }
 
 .waterfall-marker-container.selected .waterfall-marker-bullet,
 .waterfall-marker-container.selected .waterfall-marker-bar {
   border-color: initial!important;
 }
 
+.waterfall-marker-location {
+   color: -moz-nativehyperlinktext;
+}
+
+.waterfall-marker-location:hover,
+.waterfall-marker-location:focus {
+   text-decoration: underline;
+}
+
 #timeline-waterfall-details {
   -moz-padding-start: 8px;
   -moz-padding-end: 8px;
   padding-top: 8vh;
   overflow: auto;
 }
 
 .marker-details-bullet {
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -228,23 +228,24 @@ description > html|a {
 
 #dialogOverlay {
   background-color: rgba(0,0,0,0.5);
   visibility: hidden;
 }
 
 #dialogBox {
   background-color: #fbfbfb;
+  background-clip: content-box;
   color: #424e5a;
   font-size: 14px;
   /* `transparent` will use the dialogText color in high-contrast themes and
      when page colors are disabled */
   border: 1px solid transparent;
-  border-radius: 2.5px;
-  box-shadow: 0 2px 4px 0 rgba(0,0,0,0.5);
+  border-radius: 3.5px;
+  box-shadow: 0 2px 6px 0 rgba(0,0,0,0.3);
   display: -moz-box;
   margin: 0;
   padding: 0;
 }
 
 #dialogBox[resizable="true"] {
   resize: both;
   overflow: hidden;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..46200cec5cf0883e5b09b820c5091decabb31890
GIT binary patch
literal 445
zc$@*f0Yd(XP)<h;3K|Lk000e1NJLTq001BW000mO1ONa4-3hhQ0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzUr9tkR5(wq$uVofKor371HXWdQqV=y
zK@=1RyQJU8$vwoNVvvOR0YWqtAq|p-sC1F~HQYr6XX&D;dA%IxVeMsV?+^Fh<A>0H
zzD0Ji!967TFgx7f5*^LQbHKeM`EWc0T#^Su5C(t?gGZhlf)N^k3k|L#-|@k3`Yrsw
z@?3Xx(3^S-|2+3f-o^XEGvxu^%J;67w2OvaOKE_&Uyf$p3~=dLhRnBnA9w@6r3V=@
z@7=AskqfxIn<F!FYqzde)kp<gUd@pisXrV!O9x05r$D#&TkkLXK&tc$bi4IwV+u5J
zT4wvx)w_)@(8O+;?aa|+H33c^SMRJk!0GC?Xaj9$3wF`ovimu9MO1*cQv{``Y}rj5
zjn*Te+2l6ay^8Plx(zhqZIj)^k#)3ycChMJ&Dyj3Gj{nX1MMK!<(j-_H*sY4%##V6
n&ZZi>)RRsUa5_sW`uqC^P2*`ACv4$T00000NkvXXu0mjf5Gc$u
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d1426d381a33a38f25a4b825852a89878e95444d
GIT binary patch
literal 583
zc$@)80=WH&P)<h;3K|Lk000e1NJLTq002M$001Be1ONa42$Z%D0006GNkl<ZcwWWO
zPe@i-9LM43USFgFBV9<yP*^L8qKz;NVgGU0%7P?<lKzR!;7Ego@iG>@8A91Ep_*B=
zw4z<0ZK{R42--9X3Nzpgw1^f(pw8;RdpX>NoQ2Qg>^_Gd-)z<T%3eq_Crz##S1egF
zEK@4AuPE2=4ygHRLW$JAvZ};pHw0CG_^d=)pDQz{MbK<WiL}0affj@6{bIr=F{yp_
zOboYNRWxQ;%zQ#$zCf$Xpr$5w-4F)c^{+^3pG}DwH-x`!w}M7XnsLvd$b4cSFF>1q
z@X-qRZcZmI8uyP#S|1<4H5)-LSOy!6xTHvGA0M#IIlyK?6X2{d3kp*E_<#|OfFA^H
zfJWE#DM;(%19mziXw~EkwrCcdcH3V9r1kLuRTTm4-lJVWMb#+<34L@xhhFt|L?3Wc
z%?E*$K0ctTLA_m`qeH!1)d_*rzOo{)$8klw!&}f}uXg|meYC)+T+w0eIHV{u>X;z0
z&;A7HG%FJ+@uOK_A+Zk&@cSBaMs)+JefAjm$y>2E?qfigSeMtZkl2R>Zi>z5wv4A{
z>{rAM2Ozc29>PJd#Cl8tdOZ<4YMRBQJ}mH?7@6Rk7{8h(wa-D97#B2v+<B2urS)Nf
zFE)uC@zSJKKien9H%RTXNvIjNcH^$FnAC>_s+RTHx`y~ak5nPG&mKs#C{1pi{SU)~
V(}ZC)5f1<W002ovPDHLkV1jdM2K@j4
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -446,16 +446,18 @@ browser.jar:
         skin/classic/browser/devtools/tooltip/arrow-vertical-dark@2x.png   (../shared/devtools/tooltip/arrow-vertical-dark@2x.png)
         skin/classic/browser/devtools/tooltip/arrow-horizontal-light.png   (../shared/devtools/tooltip/arrow-horizontal-light.png)
         skin/classic/browser/devtools/tooltip/arrow-horizontal-light@2x.png   (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
         skin/classic/browser/devtools/tooltip/arrow-vertical-light.png   (../shared/devtools/tooltip/arrow-vertical-light.png)
         skin/classic/browser/devtools/tooltip/arrow-vertical-light@2x.png   (../shared/devtools/tooltip/arrow-vertical-light@2x.png)
 #ifdef E10S_TESTING_ONLY
         skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
 #endif
+        skin/classic/browser/warning16.png                            (../shared/warning16.png)
+        skin/classic/browser/warning16@2x.png                         (../shared/warning16@2x.png)
 
 #ifdef XP_WIN
 browser.jar:
 % skin browser classic/1.0 %skin/classic/aero/browser/ os=WINNT osversion>=6
         skin/classic/aero/browser/sanitizeDialog.css                       (sanitizeDialog.css)
 *       skin/classic/aero/browser/aboutSessionRestore.css            (aboutSessionRestore.css)
         skin/classic/aero/browser/aboutSessionRestore-window-icon.png (aboutSessionRestore-window-icon-aero.png)
         skin/classic/aero/browser/aboutCertError.css
@@ -903,16 +905,18 @@ browser.jar:
         skin/classic/aero/browser/devtools/tooltip/arrow-vertical-dark@2x.png   (../shared/devtools/tooltip/arrow-vertical-dark@2x.png)
         skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-light.png   (../shared/devtools/tooltip/arrow-horizontal-light.png)
         skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-light@2x.png   (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
         skin/classic/aero/browser/devtools/tooltip/arrow-vertical-light.png   (../shared/devtools/tooltip/arrow-vertical-light.png)
         skin/classic/aero/browser/devtools/tooltip/arrow-vertical-light@2x.png   (../shared/devtools/tooltip/arrow-vertical-light@2x.png)
 #ifdef E10S_TESTING_ONLY
         skin/classic/aero/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
 #endif
+        skin/classic/aero/browser/warning16.png                       (../shared/warning16.png)
+        skin/classic/aero/browser/warning16@2x.png                    (../shared/warning16@2x.png)
 
 % override chrome://browser/skin/Toolbar.png               chrome://browser/skin/Toolbar-aero.png                  os=WINNT osversion=6
 % override chrome://browser/skin/Toolbar.png               chrome://browser/skin/Toolbar-aero.png                  os=WINNT osversion=6.1
 
 % override chrome://browser/skin/menuPanel.png             chrome://browser/skin/menuPanel-aero.png                os=WINNT osversion=6
 % override chrome://browser/skin/menuPanel.png             chrome://browser/skin/menuPanel-aero.png                os=WINNT osversion=6.1
 
 % override chrome://browser/skin/menuPanel-small.png       chrome://browser/skin/menuPanel-small-aero.png          os=WINNT osversion=6
--- a/configure.in
+++ b/configure.in
@@ -45,17 +45,17 @@ dnl ====================================
 _SUBDIR_HOST_CFLAGS="$HOST_CFLAGS"
 _SUBDIR_HOST_CXXFLAGS="$HOST_CXXFLAGS"
 _SUBDIR_HOST_LDFLAGS="$HOST_LDFLAGS"
 _SUBDIR_CONFIG_ARGS="$ac_configure_args"
 
 dnl Set the version number of the libs included with mozilla
 dnl ========================================================
 MOZJPEG=62
-MOZPNG=10614
+MOZPNG=10616
 NSPR_VERSION=4
 NSPR_MINVER=4.10.3
 NSS_VERSION=3
 
 dnl Set the minimum version of toolkit libs used by mozilla
 dnl ========================================================
 GLIB_VERSION=1.2.0
 PERL_VERSION=5.006
@@ -2669,21 +2669,17 @@ WINNT|Darwin|Android)
   ;;
 esac
 
 AC_SUBST(WRAP_SYSTEM_INCLUDES)
 AC_SUBST(VISIBILITY_FLAGS)
 
 MOZ_GCC_PR49911
 MOZ_GCC_PR39608
-if test "$OS_TARGET" != WINNT; then
-    # Only run this test with clang on non-Windows platforms, because clang
-    # cannot do enough code gen for now to make this test work correctly.
-    MOZ_LLVM_PR8927
-fi
+MOZ_LLVM_PR8927
 
 dnl Check for __force_align_arg_pointer__ for SSE2 on gcc
 dnl ========================================================
 if test "$GNU_CC"; then
   CFLAGS_save="${CFLAGS}"
   CFLAGS="${CFLAGS} -Werror"
   AC_CACHE_CHECK(for __force_align_arg_pointer__ attribute,
                  ac_cv_force_align_arg_pointer,
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2882,16 +2882,17 @@ nsDocShell::PopProfileTimelineMarkers(JS
   // START marker is found, look for the corresponding END marker and build a
   // {name,start,end} JS object.
   // Paint markers are different because paint is handled at root docShell level
   // in the information that a paint was done is then stored at each sub
   // docShell level but we can only be sure that a paint did happen in a
   // docShell if an Layer marker type was recorded too.
 
   nsTArray<mozilla::dom::ProfileTimelineMarker> profileTimelineMarkers;
+  SequenceRooter<mozilla::dom::ProfileTimelineMarker> rooter(aCx, &profileTimelineMarkers);
 
   // If we see an unpaired START, we keep it around for the next call
   // to PopProfileTimelineMarkers.  We store the kept START objects in
   // this array.
   nsTArray<TimelineMarker*> keptMarkers;
 
   for (uint32_t i = 0; i < mProfileTimelineMarkers.Length(); ++i) {
     TimelineMarker* startPayload = mProfileTimelineMarkers[i];
@@ -2933,27 +2934,28 @@ nsDocShell::PopProfileTimelineMarkers(JS
         if (endPayload->GetMetaData() == TRACING_INTERVAL_START) {
           ++markerDepth;
         } else if (endPayload->GetMetaData() == TRACING_INTERVAL_END) {
           if (markerDepth > 0) {
             --markerDepth;
           } else {
             // But ignore paint start/end if no layer has been painted.
             if (!isPaint || (isPaint && hasSeenPaintedLayer)) {
-              mozilla::dom::ProfileTimelineMarker marker;
-
-              marker.mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
-              marker.mStart = startPayload->GetTime();
-              marker.mEnd = endPayload->GetTime();
+              mozilla::dom::ProfileTimelineMarker* marker =
+                profileTimelineMarkers.AppendElement();
+
+              marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
+              marker->mStart = startPayload->GetTime();
+              marker->mEnd = endPayload->GetTime();
+              marker->mStack = startPayload->GetStack();
               if (isPaint) {
-                marker.mRectangles.Construct(layerRectangles);
-              } else {
-                startPayload->AddDetails(marker);
+                marker->mRectangles.Construct(layerRectangles);
               }
-              profileTimelineMarkers.AppendElement(marker);
+              startPayload->AddDetails(*marker);
+              endPayload->AddDetails(*marker);
             }
 
             // We want the start to be dropped either way.
             hasSeenEnd = true;
 
             break;
           }
         }
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -18,23 +18,25 @@
 #include "nsIContentViewerContainer.h"
 #include "nsIDOMStorageManager.h"
 #include "nsDocLoader.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "GeckoProfiler.h"
 #include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "jsapi.h"
 
 // Helper Classes
 #include "nsCOMPtr.h"
 #include "nsPoint.h" // mCurrent/mDefaultScrollbarPreferences
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
+#include "nsContentUtils.h"
 
 // Threshold value in ms for META refresh based redirects
 #define REFRESH_REDIRECT_TIMER 15000
 
 // Interfaces Needed
 #include "nsIDocCharset.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIRefreshURI.h"
@@ -267,45 +269,54 @@ public:
         TimelineMarker(nsDocShell* aDocShell, const char* aName,
                        TracingMetadata aMetaData)
             : mName(aName)
             , mMetaData(aMetaData)
         {
             MOZ_COUNT_CTOR(TimelineMarker);
             MOZ_ASSERT(aName);
             aDocShell->Now(&mTime);
+            if (aMetaData == TRACING_INTERVAL_START) {
+                CaptureStack();
+            }
         }
 
         TimelineMarker(nsDocShell* aDocShell, const char* aName,
                        TracingMetadata aMetaData,
                        const nsAString& aCause)
             : mName(aName)
             , mMetaData(aMetaData)
             , mCause(aCause)
         {
             MOZ_COUNT_CTOR(TimelineMarker);
             MOZ_ASSERT(aName);
             aDocShell->Now(&mTime);
+            if (aMetaData == TRACING_INTERVAL_START) {
+                CaptureStack();
+            }
         }
 
         virtual ~TimelineMarker()
         {
             MOZ_COUNT_DTOR(TimelineMarker);
         }
 
         // Check whether two markers should be considered the same,
         // for the purpose of pairing start and end markers.  Normally
         // this definition suffices.
         virtual bool Equals(const TimelineMarker* other)
         {
             return strcmp(mName, other->mName) == 0;
         }
 
         // Add details specific to this marker type to aMarker.  The
-        // standard elements have already been set.
+        // standard elements have already been set.  This method is
+        // called on both the starting and ending markers of a pair.
+        // Ordinarily the ending marker doesn't need to do anything
+        // here.
         virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker)
         {
         }
 
         virtual void AddLayerRectangles(mozilla::dom::Sequence<mozilla::dom::ProfileTimelineLayerRect>&)
         {
             MOZ_ASSERT_UNREACHABLE("can only be called on layer markers");
         }
@@ -325,21 +336,52 @@ public:
             return mTime;
         }
 
         const nsString& GetCause() const
         {
             return mCause;
         }
 
+        JSObject* GetStack()
+        {
+            if (mStackTrace) {
+                return mStackTrace->get();
+            }
+            return nullptr;
+        }
+
+    protected:
+
+        void CaptureStack()
+        {
+            JSContext* ctx = nsContentUtils::GetCurrentJSContext();
+            if (ctx) {
+                JS::RootedObject stack(ctx);
+                if (JS::CaptureCurrentStack(ctx, &stack)) {
+                    mStackTrace.emplace(ctx, stack.get());
+                } else {
+                    JS_ClearPendingException(ctx);
+                }
+            }
+        }
+
     private:
+
         const char* mName;
         TracingMetadata mMetaData;
         DOMHighResTimeStamp mTime;
         nsString mCause;
+
+        // While normally it is not a good idea to make a persistent
+        // root, in this case changing nsDocShell to participate in
+        // cycle collection was deemed too invasive, the stack trace
+        // can't actually cause a cycle, and the markers are only held
+        // here temporarily to boot.
+        mozilla::Maybe<JS::PersistentRooted<JSObject*>> mStackTrace;
     };
 
     // Add new profile timeline markers to this docShell. This will only add
     // markers if the docShell is currently recording profile timeline markers.
     // See nsIDocShell::recordProfileTimelineMarkers
     void AddProfileTimelineMarker(const char* aName,
                                   TracingMetadata aMetaData);
     void AddProfileTimelineMarker(mozilla::UniquePtr<TimelineMarker> &aMarker);
--- a/docshell/test/browser/browser.ini
+++ b/docshell/test/browser/browser.ini
@@ -34,16 +34,17 @@ support-files =
   file_bug852909.png
   file_bug1046022.html
   print_postdata.sjs
   test-form_sjis.html
   timelineMarkers-04.html
   browser_timelineMarkers-frame-02.js
   browser_timelineMarkers-frame-03.js
   browser_timelineMarkers-frame-04.js
+  browser_timelineMarkers-frame-05.js
   head.js
   frame-head.js
 
 [browser_bug134911.js]
 skip-if = e10s # Bug ?????? - BrowserSetForcedCharacterSet() in browser.js references docShell
 [browser_bug234628-1.js]
 skip-if = e10s # Bug ?????? - event handler checks event.target is the content document and test e10s-utils doesn't do that.
 [browser_bug234628-10.js]
@@ -100,8 +101,9 @@ skip-if = e10s
 skip-if = e10s # Bug ?????? - event handler checks event.target is the content document and test e10s-utils doesn't do that.
 [browser_onbeforeunload_navigation.js]
 skip-if = e10s
 [browser_search_notification.js]
 [browser_timelineMarkers-01.js]
 [browser_timelineMarkers-02.js]
 [browser_timelineMarkers-03.js]
 [browser_timelineMarkers-04.js]
+[browser_timelineMarkers-05.js]
new file mode 100644
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-05.js
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let URL = '<!DOCTYPE html><style>' +
+          'body {margin:0; padding: 0;} ' +
+          'div {width:100px;height:100px;background:red;} ' +
+          '.resize-change-color {width:50px;height:50px;background:blue;} ' +
+          '.change-color {width:50px;height:50px;background:yellow;} ' +
+          '.add-class {}' +
+          '</style><div></div>';
+URL = "data:text/html;charset=utf8," + encodeURIComponent(URL);
+
+let test = makeTimelineTest("browser_timelineMarkers-frame-05.js", URL);
new file mode 100644
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-frame-05.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function forceSyncReflow(div) {
+  div.setAttribute('class', 'resize-change-color');
+  // Force a reflow.
+  return div.offsetWidth;
+}
+
+function testSendingEvent() {
+  content.document.body.dispatchEvent(new content.Event("dog"));
+}
+
+function testConsoleTime() {
+  content.console.time("cats");
+}
+
+function testConsoleTimeEnd() {
+  content.console.timeEnd("cats");
+}
+
+let TESTS = [{
+  desc: "Stack trace on sync reflow",
+  searchFor: "Reflow",
+  setup: function(docShell) {
+    let div = content.document.querySelector("div");
+    forceSyncReflow(div);
+  },
+  check: function(markers) {
+    markers = markers.filter(m => m.name == "Reflow");
+    ok(markers.length > 0, "Reflow marker includes stack");
+    ok(markers[0].stack.functionDisplayName == "forceSyncReflow");
+  }
+}, {
+  desc: "Stack trace on DOM event",
+  searchFor: "DOMEvent",
+  setup: function(docShell) {
+    content.document.body.addEventListener("dog",
+                                           function(e) { console.log("hi"); },
+                                           true);
+    testSendingEvent();
+  },
+  check: function(markers) {
+    markers = markers.filter(m => m.name == "DOMEvent");
+    ok(markers.length > 0, "DOMEvent marker includes stack");
+    ok(markers[0].stack.functionDisplayName == "testSendingEvent",
+       "testSendingEvent is on the stack");
+  }
+}, {
+  desc: "Stack trace on console event",
+  searchFor: "ConsoleTime",
+  setup: function(docShell) {
+    testConsoleTime();
+    testConsoleTimeEnd();
+  },
+  check: function(markers) {
+    markers = markers.filter(m => m.name == "ConsoleTime");
+    ok(markers.length > 0, "ConsoleTime marker includes stack");
+    ok(markers[0].stack.functionDisplayName == "testConsoleTime",
+       "testConsoleTime is on the stack");
+    ok(markers[0].endStack.functionDisplayName == "testConsoleTimeEnd",
+       "testConsoleTimeEnd is on the stack");
+  }
+}];
+
+timelineContentTest(TESTS);
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -808,30 +808,37 @@ ReifyStack(nsIStackFrame* aStack, nsTArr
 class ConsoleTimelineMarker : public nsDocShell::TimelineMarker
 {
 public:
   ConsoleTimelineMarker(nsDocShell* aDocShell,
                         TracingMetadata aMetaData,
                         const nsAString& aCause)
     : nsDocShell::TimelineMarker(aDocShell, "ConsoleTime", aMetaData, aCause)
   {
+    if (aMetaData == TRACING_INTERVAL_END) {
+      CaptureStack();
+    }
   }
 
   virtual bool Equals(const nsDocShell::TimelineMarker* aOther)
   {
     if (!nsDocShell::TimelineMarker::Equals(aOther)) {
       return false;
     }
     // Console markers must have matching causes as well.
     return GetCause() == aOther->GetCause();
   }
 
   virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker)
   {
-    aMarker.mCauseName.Construct(GetCause());
+    if (GetMetaData() == TRACING_INTERVAL_START) {
+      aMarker.mCauseName.Construct(GetCause());
+    } else {
+      aMarker.mEndStack = GetStack();
+    }
   }
 };
 
 // Queue a call to a console method. See the CALL_DELAY constant.
 void
 Console::Method(JSContext* aCx, MethodName aMethodName,
                 const nsAString& aMethodString,
                 const Sequence<JS::Value>& aData)
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1031,18 +1031,20 @@ public:
                       uint16_t aPhase, const nsAString& aCause)
     : nsDocShell::TimelineMarker(aDocShell, "DOMEvent", aMetaData, aCause)
     , mPhase(aPhase)
   {
   }
 
   virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker)
   {
-    aMarker.mType.Construct(GetCause());
-    aMarker.mEventPhase.Construct(mPhase);
+    if (GetMetaData() == TRACING_INTERVAL_START) {
+      aMarker.mType.Construct(GetCause());
+      aMarker.mEventPhase.Construct(mPhase);
+    }
   }
 
 private:
   uint16_t mPhase;
 };
 
 /**
 * Causes a check for event listeners and processing by them if they exist.
--- a/dom/svg/test/test_pathLength.html
+++ b/dom/svg/test/test_pathLength.html
@@ -5,17 +5,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 -->
 <head>
   <meta charset="utf-8">
   <title>Test path length changes when manipulated</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=946529">Mozilla Bug 1024926</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1024926">Mozilla Bug 1024926</a>
 <p id="display"></p>
 <div id="content" style="display: none">
   <svg width="100%" height="1" id="svg">
     <path id="path" d="M50,100l0,0l0,-50l100,0l86.3325,122.665z"></path>
   </svg>
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
--- a/dom/webidl/ProfileTimelineMarker.webidl
+++ b/dom/webidl/ProfileTimelineMarker.webidl
@@ -10,16 +10,18 @@ dictionary ProfileTimelineLayerRect {
   long width = 0;
   long height = 0;
 };
 
 dictionary ProfileTimelineMarker {
   DOMString name = "";
   DOMHighResTimeStamp start = 0;
   DOMHighResTimeStamp end = 0;
+  object? stack = null;
   /* For ConsoleTime markers.  */
   DOMString causeName;
+  object? endStack = null;
   /* For DOMEvent markers.  */
   DOMString type;
   unsigned short eventPhase;
   /* For Paint markers.  */
   sequence<ProfileTimelineLayerRect> rectangles;
 };
--- a/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/upstream-hunspell.diff
+++ b/extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/upstream-hunspell.diff
@@ -9672,16 +9672,20 @@ 19167,19168c24927
 ---
 > cuss/FEGSDM
 19246c25005
 < cysteine
 ---
 > cysteine/M
 19536a25296
 > decertify/DSGNX
+19759c25519
+< deliverable/U
+---
+> deliverable/US
 19935a25696
 > dequeue/DSG
 19999a25761
 > designated/U
 20196,20197c25958,25959
 < dialog/SM
 < dialogue/SM
 ---
@@ -10425,16 +10429,20 @@ 32361,32362c38063
 < melt/ADSG
 ---
 > melt/ADSGM
 32365,32366c38066
 < member's
 < member/EAS
 ---
 > member/EASM
+32386c38086
+< men
+---
+> men/M
 32708a38409
 > might've
 32717a38419
 > migrator/SM
 32760a38463
 > millennia
 32777d38479
 < millionnaire/M
@@ -10456,16 +10464,20 @@ 33051,33052c38753
 < mist's
 < mist/CDRSZG
 ---
 > mist/CDRSZGM
 33056c38757
 < mister's
 ---
 > mister/M
+33083c38784
+< mitigation/M
+---
+> mitigation/MS
 33107,33108c38808
 < mob's
 < mob/CS
 ---
 > mob/CSM
 33448,33449c39148
 < mortgage's
 < mortgage/AGDS
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -25783,17 +25783,17 @@ delineation/M
 delinquency/SM
 delinquent/SMY
 deliquesce/GDS
 deliquescent
 delirious/PY
 deliriousness/M
 delirium/SM
 deliver/ADGS
-deliverable/U
+deliverable/US
 deliverance/M
 delivered/U
 deliverer/SM
 dell/SM
 delphinium/MS
 delta/MS
 deltoids
 delude/GDS
@@ -38379,17 +38379,17 @@ memorably
 memorandum/MS
 memorial/SM
 memorialize/DSG
 memorization/M
 memorize/DSG
 memorized/U
 memory/SM
 memsahib/S
-men
+men/M
 menace/MGDS
 menacing/Y
 menage/MS
 menagerie/MS
 menarche
 mend/MDRSZG
 mendacious/Y
 mendacity/M
@@ -39081,17 +39081,17 @@ mistype/JGDS
 misunderstand/GSJ
 misunderstanding/M
 misunderstood
 misuse/DRSMG
 mite/MZRS
 miter/MDG
 mitigate/DSGN
 mitigated/U
-mitigation/M
+mitigation/MS
 mitigatory
 mitochondria
 mitochondrial
 mitoses
 mitosis/M
 mitotic
 mitt/MNSX
 mitten/M
--- a/gfx/skia/trunk/include/core/SkRect.h
+++ b/gfx/skia/trunk/include/core/SkRect.h
@@ -272,17 +272,16 @@ struct SK_API SkIRect {
         return this->intersect(r.fLeft, r.fTop, r.fRight, r.fBottom);
     }
 
     /** If rectangles a and b intersect, return true and set this rectangle to
         that intersection, otherwise return false and do not change this
         rectangle. If either rectangle is empty, do nothing and return false.
     */
     bool intersect(const SkIRect& a, const SkIRect& b) {
-        SkASSERT(&a && &b);
 
         if (!a.isEmpty() && !b.isEmpty() &&
                 a.fLeft < b.fRight && b.fLeft < a.fRight &&
                 a.fTop < b.fBottom && b.fTop < a.fBottom) {
             fLeft   = SkMax32(a.fLeft,   b.fLeft);
             fTop    = SkMax32(a.fTop,    b.fTop);
             fRight  = SkMin32(a.fRight,  b.fRight);
             fBottom = SkMin32(a.fBottom, b.fBottom);
@@ -293,17 +292,16 @@ struct SK_API SkIRect {
 
     /** If rectangles a and b intersect, return true and set this rectangle to
         that intersection, otherwise return false and do not change this
         rectangle. For speed, no check to see if a or b are empty is performed.
         If either is, then the return result is undefined. In the debug build,
         we assert that both rectangles are non-empty.
     */
     bool intersectNoEmptyCheck(const SkIRect& a, const SkIRect& b) {
-        SkASSERT(&a && &b);
         SkASSERT(!a.isEmpty() && !b.isEmpty());
 
         if (a.fLeft < b.fRight && b.fLeft < a.fRight &&
                 a.fTop < b.fBottom && b.fTop < a.fBottom) {
             fLeft   = SkMax32(a.fLeft,   b.fLeft);
             fTop    = SkMax32(a.fTop,    b.fTop);
             fRight  = SkMin32(a.fRight,  b.fRight);
             fBottom = SkMin32(a.fBottom, b.fBottom);
--- a/gfx/skia/trunk/include/views/SkOSMenu.h
+++ b/gfx/skia/trunk/include/views/SkOSMenu.h
@@ -124,17 +124,17 @@ public:
     /**
      * Create predefined items with the given parameters. To be used with the
      * other helper functions below to retrive/update state information.
      * Note: the helper functions below assume that slotName is UNIQUE for all
      * menu items of the same type since it's used to identify the event
      */
     int appendAction(const char label[], SkEventSinkID target);
     int appendList(const char label[], const char slotName[],
-                   SkEventSinkID target, int defaultIndex, const char[] ...);
+                   SkEventSinkID target, int defaultIndex, const char* ...);
     int appendSlider(const char label[], const char slotName[],
                      SkEventSinkID target, SkScalar min, SkScalar max,
                      SkScalar defaultValue);
     int appendSwitch(const char label[], const char slotName[],
                      SkEventSinkID target, bool defaultState = false);
     int appendTriState(const char label[], const char slotName[],
                        SkEventSinkID target, TriState defaultState = kOffState);
     int appendTextField(const char label[], const char slotName[],
--- a/gfx/skia/trunk/src/core/SkMatrix.cpp
+++ b/gfx/skia/trunk/src/core/SkMatrix.cpp
@@ -1062,17 +1062,17 @@ void SkMatrix::mapVectors(SkPoint dst[],
 
         tmp.fMat[kMTransX] = tmp.fMat[kMTransY] = 0;
         tmp.clearTypeMask(kTranslate_Mask);
         tmp.mapPoints(dst, src, count);
     }
 }
 
 bool SkMatrix::mapRect(SkRect* dst, const SkRect& src) const {
-    SkASSERT(dst && &src);
+    SkASSERT(dst);
 
     if (this->rectStaysRect()) {
         this->mapPoints((SkPoint*)dst, (const SkPoint*)&src, 2);
         dst->sort();
         return true;
     } else {
         SkPoint quad[4];
 
--- a/gfx/skia/trunk/src/core/SkRect.cpp
+++ b/gfx/skia/trunk/src/core/SkRect.cpp
@@ -109,38 +109,35 @@ bool SkRect::intersect(SkScalar left, Sk
         if (fRight > right) fRight = right;
         if (fBottom > bottom) fBottom = bottom;
         return true;
     }
     return false;
 }
 
 bool SkRect::intersect(const SkRect& r) {
-    SkASSERT(&r);
     return this->intersect(r.fLeft, r.fTop, r.fRight, r.fBottom);
 }
 
 bool SkRect::intersect2(const SkRect& r) {
-    SkASSERT(&r);
     SkScalar L = SkMaxScalar(fLeft, r.fLeft);
     SkScalar R = SkMinScalar(fRight, r.fRight);
     if (L >= R) {
         return false;
     }
     SkScalar T = SkMaxScalar(fTop, r.fTop);
     SkScalar B = SkMinScalar(fBottom, r.fBottom);
     if (T >= B) {
         return false;
     }
     this->set(L, T, R, B);
     return true;
 }
 
 bool SkRect::intersect(const SkRect& a, const SkRect& b) {
-    SkASSERT(&a && &b);
 
     if (!a.isEmpty() && !b.isEmpty() &&
         a.fLeft < b.fRight && b.fLeft < a.fRight &&
         a.fTop < b.fBottom && b.fTop < a.fBottom) {
         fLeft   = SkMaxScalar(a.fLeft,   b.fLeft);
         fTop    = SkMaxScalar(a.fTop,    b.fTop);
         fRight  = SkMinScalar(a.fRight,  b.fRight);
         fBottom = SkMinScalar(a.fBottom, b.fBottom);
--- a/gfx/skia/trunk/src/core/SkScan_Path.cpp
+++ b/gfx/skia/trunk/src/core/SkScan_Path.cpp
@@ -427,17 +427,17 @@ static SkEdge* sort_edges(SkEdge* list[]
 // clipRect may be null, even though we always have a clip. This indicates that
 // the path is contained in the clip, and so we can ignore it during the blit
 //
 // clipRect (if no null) has already been shifted up
 //
 void sk_fill_path(const SkPath& path, const SkIRect* clipRect, SkBlitter* blitter,
                   int start_y, int stop_y, int shiftEdgesUp,
                   const SkRegion& clipRgn) {
-    SkASSERT(&path && blitter);
+    SkASSERT(blitter);
 
     SkEdgeBuilder   builder;
 
     int count = builder.build(path, clipRect, shiftEdgesUp);
     SkEdge**    list = builder.edgeList();
 
     if (count < 2) {
         if (path.isInverseFillType()) {
--- a/gfx/skia/trunk/src/ports/SkFontHost_FreeType.cpp
+++ b/gfx/skia/trunk/src/ports/SkFontHost_FreeType.cpp
@@ -1332,17 +1332,17 @@ void SkScalerContext_FreeType::generateI
     generateGlyphImage(fFace, glyph);
 }
 
 
 void SkScalerContext_FreeType::generatePath(const SkGlyph& glyph,
                                             SkPath* path) {
     SkAutoMutexAcquire  ac(gFTMutex);
 
-    SkASSERT(&glyph && path);
+    SkASSERT(path);
 
     if (this->setupSize()) {
         path->reset();
         return;
     }
 
     uint32_t flags = fLoadGlyphFlags;
     flags |= FT_LOAD_NO_BITMAP; // ignore embedded bitmaps so we're sure to get the outline
--- a/gfx/skia/trunk/src/views/SkTextBox.cpp
+++ b/gfx/skia/trunk/src/views/SkTextBox.cpp
@@ -162,17 +162,17 @@ void SkTextBox::setSpacing(SkScalar mul,
     fSpacingMul = mul;
     fSpacingAdd = add;
 }
 
 /////////////////////////////////////////////////////////////////////////////////////////////
 
 void SkTextBox::draw(SkCanvas* canvas, const char text[], size_t len, const SkPaint& paint)
 {
-    SkASSERT(canvas && &paint && (text || len == 0));
+    SkASSERT(canvas && (text || len == 0));
 
     SkScalar marginWidth = fBox.width();
 
     if (marginWidth <= 0 || len == 0)
         return;
 
     const char* textStop = text + len;
 
--- a/gfx/skia/trunk/src/views/SkViewPriv.cpp
+++ b/gfx/skia/trunk/src/views/SkViewPriv.cpp
@@ -12,17 +12,17 @@
 void SkView::Artist::draw(SkView* view, SkCanvas* canvas)
 {
     SkASSERT(view && canvas);
     this->onDraw(view, canvas);
 }
 
 void SkView::Artist::inflate(const SkDOM& dom, const SkDOM::Node* node)
 {
-    SkASSERT(&dom && node);
+    SkASSERT(node);
     this->onInflate(dom, node);
 }
 
 void SkView::Artist::onInflate(const SkDOM&, const SkDOM::Node*)
 {
     // subclass should override this as needed
 }
 
@@ -58,17 +58,17 @@ void SkView::Layout::layoutChildren(SkVi
 {
     SkASSERT(parent);
     if (parent->width() > 0 && parent->height() > 0)
         this->onLayoutChildren(parent);
 }
 
 void SkView::Layout::inflate(const SkDOM& dom, const SkDOM::Node* node)
 {
-    SkASSERT(&dom && node);
+    SkASSERT(node);
     this->onInflate(dom, node);
 }
 
 void SkView::Layout::onInflate(const SkDOM&, const SkDOM::Node*)
 {
     // subclass should override this as needed
 }
 
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -2239,21 +2239,17 @@ WINNT|Darwin|Android)
   ;;
 esac
 
 AC_SUBST(WRAP_SYSTEM_INCLUDES)
 AC_SUBST(VISIBILITY_FLAGS)
 
 MOZ_GCC_PR49911
 MOZ_GCC_PR39608
-if test "$OS_TARGET" != WINNT; then
-    # Only run this test with clang on non-Windows platforms, because clang
-    # cannot do enough code gen for now to make this test work correctly.
-    MOZ_LLVM_PR8927
-fi
+MOZ_LLVM_PR8927
 
 dnl Checks for header files.
 dnl ========================================================
 AC_HEADER_DIRENT
 case "$target_os" in
 freebsd*)
 # for stuff like -lXshm
     CPPFLAGS="${CPPFLAGS} ${X_CFLAGS}"
--- a/layout/base/tests/chrome/test_leaf_layers_partition_browser_window.xul
+++ b/layout/base/tests/chrome/test_leaf_layers_partition_browser_window.xul
@@ -91,18 +91,24 @@
 
       if (winLowerThanVista && !tests[testIndex].maximize) {
         ok(true, "Skipping non-maximized test " + testIndex + " on winLowerThanVista since the resizer causes overlapping layers");
         ++testIndex;
         nextTest();
         return;
       }
 
+      window.focus();
       // Run the test in a separate window so we get a clean browser window.
       win = window.open("data:text/html,<html style='overflow:scroll'>",
                 "", "scrollbars=yes,toolbar,menubar,width=500,height=500");
+      setTimeout(setupWindow, 0);
+    }
+
+    function setupWindow() {
       win.addEventListener("load", doTest, false);
+      win.focus();
     }
 
     nextTest();
   ]]>
   </script>
 </window>
new file mode 100644
--- /dev/null
+++ b/layout/generic/RubyReflowState.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/* states and methods used while laying out a ruby segment */
+
+#include "RubyReflowState.h"
+
+using namespace mozilla;
+
+RubyReflowState::RubyReflowState(
+  WritingMode aLineWM,
+  const nsTArray<nsRubyTextContainerFrame*>& aTextContainers)
+  : mCurrentContainerIndex(kBaseContainerIndex)
+{
+  uint32_t rtcCount = aTextContainers.Length();
+  mTextContainers.SetCapacity(rtcCount);
+  for (uint32_t i = 0; i < rtcCount; i++) {
+    mTextContainers.AppendElement(
+      TextContainerInfo(aLineWM, aTextContainers[i]));
+  }
+}
new file mode 100644
--- /dev/null
+++ b/layout/generic/RubyReflowState.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+/* states and methods used while laying out a ruby segment */
+
+#ifndef mozilla_RubyReflowState_h_
+#define mozilla_RubyReflowState_h_
+
+#include "mozilla/Attributes.h"
+#include "WritingModes.h"
+#include "nsTArray.h"
+
+#define RTC_ARRAY_SIZE 1
+
+class nsRubyTextContainerFrame;
+
+namespace mozilla {
+
+class MOZ_STACK_CLASS RubyReflowState MOZ_FINAL
+{
+public:
+  explicit RubyReflowState(
+    WritingMode aLineWM,
+    const nsTArray<nsRubyTextContainerFrame*>& aTextContainers);
+
+  struct TextContainerInfo
+  {
+    nsRubyTextContainerFrame* mFrame;
+    LogicalSize mLineSize;
+
+    TextContainerInfo(WritingMode aLineWM, nsRubyTextContainerFrame* aFrame)
+      : mFrame(aFrame)
+      , mLineSize(aLineWM) { }
+  };
+
+  void AdvanceCurrentContainerIndex() { mCurrentContainerIndex++; }
+
+  void SetTextContainerInfo(int32_t aIndex,
+                            nsRubyTextContainerFrame* aContainer,
+                            const LogicalSize& aLineSize)
+  {
+    MOZ_ASSERT(mTextContainers[aIndex].mFrame == aContainer);
+    mTextContainers[aIndex].mLineSize = aLineSize;
+  }
+
+  const TextContainerInfo&
+    GetCurrentTextContainerInfo(nsRubyTextContainerFrame* aFrame) const
+  {
+    MOZ_ASSERT(mTextContainers[mCurrentContainerIndex].mFrame == aFrame);
+    return mTextContainers[mCurrentContainerIndex];
+  }
+
+private:
+  static MOZ_CONSTEXPR_VAR int32_t kBaseContainerIndex = -1;
+  // The index of the current reflowing container. When it equals to
+  // kBaseContainerIndex, we are reflowing ruby base. Otherwise, it
+  // stands for the index of text containers in the ruby segment.
+  int32_t mCurrentContainerIndex;
+
+  nsAutoTArray<TextContainerInfo, RTC_ARRAY_SIZE> mTextContainers;
+};
+
+}
+
+#endif // mozilla_RubyReflowState_h_
--- a/layout/generic/RubyUtils.cpp
+++ b/layout/generic/RubyUtils.cpp
@@ -1,16 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "RubyUtils.h"
-#include "nsIFrame.h"
 
 using namespace mozilla;
 
 NS_DECLARE_FRAME_PROPERTY(ReservedISize, nullptr);
 
 union NSCoordValue
 {
   nscoord mCoord;
@@ -37,8 +36,25 @@ RubyUtils::ClearReservedISize(nsIFrame* 
 /* static */ nscoord
 RubyUtils::GetReservedISize(nsIFrame* aFrame)
 {
   MOZ_ASSERT(IsExpandableRubyBox(aFrame));
   NSCoordValue value;
   value.mPointer = aFrame->Properties().Get(ReservedISize());
   return value.mCoord;
 }
+
+RubyTextContainerIterator::RubyTextContainerIterator(
+  nsRubyBaseContainerFrame* aBaseContainer)
+{
+  mFrame = aBaseContainer;
+  Next();
+}
+
+void
+RubyTextContainerIterator::Next()
+{
+  MOZ_ASSERT(mFrame, "Should have checked AtEnd()");
+  mFrame = mFrame->GetNextSibling();
+  if (mFrame && mFrame->GetType() != nsGkAtoms::rubyTextContainerFrame) {
+    mFrame = nullptr;
+  }
+}
--- a/layout/generic/RubyUtils.h
+++ b/layout/generic/RubyUtils.h
@@ -3,17 +3,18 @@
 /* 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_RubyUtils_h_
 #define mozilla_RubyUtils_h_
 
 #include "nsGkAtoms.h"
-#include "nsIFrame.h"
+#include "nsRubyBaseContainerFrame.h"
+#include "nsRubyTextContainerFrame.h"
 
 namespace mozilla {
 
 /**
  * Reserved ISize
  *
  * With some exceptions, each ruby internal box has two isizes, which
  * are the reflowed isize and the final isize. The reflowed isize is
@@ -53,11 +54,31 @@ public:
            type == nsGkAtoms::rubyTextContainerFrame;
   }
 
   static void SetReservedISize(nsIFrame* aFrame, nscoord aISize);
   static void ClearReservedISize(nsIFrame* aFrame);
   static nscoord GetReservedISize(nsIFrame* aFrame);
 };
 
+/**
+ * This class iterates all ruby text containers paired with
+ * the given ruby base container.
+ */
+class MOZ_STACK_CLASS RubyTextContainerIterator
+{
+public:
+  explicit RubyTextContainerIterator(nsRubyBaseContainerFrame* aBaseContainer);
+
+  void Next();
+  bool AtEnd() const { return !mFrame; }
+  nsRubyTextContainerFrame* GetTextContainer() const
+  {
+    return static_cast<nsRubyTextContainerFrame*>(mFrame);
+  }
+
+private:
+  nsIFrame* mFrame;
+};
+
 }
 
 #endif /* !defined(mozilla_RubyUtils_h_) */
--- a/layout/generic/moz.build
+++ b/layout/generic/moz.build
@@ -89,16 +89,17 @@ UNIFIED_SOURCES += [
     'nsSimplePageSequenceFrame.cpp',
     'nsSplittableFrame.cpp',
     'nsSubDocumentFrame.cpp',
     'nsTextFrame.cpp',
     'nsTextFrameUtils.cpp',
     'nsTextRunTransformations.cpp',
     'nsVideoFrame.cpp',
     'nsViewportFrame.cpp',
+    'RubyReflowState.cpp',
     'RubyUtils.cpp',
     'ScrollbarActivity.cpp',
     'StickyScrollContainer.cpp',
     'TextOverflow.cpp',
 ]
 
 # nsLineLayout.cpp needs to be built separately because it uses plarena.h.
 # nsPluginFrame.cpp needs to be built separately because of name clashes in the OS X headers.
--- a/layout/generic/nsFrameState.cpp
+++ b/layout/generic/nsFrameState.cpp
@@ -13,16 +13,17 @@
 #include "nsFlexContainerFrame.h"
 #include "nsGfxScrollFrame.h"
 #include "nsIFrame.h"
 #include "nsISVGChildFrame.h"
 #include "nsImageFrame.h"
 #include "nsInlineFrame.h"
 #include "nsPlaceholderFrame.h"
 #include "nsRubyTextFrame.h"
+#include "nsRubyTextContainerFrame.h"
 #include "nsSVGContainerFrame.h"
 #include "nsTableCellFrame.h"
 #include "nsTableRowFrame.h"
 #include "nsTableRowGroupFrame.h"
 #include "nsTextFrame.h"
 
 namespace mozilla {
 
--- a/layout/generic/nsFrameStateBits.h
+++ b/layout/generic/nsFrameStateBits.h
@@ -518,16 +518,23 @@ FRAME_STATE_BIT(Inline, 23, NS_INLINE_FR
 // == Frame state bits that apply to ruby text frames =========================
 
 FRAME_STATE_GROUP(RubyText, nsRubyTextFrame)
 
 // inherits from nsInlineFrame
 FRAME_STATE_BIT(RubyText, 24, NS_RUBY_TEXT_FRAME_AUTOHIDE)
 
 
+// == Frame state bits that apply to ruby text container frames ===============
+
+FRAME_STATE_GROUP(RubyTextContainer, nsRubyTextContainerFrame)
+
+FRAME_STATE_BIT(RubyTextContainer, 20, NS_RUBY_TEXT_CONTAINER_IS_SPAN)
+
+
 // == Frame state bits that apply to placeholder frames =======================
 
 FRAME_STATE_GROUP(Placeholder, nsPlaceholderFrame)
 
 // Frame state bits that are used to keep track of what this is a
 // placeholder for.
 
 FRAME_STATE_BIT(Placeholder, 20, PLACEHOLDER_FOR_FLOAT)
--- a/layout/generic/nsHTMLReflowState.cpp
+++ b/layout/generic/nsHTMLReflowState.cpp
@@ -67,16 +67,17 @@ nsHTMLReflowState::nsHTMLReflowState(nsP
   MOZ_ASSERT(aPresContext, "no pres context");
   MOZ_ASSERT(aFrame, "no frame");
   MOZ_ASSERT(aPresContext == aFrame->PresContext(), "wrong pres context");
   parentReflowState = nullptr;
   AvailableISize() = aAvailableSpace.ISize(mWritingMode);
   AvailableBSize() = aAvailableSpace.BSize(mWritingMode);
   mFloatManager = nullptr;
   mLineLayout = nullptr;
+  mRubyReflowState = nullptr;
   memset(&mFlags, 0, sizeof(mFlags));
   mDiscoveredClearance = nullptr;
   mPercentHeightObserver = nullptr;
 
   if (aFlags & DUMMY_PARENT_REFLOW_STATE) {
     mFlags.mDummyParentReflowState = true;
   }
 
@@ -202,16 +203,17 @@ nsHTMLReflowState::nsHTMLReflowState(nsP
     }
   }
 
   mFloatManager = aParentReflowState.mFloatManager;
   if (frame->IsFrameOfType(nsIFrame::eLineParticipant))
     mLineLayout = aParentReflowState.mLineLayout;
   else
     mLineLayout = nullptr;
+  mRubyReflowState = nullptr;
 
   // Note: mFlags was initialized as a copy of aParentReflowState.mFlags up in
   // this constructor's init list, so the only flags that we need to explicitly
   // initialize here are those that may need a value other than our parent's.
   mFlags.mNextInFlowUntouched = aParentReflowState.mFlags.mNextInFlowUntouched &&
     CheckNextInFlowParenthood(aFrame, aParentReflowState.frame);
   mFlags.mAssumingHScrollbar = mFlags.mAssumingVScrollbar = false;
   mFlags.mHasClearance = false;
--- a/layout/generic/nsHTMLReflowState.h
+++ b/layout/generic/nsHTMLReflowState.h
@@ -16,16 +16,20 @@
 
 class nsPresContext;
 class nsRenderingContext;
 class nsFloatManager;
 class nsLineLayout;
 class nsIPercentHeightObserver;
 struct nsHypotheticalBox;
 
+namespace mozilla {
+class RubyReflowState;
+}
+
 /**
  * @return aValue clamped to [aMinValue, aMaxValue].
  *
  * @note This function needs to handle aMinValue > aMaxValue. In that case,
  *       aMinValue is returned.
  * @see http://www.w3.org/TR/CSS21/visudet.html#min-max-widths
  * @see http://www.w3.org/TR/CSS21/visudet.html#min-max-heights
  */
@@ -253,16 +257,19 @@ struct nsHTMLReflowState : public nsCSSO
   const nsHTMLReflowState* parentReflowState;
 
   // pointer to the float manager associated with this area
   nsFloatManager* mFloatManager;
 
   // LineLayout object (only for inline reflow; set to nullptr otherwise)
   nsLineLayout*    mLineLayout;
 
+  // RubyReflowState object (only for ruby reflow; set to nullptr otherwise)
+  mozilla::RubyReflowState* mRubyReflowState;
+
   // The appropriate reflow state for the containing block (for
   // percentage widths, etc.) of this reflow state's frame.
   const nsHTMLReflowState *mCBReflowState;
 
   // The type of frame, from css's perspective. This value is
   // initialized by the Init method below.
   nsCSSFrameType   mFrameType;
 
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -61,16 +61,17 @@ nsLineLayout::nsLineLayout(nsPresContext
     mLastOptionalBreakFrame(nullptr),
     mForceBreakFrame(nullptr),
     mBlockRS(nullptr),/* XXX temporary */
     mLastOptionalBreakPriority(gfxBreakPriority::eNoBreak),
     mLastOptionalBreakFrameOffset(-1),
     mForceBreakFrameOffset(-1),
     mMinLineBSize(0),
     mTextIndent(0),
+    mRubyReflowState(nullptr),
     mFirstLetterStyleOK(false),
     mIsTopOfPage(false),
     mImpactedByFloats(false),
     mLastFloatWasLetterFrame(false),
     mLineIsEmpty(false),
     mLineEndsInBR(false),
     mNeedBackup(false),
     mInFirstLine(false),
@@ -863,16 +864,20 @@ nsLineLayout::ReflowFrame(nsIFrame* aFra
     // includes room for the side margins.
     // For now, set the available block-size to unconstrained always.
     LogicalSize availSize = mBlockReflowState->ComputedSize(frameWM);
     availSize.BSize(frameWM) = NS_UNCONSTRAINEDSIZE;
     reflowStateHolder.emplace(mPresContext, *psd->mReflowState,
                               aFrame, availSize);
     nsHTMLReflowState& reflowState = *reflowStateHolder;
     reflowState.mLineLayout = this;
+    if (mRubyReflowState) {
+      reflowState.mRubyReflowState = mRubyReflowState;
+      mRubyReflowState = nullptr;
+    }
     reflowState.mFlags.mIsTopOfPage = mIsTopOfPage;
     if (reflowState.ComputedISize() == NS_UNCONSTRAINEDSIZE) {
       reflowState.AvailableISize() = availableSpaceOnLine;
     }
     WritingMode stateWM = reflowState.GetWritingMode();
     pfd->mMargin =
       reflowState.ComputedLogicalMargin().ConvertTo(lineWM, stateWM);
     pfd->mBorderPadding =
--- a/layout/generic/nsLineLayout.h
+++ b/layout/generic/nsLineLayout.h
@@ -22,16 +22,20 @@
 #include "plarena.h"
 #include "gfxTypes.h"
 #include "WritingModes.h"
 #include "JustificationUtils.h"
 
 class nsFloatManager;
 struct nsStyleText;
 
+namespace mozilla {
+class RubyReflowState;
+}
+
 class nsLineLayout {
 public:
   /**
    * @param aBaseLineLayout the nsLineLayout for ruby base,
    * nullptr if no separate base nsLineLayout is needed.
    */
   nsLineLayout(nsPresContext* aPresContext,
                nsFloatManager* aFloatManager,
@@ -93,16 +97,23 @@ public:
   }
 
   int32_t GetCurrentSpanCount() const;
 
   void SplitLineTo(int32_t aNewCount);
 
   bool IsZeroBSize();
 
+  // The ruby layout will be passed to the next frame to be reflowed
+  // via the HTML reflow state.
+  void SetRubyReflowState(mozilla::RubyReflowState* aRubyReflowState)
+  {
+    mRubyReflowState = aRubyReflowState;
+  }
+
   // Reflows the frame and returns the reflow status. aPushedFrame is true
   // if the frame is pushed to the next line because it doesn't fit.
   void ReflowFrame(nsIFrame* aFrame,
                    nsReflowStatus& aReflowStatus,
                    nsHTMLReflowMetrics* aMetrics,
                    bool& aPushedFrame);
 
   void AddBulletFrame(nsIFrame* aFrame, const nsHTMLReflowMetrics& aMetrics);
@@ -553,16 +564,20 @@ protected:
   // max-element-size calculation.
   nscoord mTextIndent;
 
   // This state varies during the reflow of a line but is line
   // "global" state not span "local" state.
   int32_t mLineNumber;
   mozilla::JustificationInfo mJustificationInfo;
 
+  // The ruby layout for the next frame to be reflowed.
+  // It is reset every time it is used.
+  mozilla::RubyReflowState* mRubyReflowState;
+
   int32_t mTotalPlacedFrames;
 
   nscoord mBStartEdge;
   nscoord mMaxStartBoxBSize;
   nscoord mMaxEndBoxBSize;
 
   nscoord mInflationMinFontSize;
 
--- a/layout/generic/nsRubyBaseContainerFrame.cpp
+++ b/layout/generic/nsRubyBaseContainerFrame.cpp
@@ -50,170 +50,172 @@ nsRubyBaseContainerFrame::GetType() cons
 #ifdef DEBUG_FRAME_DUMP
 nsresult
 nsRubyBaseContainerFrame::GetFrameName(nsAString& aResult) const
 {
   return MakeFrameName(NS_LITERAL_STRING("RubyBaseContainer"), aResult);
 }
 #endif
 
-class MOZ_STACK_CLASS PairEnumerator
+/**
+ * Ruby column is a unit consists of one ruby base and all ruby
+ * annotations paired with it.
+ * See http://dev.w3.org/csswg/css-ruby/#ruby-pairing
+ */
+struct MOZ_STACK_CLASS mozilla::RubyColumn
+{
+  nsIFrame* mBaseFrame;
+  nsAutoTArray<nsIFrame*, RTC_ARRAY_SIZE> mTextFrames;
+  RubyColumn() : mBaseFrame(nullptr) { }
+};
+
+class MOZ_STACK_CLASS RubyColumnEnumerator
 {
 public:
-  PairEnumerator(nsRubyBaseContainerFrame* aRBCFrame,
-                 const nsTArray<nsRubyTextContainerFrame*>& aRTCFrames);
+  RubyColumnEnumerator(nsRubyBaseContainerFrame* aRBCFrame,
+                       const nsTArray<nsRubyTextContainerFrame*>& aRTCFrames);
 
   void Next();
   bool AtEnd() const;
 
   uint32_t GetLevelCount() const { return mFrames.Length(); }
   nsIFrame* GetFrame(uint32_t aIndex) const { return mFrames[aIndex]; }
   nsIFrame* GetBaseFrame() const { return GetFrame(0); }
   nsIFrame* GetTextFrame(uint32_t aIndex) const { return GetFrame(aIndex + 1); }
-  void GetFrames(nsIFrame*& aBaseFrame, nsTArray<nsIFrame*>& aTextFrames) const;
+  void GetColumn(RubyColumn& aColumn) const;
 
 private:
   nsAutoTArray<nsIFrame*, RTC_ARRAY_SIZE + 1> mFrames;
 };
 
-PairEnumerator::PairEnumerator(
-    nsRubyBaseContainerFrame* aBaseContainer,
-    const nsTArray<nsRubyTextContainerFrame*>& aTextContainers)
+RubyColumnEnumerator::RubyColumnEnumerator(
+  nsRubyBaseContainerFrame* aBaseContainer,
+  const nsTArray<nsRubyTextContainerFrame*>& aTextContainers)
 {
   const uint32_t rtcCount = aTextContainers.Length();
   mFrames.SetCapacity(rtcCount + 1);
   mFrames.AppendElement(aBaseContainer->GetFirstPrincipalChild());
   for (uint32_t i = 0; i < rtcCount; i++) {
-    nsIFrame* rtFrame = aTextContainers[i]->GetFirstPrincipalChild();
+    nsRubyTextContainerFrame* container = aTextContainers[i];
+    // If the container is for span, leave a nullptr here.
+    // Spans do not take part in pairing.
+    nsIFrame* rtFrame = !container->IsSpanContainer() ?
+      aTextContainers[i]->GetFirstPrincipalChild() : nullptr;
     mFrames.AppendElement(rtFrame);
   }
 }
 
 void
-PairEnumerator::Next()
+RubyColumnEnumerator::Next()
 {
   for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
     if (mFrames[i]) {
       mFrames[i] = mFrames[i]->GetNextSibling();
     }
   }
 }
 
 bool
-PairEnumerator::AtEnd() const
+RubyColumnEnumerator::AtEnd() const
 {
   for (uint32_t i = 0, iend = mFrames.Length(); i < iend; i++) {
     if (mFrames[i]) {
       return false;
     }
   }
   return true;
 }
 
 void
-PairEnumerator::GetFrames(nsIFrame*& aBaseFrame,
-                          nsTArray<nsIFrame*>& aTextFrames) const
+RubyColumnEnumerator::GetColumn(RubyColumn& aColumn) const
 {
-  aBaseFrame = mFrames[0];
-  aTextFrames.ClearAndRetainStorage();
+  aColumn.mBaseFrame = mFrames[0];
+  aColumn.mTextFrames.ClearAndRetainStorage();
   for (uint32_t i = 1, iend = mFrames.Length(); i < iend; i++) {
-    aTextFrames.AppendElement(mFrames[i]);
+    aColumn.mTextFrames.AppendElement(mFrames[i]);
   }
 }
 
-nscoord
-nsRubyBaseContainerFrame::CalculateMaxSpanISize(
-    nsRenderingContext* aRenderingContext)
-{
-  nscoord max = 0;
-  uint32_t spanCount = mSpanContainers.Length();
-  for (uint32_t i = 0; i < spanCount; i++) {
-    nsIFrame* frame = mSpanContainers[i]->GetFirstPrincipalChild();
-    nscoord isize = frame->GetPrefISize(aRenderingContext);
-    max = std::max(max, isize);
-  }
-  return max;
-}
-
 static nscoord
-CalculatePairPrefISize(nsRenderingContext* aRenderingContext,
-                       const PairEnumerator& aEnumerator)
+CalculateColumnPrefISize(nsRenderingContext* aRenderingContext,
+                         const RubyColumnEnumerator& aEnumerator)
 {
   nscoord max = 0;
   uint32_t levelCount = aEnumerator.GetLevelCount();
   for (uint32_t i = 0; i < levelCount; i++) {
     nsIFrame* frame = aEnumerator.GetFrame(i);
     if (frame) {
       max = std::max(max, frame->GetPrefISize(aRenderingContext));
     }
   }
   return max;
 }
 
 /* virtual */ void
 nsRubyBaseContainerFrame::AddInlineMinISize(
     nsRenderingContext *aRenderingContext, nsIFrame::InlineMinISizeData *aData)
 {
-  if (!mSpanContainers.IsEmpty()) {
-    // Since spans are not breakable internally, use our pref isize
-    // directly if there is any span.
-    aData->currentLine += GetPrefISize(aRenderingContext);
-    return;
+  AutoTextContainerArray textContainers;
+  GetTextContainers(textContainers);
+
+  for (uint32_t i = 0, iend = textContainers.Length(); i < iend; i++) {
+    if (textContainers[i]->IsSpanContainer()) {
+      // Since spans are not breakable internally, use our pref isize
+      // directly if there is any span.
+      aData->currentLine += GetPrefISize(aRenderingContext);
+      return;
+    }
   }
 
   nscoord max = 0;
-  PairEnumerator enumerator(this, mTextContainers);
+  RubyColumnEnumerator enumerator(this, textContainers);
   for (; !enumerator.AtEnd(); enumerator.Next()) {
-    // We use *pref* isize for computing the min isize of pairs
+    // We use *pref* isize for computing the min isize of columns
     // because ruby bases and texts are unbreakable internally.
-    max = std::max(max, CalculatePairPrefISize(aRenderingContext, enumerator));
+    max = std::max(max, CalculateColumnPrefISize(aRenderingContext,
+                                                 enumerator));
   }
   aData->currentLine += max;
 }
 
 /* virtual */ void
 nsRubyBaseContainerFrame::AddInlinePrefISize(
     nsRenderingContext *aRenderingContext, nsIFrame::InlinePrefISizeData *aData)
 {
+  AutoTextContainerArray textContainers;
+  GetTextContainers(textContainers);
+
   nscoord sum = 0;
-  PairEnumerator enumerator(this, mTextContainers);
+  RubyColumnEnumerator enumerator(this, textContainers);
   for (; !enumerator.AtEnd(); enumerator.Next()) {
-    sum += CalculatePairPrefISize(aRenderingContext, enumerator);
+    sum += CalculateColumnPrefISize(aRenderingContext, enumerator);
   }
-  sum = std::max(sum, CalculateMaxSpanISize(aRenderingContext));
+  for (uint32_t i = 0, iend = textContainers.Length(); i < iend; i++) {
+    if (textContainers[i]->IsSpanContainer()) {
+      nsIFrame* frame = textContainers[i]->GetFirstPrincipalChild();
+      sum = std::max(sum, frame->GetPrefISize(aRenderingContext));
+    }
+  }
   aData->currentLine += sum;
 }
 
 /* virtual */ bool 
 nsRubyBaseContainerFrame::IsFrameOfType(uint32_t aFlags) const 
 {
   return nsContainerFrame::IsFrameOfType(aFlags & 
          ~(nsIFrame::eLineParticipant));
 }
 
-void nsRubyBaseContainerFrame::AppendTextContainer(nsIFrame* aFrame)
+void
+nsRubyBaseContainerFrame::GetTextContainers(TextContainerArray& aTextContainers)
 {
-  nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(aFrame);
-  MOZ_ASSERT(rtcFrame, "Must provide a ruby text container.");
-
-  nsTArray<nsRubyTextContainerFrame*>* containers = &mTextContainers;
-  if (!GetPrevContinuation() && !GetNextContinuation()) {
-    nsIFrame* onlyChild = rtcFrame->PrincipalChildList().OnlyChild();
-    if (onlyChild && onlyChild->IsPseudoFrame(rtcFrame->GetContent())) {
-      // Per CSS Ruby spec, if the only child of an rtc frame is
-      // a pseudo rt frame, it spans all bases in the segment.
-      containers = &mSpanContainers;
-    }
+  MOZ_ASSERT(aTextContainers.IsEmpty());
+  for (RubyTextContainerIterator iter(this); !iter.AtEnd(); iter.Next()) {
+    aTextContainers.AppendElement(iter.GetTextContainer());
   }
-  containers->AppendElement(rtcFrame);
-}
-
-void nsRubyBaseContainerFrame::ClearTextContainers() {
-  mSpanContainers.Clear();
-  mTextContainers.Clear();
 }
 
 /* virtual */ bool
 nsRubyBaseContainerFrame::CanContinueTextRun() const
 {
   return true;
 }
 
@@ -233,16 +235,24 @@ nsRubyBaseContainerFrame::ComputeSize(ns
 }
 
 /* virtual */ nscoord
 nsRubyBaseContainerFrame::GetLogicalBaseline(WritingMode aWritingMode) const
 {
   return mBaseline;
 }
 
+struct nsRubyBaseContainerFrame::ReflowState
+{
+  bool mAllowLineBreak;
+  const TextContainerArray& mTextContainers;
+  const nsHTMLReflowState& mBaseReflowState;
+  const nsTArray<UniquePtr<nsHTMLReflowState>>& mTextReflowStates;
+};
+
 // Check whether the given extra isize can fit in the line in base level.
 static bool
 ShouldBreakBefore(const nsHTMLReflowState& aReflowState, nscoord aExtraISize)
 {
   nsLineLayout* lineLayout = aReflowState.mLineLayout;
   int32_t offset;
   gfxBreakPriority priority;
   nscoord icoord = lineLayout->GetCurrentICoord();
@@ -261,62 +271,55 @@ nsRubyBaseContainerFrame::Reflow(nsPresC
   aStatus = NS_FRAME_COMPLETE;
 
   if (!aReflowState.mLineLayout) {
     NS_ASSERTION(
       aReflowState.mLineLayout,
       "No line layout provided to RubyBaseContainerFrame reflow method.");
     return;
   }
+  MOZ_ASSERT(aReflowState.mRubyReflowState, "No ruby reflow state provided");
+
+  AutoTextContainerArray textContainers;
+  GetTextContainers(textContainers);
 
   MoveOverflowToChildList();
   // Ask text containers to drain overflows
-  const uint32_t rtcCount = mTextContainers.Length();
+  const uint32_t rtcCount = textContainers.Length();
   for (uint32_t i = 0; i < rtcCount; i++) {
-    mTextContainers[i]->MoveOverflowToChildList();
+    textContainers[i]->MoveOverflowToChildList();
   }
 
   WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
   LogicalSize availSize(lineWM, aReflowState.AvailableWidth(),
                         aReflowState.AvailableHeight());
 
-  const uint32_t spanCount = mSpanContainers.Length();
-  const uint32_t totalCount = rtcCount + spanCount;
   // We have a reflow state and a line layout for each RTC.
   // They are conceptually the state of the RTCs, but we don't actually
   // reflow those RTCs in this code. These two arrays are holders of
   // the reflow states and line layouts.
   // Since there are pointers refer to reflow states and line layouts,
   // it is necessary to guarantee that they won't be moved. For this
   // reason, they are wrapped in UniquePtr here.
   nsAutoTArray<UniquePtr<nsHTMLReflowState>, RTC_ARRAY_SIZE> reflowStates;
   nsAutoTArray<UniquePtr<nsLineLayout>, RTC_ARRAY_SIZE> lineLayouts;
-  reflowStates.SetCapacity(totalCount);
-  lineLayouts.SetCapacity(totalCount);
-
-  nsAutoTArray<nsHTMLReflowState*, RTC_ARRAY_SIZE> rtcReflowStates;
-  nsAutoTArray<nsHTMLReflowState*, RTC_ARRAY_SIZE> spanReflowStates;
-  rtcReflowStates.SetCapacity(rtcCount);
-  spanReflowStates.SetCapacity(spanCount);
+  reflowStates.SetCapacity(rtcCount);
+  lineLayouts.SetCapacity(rtcCount);
 
   // Begin the line layout for each ruby text container in advance.
-  for (uint32_t i = 0; i < totalCount; i++) {
-    nsIFrame* textContainer;
-    nsTArray<nsHTMLReflowState*>* reflowStateArray;
-    if (i < rtcCount) {
-      textContainer = mTextContainers[i];
-      reflowStateArray = &rtcReflowStates;
-    } else {
-      textContainer = mSpanContainers[i - rtcCount];
-      reflowStateArray = &spanReflowStates;
+  bool hasSpan = false;
+  for (uint32_t i = 0; i < rtcCount; i++) {
+    nsRubyTextContainerFrame* textContainer = textContainers[i];
+    if (textContainer->IsSpanContainer()) {
+      hasSpan = true;
     }
+
     nsHTMLReflowState* reflowState = new nsHTMLReflowState(
       aPresContext, *aReflowState.parentReflowState, textContainer, availSize);
     reflowStates.AppendElement(reflowState);
-    reflowStateArray->AppendElement(reflowState);
     nsLineLayout* lineLayout = new nsLineLayout(aPresContext,
                                                 reflowState->mFloatManager,
                                                 reflowState, nullptr,
                                                 aReflowState.mLineLayout);
     lineLayouts.AppendElement(lineLayout);
 
     // Line number is useless for ruby text
     // XXX nullptr here may cause problem, see comments for
@@ -357,80 +360,82 @@ nsRubyBaseContainerFrame::Reflow(nsPresC
       aReflowState.mLineLayout->NotifyOptionalBreakPosition(
         this, 0, startEdge <= aReflowState.AvailableISize(),
         gfxBreakPriority::eNormalBreak)) {
     aStatus = NS_INLINE_LINE_BREAK_BEFORE();
   }
 
   nscoord isize = 0;
   if (aStatus == NS_FRAME_COMPLETE) {
-    // Reflow pairs excluding any span
-    bool allowInternalLineBreak = allowLineBreak && mSpanContainers.IsEmpty();
-    isize = ReflowPairs(aPresContext, allowInternalLineBreak,
-                        aReflowState, rtcReflowStates, aStatus);
+    // Reflow columns excluding any span
+    ReflowState reflowState = {
+      allowLineBreak && !hasSpan, textContainers, aReflowState, reflowStates
+    };
+    isize = ReflowColumns(reflowState, aStatus);
   }
 
-  // If there exists any span, the pairs must either be completely
+  // If there exists any span, the columns must either be completely
   // reflowed, or be not reflowed at all.
   MOZ_ASSERT(NS_INLINE_IS_BREAK_BEFORE(aStatus) ||
-             NS_FRAME_IS_COMPLETE(aStatus) || mSpanContainers.IsEmpty());
+             NS_FRAME_IS_COMPLETE(aStatus) || !hasSpan);
   if (!NS_INLINE_IS_BREAK_BEFORE(aStatus) &&
-      NS_FRAME_IS_COMPLETE(aStatus) && !mSpanContainers.IsEmpty()) {
+      NS_FRAME_IS_COMPLETE(aStatus) && hasSpan) {
     // Reflow spans
-    nscoord spanISize = ReflowSpans(aPresContext, aReflowState,
-                                    spanReflowStates);
+    ReflowState reflowState = {
+      false, textContainers, aReflowState, reflowStates
+    };
+    nscoord spanISize = ReflowSpans(reflowState);
     nscoord deltaISize = spanISize - isize;
     if (deltaISize <= 0) {
       RubyUtils::ClearReservedISize(this);
     } else if (allowLineBreak && ShouldBreakBefore(aReflowState, deltaISize)) {
       aStatus = NS_INLINE_LINE_BREAK_BEFORE();
     } else {
       RubyUtils::SetReservedISize(this, deltaISize);
       aReflowState.mLineLayout->AdvanceICoord(deltaISize);
       isize = spanISize;
     }
-  }
-    // When there are spans, ReflowPairs and ReflowOnePair won't
+    // When there are spans, ReflowColumns and ReflowOneColumn won't
     // record any optional break position. We have to record one
     // at the end of this segment.
     if (!NS_INLINE_IS_BREAK(aStatus) && allowLineBreak &&
         aReflowState.mLineLayout->NotifyOptionalBreakPosition(
           this, INT32_MAX, startEdge + isize <= aReflowState.AvailableISize(),
           gfxBreakPriority::eNormalBreak)) {
       aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
     }
+  }
 
   DebugOnly<nscoord> lineSpanSize = aReflowState.mLineLayout->EndSpan(this);
   // When there are no frames inside the ruby base container, EndSpan
   // will return 0. However, in this case, the actual width of the
   // container could be non-zero because of non-empty ruby annotations.
   MOZ_ASSERT(NS_INLINE_IS_BREAK_BEFORE(aStatus) ||
              isize == lineSpanSize || mFrames.IsEmpty());
-  for (uint32_t i = 0; i < totalCount; i++) {
+  for (uint32_t i = 0; i < rtcCount; i++) {
     // It happens before the ruby text container is reflowed, and that
     // when it is reflowed, it will just use this size.
-    nsRubyTextContainerFrame* textContainer = i < rtcCount ?
-      mTextContainers[i] : mSpanContainers[i - rtcCount];
+    nsRubyTextContainerFrame* textContainer = textContainers[i];
     nsLineLayout* lineLayout = lineLayouts[i].get();
 
     RubyUtils::ClearReservedISize(textContainer);
     nscoord rtcISize = lineLayout->GetCurrentICoord();
     // Only span containers and containers with collapsed annotations
     // need reserving isize. For normal ruby text containers, their
     // children will be expanded properly. We only need to expand their
     // own size.
-    if (i < rtcCount) {
+    if (!textContainer->IsSpanContainer()) {
       rtcISize = isize;
     } else if (isize > rtcISize) {
       RubyUtils::SetReservedISize(textContainer, isize - rtcISize);
     }
 
     lineLayout->VerticalAlignLine();
     LogicalSize lineSize(lineWM, isize, lineLayout->GetFinalLineBSize());
-    textContainer->SetLineSize(lineSize);
+    aReflowState.mRubyReflowState->SetTextContainerInfo(i, textContainer, lineSize);
     lineLayout->EndLineReflow();
   }
 
   aDesiredSize.ISize(lineWM) = isize;
   nsLayoutUtils::SetBSizeFromFontMetrics(this, aDesiredSize, aReflowState,
                                          borderPadding, lineWM, frameWM);
 }
 
@@ -438,139 +443,139 @@ nsRubyBaseContainerFrame::Reflow(nsPresC
  * This struct stores the continuations after this frame and
  * corresponding text containers. It is used to speed up looking
  * ahead for nonempty continuations.
  */
 struct MOZ_STACK_CLASS nsRubyBaseContainerFrame::PullFrameState
 {
   ContinuationTraversingState mBase;
   nsAutoTArray<ContinuationTraversingState, RTC_ARRAY_SIZE> mTexts;
+  const TextContainerArray& mTextContainers;
 
-  explicit PullFrameState(nsRubyBaseContainerFrame* aFrame);
+  PullFrameState(nsRubyBaseContainerFrame* aBaseContainer,
+                 const TextContainerArray& aTextContainers);
 };
 
 nscoord
-nsRubyBaseContainerFrame::ReflowPairs(nsPresContext* aPresContext,
-                                      bool aAllowLineBreak,
-                                      const nsHTMLReflowState& aReflowState,
-                                      nsTArray<nsHTMLReflowState*>& aReflowStates,
-                                      nsReflowStatus& aStatus)
+nsRubyBaseContainerFrame::ReflowColumns(const ReflowState& aReflowState,
+                                        nsReflowStatus& aStatus)
 {
-  nsLineLayout* lineLayout = aReflowState.mLineLayout;
-  const uint32_t rtcCount = mTextContainers.Length();
+  nsLineLayout* lineLayout = aReflowState.mBaseReflowState.mLineLayout;
+  const uint32_t rtcCount = aReflowState.mTextContainers.Length();
   nscoord istart = lineLayout->GetCurrentICoord();
   nscoord icoord = istart;
   nsReflowStatus reflowStatus = NS_FRAME_COMPLETE;
   aStatus = NS_FRAME_COMPLETE;
 
-  mPairCount = 0;
-  nsIFrame* baseFrame = nullptr;
-  nsAutoTArray<nsIFrame*, RTC_ARRAY_SIZE> textFrames;
-  textFrames.SetCapacity(rtcCount);
-  PairEnumerator e(this, mTextContainers);
+  uint32_t columnIndex = 0;
+  RubyColumn column;
+  column.mTextFrames.SetCapacity(rtcCount);
+  RubyColumnEnumerator e(this, aReflowState.mTextContainers);
   for (; !e.AtEnd(); e.Next()) {
-    e.GetFrames(baseFrame, textFrames);
-    icoord += ReflowOnePair(aPresContext, aAllowLineBreak,
-                            aReflowState, aReflowStates,
-                            baseFrame, textFrames, reflowStatus);
+    e.GetColumn(column);
+    icoord += ReflowOneColumn(aReflowState, columnIndex, column, reflowStatus);
+    if (!NS_INLINE_IS_BREAK_BEFORE(reflowStatus)) {
+      columnIndex++;
+    }
     if (NS_INLINE_IS_BREAK(reflowStatus)) {
       break;
     }
     // We are not handling overflow here.
     MOZ_ASSERT(reflowStatus == NS_FRAME_COMPLETE);
   }
 
   bool isComplete = false;
-  PullFrameState pullFrameState(this);
+  PullFrameState pullFrameState(this, aReflowState.mTextContainers);
   while (!NS_INLINE_IS_BREAK(reflowStatus)) {
     // We are not handling overflow here.
     MOZ_ASSERT(reflowStatus == NS_FRAME_COMPLETE);
 
     // Try pull some frames from next continuations. This call replaces
-    // |baseFrame| and |textFrames| with the frame pulled in each level.
-    PullOnePair(lineLayout, pullFrameState, baseFrame, textFrames, isComplete);
+    // frames in |column| with the frame pulled in each level.
+    PullOneColumn(lineLayout, pullFrameState, column, isComplete);
     if (isComplete) {
       // No more frames can be pulled.
       break;
     }
-    icoord += ReflowOnePair(aPresContext, aAllowLineBreak,
-                            aReflowState, aReflowStates,
-                            baseFrame, textFrames, reflowStatus);
+    icoord += ReflowOneColumn(aReflowState, columnIndex, column, reflowStatus);
+    if (!NS_INLINE_IS_BREAK_BEFORE(reflowStatus)) {
+      columnIndex++;
+    }
   }
 
   if (!e.AtEnd() && NS_INLINE_IS_BREAK_AFTER(reflowStatus)) {
-    // The current pair has been successfully placed.
-    // Skip to the next pair and mark break before.
+    // The current column has been successfully placed.
+    // Skip to the next column and mark break before.
     e.Next();
-    e.GetFrames(baseFrame, textFrames);
+    e.GetColumn(column);
     reflowStatus = NS_INLINE_LINE_BREAK_BEFORE();
   }
   if (!e.AtEnd() || (GetNextInFlow() && !isComplete)) {
     NS_FRAME_SET_INCOMPLETE(aStatus);
   }
 
   if (NS_INLINE_IS_BREAK_BEFORE(reflowStatus)) {
-    if (!mPairCount || !mSpanContainers.IsEmpty()) {
-      // If no pair has been placed yet, or we have any span,
+    if (!columnIndex || !aReflowState.mAllowLineBreak) {
+      // If no column has been placed yet, or we have any span,
       // the whole container should be in the next line.
       aStatus = NS_INLINE_LINE_BREAK_BEFORE();
       return 0;
     }
     aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
-    MOZ_ASSERT(NS_FRAME_IS_COMPLETE(aStatus) || mSpanContainers.IsEmpty());
+    MOZ_ASSERT(NS_FRAME_IS_COMPLETE(aStatus) || aReflowState.mAllowLineBreak);
 
-    if (baseFrame) {
-      PushChildren(baseFrame, baseFrame->GetPrevSibling());
+    if (column.mBaseFrame) {
+      PushChildren(column.mBaseFrame, column.mBaseFrame->GetPrevSibling());
     }
     for (uint32_t i = 0; i < rtcCount; i++) {
-      nsIFrame* textFrame = textFrames[i];
+      nsIFrame* textFrame = column.mTextFrames[i];
       if (textFrame) {
-        mTextContainers[i]->PushChildren(textFrame,
-                                         textFrame->GetPrevSibling());
+        aReflowState.mTextContainers[i]->PushChildren(
+          textFrame, textFrame->GetPrevSibling());
       }
     }
   } else if (NS_INLINE_IS_BREAK_AFTER(reflowStatus)) {
     // |reflowStatus| being break after here may only happen when
-    // there is a break after the pair just pulled, or the whole
+    // there is a break after the column just pulled, or the whole
     // segment has been completely reflowed. In those cases, we do
     // not need to push anything.
     MOZ_ASSERT(e.AtEnd());
     aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
   }
 
   return icoord - istart;
 }
 
 nscoord
-nsRubyBaseContainerFrame::ReflowOnePair(nsPresContext* aPresContext,
-                                        bool aAllowLineBreak,
-                                        const nsHTMLReflowState& aReflowState,
-                                        nsTArray<nsHTMLReflowState*>& aReflowStates,
-                                        nsIFrame* aBaseFrame,
-                                        const nsTArray<nsIFrame*>& aTextFrames,
-                                        nsReflowStatus& aStatus)
+nsRubyBaseContainerFrame::ReflowOneColumn(const ReflowState& aReflowState,
+                                          uint32_t aColumnIndex,
+                                          const RubyColumn& aColumn,
+                                          nsReflowStatus& aStatus)
 {
-  WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
-  const uint32_t rtcCount = mTextContainers.Length();
-  MOZ_ASSERT(aTextFrames.Length() == rtcCount);
-  MOZ_ASSERT(aReflowStates.Length() == rtcCount);
-  nscoord istart = aReflowState.mLineLayout->GetCurrentICoord();
-  nscoord pairISize = 0;
+  const nsHTMLReflowState& baseReflowState = aReflowState.mBaseReflowState;
+  const auto& textReflowStates = aReflowState.mTextReflowStates;
+
+  WritingMode lineWM = baseReflowState.mLineLayout->GetWritingMode();
+  const uint32_t rtcCount = aReflowState.mTextContainers.Length();
+  MOZ_ASSERT(aColumn.mTextFrames.Length() == rtcCount);
+  MOZ_ASSERT(textReflowStates.Length() == rtcCount);
+  nscoord istart = baseReflowState.mLineLayout->GetCurrentICoord();
+  nscoord columnISize = 0;
 
   nsAutoString baseText;
-  if (aBaseFrame) {
-    if (!nsContentUtils::GetNodeTextContent(aBaseFrame->GetContent(),
+  if (aColumn.mBaseFrame) {
+    if (!nsContentUtils::GetNodeTextContent(aColumn.mBaseFrame->GetContent(),
                                             true, baseText)) {
       NS_RUNTIMEABORT("OOM");
     }
   }
 
   // Reflow text frames
   for (uint32_t i = 0; i < rtcCount; i++) {
-    nsIFrame* textFrame = aTextFrames[i];
+    nsIFrame* textFrame = aColumn.mTextFrames[i];
     if (textFrame) {
       MOZ_ASSERT(textFrame->GetType() == nsGkAtoms::rubyTextFrame);
       nsAutoString annotationText;
       if (!nsContentUtils::GetNodeTextContent(textFrame->GetContent(),
                                               true, annotationText)) {
         NS_RUNTIMEABORT("OOM");
       }
       // Per CSS Ruby spec, the content comparison for auto-hiding
@@ -580,139 +585,147 @@ nsRubyBaseContainerFrame::ReflowOnePair(
       // using the content tree text comparison is correct.
       if (annotationText.Equals(baseText)) {
         textFrame->AddStateBits(NS_RUBY_TEXT_FRAME_AUTOHIDE);
       } else {
         textFrame->RemoveStateBits(NS_RUBY_TEXT_FRAME_AUTOHIDE);
       }
 
       nsReflowStatus reflowStatus;
-      nsHTMLReflowMetrics metrics(*aReflowStates[i]);
+      nsHTMLReflowMetrics metrics(*textReflowStates[i]);
       RubyUtils::ClearReservedISize(textFrame);
 
       bool pushedFrame;
-      aReflowStates[i]->mLineLayout->ReflowFrame(textFrame, reflowStatus,
-                                                 &metrics, pushedFrame);
+      textReflowStates[i]->mLineLayout->ReflowFrame(textFrame, reflowStatus,
+                                                    &metrics, pushedFrame);
       MOZ_ASSERT(!NS_INLINE_IS_BREAK(reflowStatus) && !pushedFrame,
                  "Any line break inside ruby box should has been suppressed");
-      pairISize = std::max(pairISize, metrics.ISize(lineWM));
+      columnISize = std::max(columnISize, metrics.ISize(lineWM));
     }
   }
-  if (aAllowLineBreak && ShouldBreakBefore(aReflowState, pairISize)) {
+  if (aReflowState.mAllowLineBreak &&
+      ShouldBreakBefore(baseReflowState, columnISize)) {
     // Since ruby text container uses an independent line layout, it
     // may successfully place a frame because the line is empty while
     // the line of base container is not.
     aStatus = NS_INLINE_LINE_BREAK_BEFORE();
     return 0;
   }
 
   // Reflow the base frame
-  if (aBaseFrame) {
-    MOZ_ASSERT(aBaseFrame->GetType() == nsGkAtoms::rubyBaseFrame);
+  if (aColumn.mBaseFrame) {
+    MOZ_ASSERT(aColumn.mBaseFrame->GetType() == nsGkAtoms::rubyBaseFrame);
     nsReflowStatus reflowStatus;
-    nsHTMLReflowMetrics metrics(aReflowState);
-    RubyUtils::ClearReservedISize(aBaseFrame);
+    nsHTMLReflowMetrics metrics(baseReflowState);
+    RubyUtils::ClearReservedISize(aColumn.mBaseFrame);
 
     bool pushedFrame;
-    aReflowState.mLineLayout->ReflowFrame(aBaseFrame, reflowStatus,
-                                          &metrics, pushedFrame);
+    baseReflowState.mLineLayout->ReflowFrame(aColumn.mBaseFrame, reflowStatus,
+                                             &metrics, pushedFrame);
     MOZ_ASSERT(!NS_INLINE_IS_BREAK(reflowStatus) && !pushedFrame,
                "Any line break inside ruby box should has been suppressed");
-    pairISize = std::max(pairISize, metrics.ISize(lineWM));
+    columnISize = std::max(columnISize, metrics.ISize(lineWM));
   }
 
   // Align all the line layout to the new coordinate.
-  nscoord icoord = istart + pairISize;
-  nscoord deltaISize = icoord - aReflowState.mLineLayout->GetCurrentICoord();
+  nscoord icoord = istart + columnISize;
+  nscoord deltaISize = icoord - baseReflowState.mLineLayout->GetCurrentICoord();
   if (deltaISize > 0) {
-    aReflowState.mLineLayout->AdvanceICoord(deltaISize);
-    if (aBaseFrame) {
-      RubyUtils::SetReservedISize(aBaseFrame, deltaISize);
+    baseReflowState.mLineLayout->AdvanceICoord(deltaISize);
+    if (aColumn.mBaseFrame) {
+      RubyUtils::SetReservedISize(aColumn.mBaseFrame, deltaISize);
     }
   }
   for (uint32_t i = 0; i < rtcCount; i++) {
-    nsLineLayout* lineLayout = aReflowStates[i]->mLineLayout;
-    nsIFrame* textFrame = aTextFrames[i];
+    if (aReflowState.mTextContainers[i]->IsSpanContainer()) {
+      continue;
+    }
+    nsLineLayout* lineLayout = textReflowStates[i]->mLineLayout;
+    nsIFrame* textFrame = aColumn.mTextFrames[i];
     nscoord deltaISize = icoord - lineLayout->GetCurrentICoord();
     if (deltaISize > 0) {
       lineLayout->AdvanceICoord(deltaISize);
       if (textFrame) {
         RubyUtils::SetReservedISize(textFrame, deltaISize);
       }
     }
-    if (aBaseFrame && textFrame) {
+    if (aColumn.mBaseFrame && textFrame) {
       lineLayout->AttachLastFrameToBaseLineLayout();
     }
   }
 
-  mPairCount++;
-  if (aAllowLineBreak &&
-      aReflowState.mLineLayout->NotifyOptionalBreakPosition(
-        this, mPairCount, icoord <= aReflowState.AvailableISize(),
+  if (aReflowState.mAllowLineBreak &&
+      baseReflowState.mLineLayout->NotifyOptionalBreakPosition(
+        this, aColumnIndex + 1, icoord <= baseReflowState.AvailableISize(),
         gfxBreakPriority::eNormalBreak)) {
     aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
   }
 
-  return pairISize;
+  return columnISize;
 }
 
 nsRubyBaseContainerFrame::PullFrameState::PullFrameState(
-    nsRubyBaseContainerFrame* aFrame)
-  : mBase(aFrame)
+    nsRubyBaseContainerFrame* aBaseContainer,
+    const TextContainerArray& aTextContainers)
+  : mBase(aBaseContainer)
+  , mTextContainers(aTextContainers)
 {
-  const uint32_t rtcCount = aFrame->mTextContainers.Length();
+  const uint32_t rtcCount = aTextContainers.Length();
   for (uint32_t i = 0; i < rtcCount; i++) {
-    mTexts.AppendElement(aFrame->mTextContainers[i]);
+    mTexts.AppendElement(aTextContainers[i]);
   }
 }
 
 void
-nsRubyBaseContainerFrame::PullOnePair(nsLineLayout* aLineLayout,
-                                      PullFrameState& aPullFrameState,
-                                      nsIFrame*& aBaseFrame,
-                                      nsTArray<nsIFrame*>& aTextFrames,
-                                      bool& aIsComplete)
+nsRubyBaseContainerFrame::PullOneColumn(nsLineLayout* aLineLayout,
+                                        PullFrameState& aPullFrameState,
+                                        RubyColumn& aColumn,
+                                        bool& aIsComplete)
 {
-  const uint32_t rtcCount = mTextContainers.Length();
+  const TextContainerArray& textContainers = aPullFrameState.mTextContainers;
+  const uint32_t rtcCount = textContainers.Length();
 
-  aBaseFrame = PullNextInFlowChild(aPullFrameState.mBase);
-  aIsComplete = !aBaseFrame;
+  aColumn.mBaseFrame = PullNextInFlowChild(aPullFrameState.mBase);
+  aIsComplete = !aColumn.mBaseFrame;
 
-  aTextFrames.ClearAndRetainStorage();
+  aColumn.mTextFrames.ClearAndRetainStorage();
   for (uint32_t i = 0; i < rtcCount; i++) {
     nsIFrame* nextText =
-      mTextContainers[i]->PullNextInFlowChild(aPullFrameState.mTexts[i]);
-    aTextFrames.AppendElement(nextText);
+      textContainers[i]->PullNextInFlowChild(aPullFrameState.mTexts[i]);
+    aColumn.mTextFrames.AppendElement(nextText);
     // If there exists any frame in continations, we haven't
     // completed the reflow process.
     aIsComplete = aIsComplete && !nextText;
   }
 
   if (!aIsComplete) {
     // We pulled frames from the next line, hence mark it dirty.
     aLineLayout->SetDirtyNextLine();
   }
 }
 
 nscoord
-nsRubyBaseContainerFrame::ReflowSpans(nsPresContext* aPresContext,
-                                      const nsHTMLReflowState& aReflowState,
-                                      nsTArray<nsHTMLReflowState*>& aReflowStates)
+nsRubyBaseContainerFrame::ReflowSpans(const ReflowState& aReflowState)
 {
-  WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
-  const uint32_t spanCount = mSpanContainers.Length();
+  WritingMode lineWM =
+    aReflowState.mBaseReflowState.mLineLayout->GetWritingMode();
   nscoord spanISize = 0;
 
-  for (uint32_t i = 0; i < spanCount; i++) {
-    nsRubyTextContainerFrame* container = mSpanContainers[i];
+  for (uint32_t i = 0, iend = aReflowState.mTextContainers.Length();
+       i < iend; i++) {
+    nsRubyTextContainerFrame* container = aReflowState.mTextContainers[i];
+    if (!container->IsSpanContainer()) {
+      continue;
+    }
+
     nsIFrame* rtFrame = container->GetFirstPrincipalChild();
     nsReflowStatus reflowStatus;
-    nsHTMLReflowMetrics metrics(*aReflowStates[i]);
+    nsHTMLReflowMetrics metrics(*aReflowState.mTextReflowStates[i]);
     bool pushedFrame;
-    aReflowStates[i]->mLineLayout->ReflowFrame(rtFrame, reflowStatus,
-                                               &metrics, pushedFrame);
+    aReflowState.mTextReflowStates[i]->mLineLayout->
+      ReflowFrame(rtFrame, reflowStatus, &metrics, pushedFrame);
     MOZ_ASSERT(!NS_INLINE_IS_BREAK(reflowStatus) && !pushedFrame,
                "Any line break inside ruby box should has been suppressed");
     spanISize = std::max(spanISize, metrics.ISize(lineWM));
   }
 
   return spanISize;
 }
--- a/layout/generic/nsRubyBaseContainerFrame.h
+++ b/layout/generic/nsRubyBaseContainerFrame.h
@@ -8,26 +8,29 @@
 
 #ifndef nsRubyBaseContainerFrame_h___
 #define nsRubyBaseContainerFrame_h___
 
 #include "nsContainerFrame.h"
 #include "nsRubyTextContainerFrame.h"
 #include "nsRubyBaseFrame.h"
 #include "nsRubyTextFrame.h"
-
-#define RTC_ARRAY_SIZE 1
+#include "RubyReflowState.h"
 
 /**
  * Factory function.
  * @return a newly allocated nsRubyBaseContainerFrame (infallible)
  */
 nsContainerFrame* NS_NewRubyBaseContainerFrame(nsIPresShell* aPresShell,
                                                nsStyleContext* aContext);
 
+namespace mozilla {
+struct RubyColumn;
+}
+
 class nsRubyBaseContainerFrame MOZ_FINAL : public nsContainerFrame
 {
 public:
   NS_DECL_FRAMEARENA_HELPERS
   NS_DECL_QUERYFRAME_TARGET(nsRubyBaseContainerFrame)
   NS_DECL_QUERYFRAME
 
   // nsIFrame overrides
@@ -54,72 +57,40 @@ public:
 
   virtual nscoord
     GetLogicalBaseline(mozilla::WritingMode aWritingMode) const MOZ_OVERRIDE;
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const MOZ_OVERRIDE;
 #endif
 
-#ifdef DEBUG
-  void AssertTextContainersEmpty()
-  {
-    MOZ_ASSERT(mSpanContainers.IsEmpty());
-    MOZ_ASSERT(mTextContainers.IsEmpty());
-  }
-#endif
-
-  void AppendTextContainer(nsIFrame* aFrame);
-  void ClearTextContainers();
-
 protected:
   friend nsContainerFrame*
     NS_NewRubyBaseContainerFrame(nsIPresShell* aPresShell,
                                  nsStyleContext* aContext);
   explicit nsRubyBaseContainerFrame(nsStyleContext* aContext) : nsContainerFrame(aContext) {}
 
-  nscoord CalculateMaxSpanISize(nsRenderingContext* aRenderingContext);
-
-  nscoord ReflowPairs(nsPresContext* aPresContext,
-                      bool aAllowLineBreak,
-                      const nsHTMLReflowState& aReflowState,
-                      nsTArray<nsHTMLReflowState*>& aReflowStates,
-                      nsReflowStatus& aStatus);
+  typedef nsTArray<nsRubyTextContainerFrame*> TextContainerArray;
+  typedef nsAutoTArray<nsRubyTextContainerFrame*, RTC_ARRAY_SIZE> AutoTextContainerArray;
+  void GetTextContainers(TextContainerArray& aTextContainers);
 
-  nscoord ReflowOnePair(nsPresContext* aPresContext,
-                        bool aAllowLineBreak,
-                        const nsHTMLReflowState& aReflowState,
-                        nsTArray<nsHTMLReflowState*>& aReflowStates,
-                        nsIFrame* aBaseFrame,
-                        const nsTArray<nsIFrame*>& aTextFrames,
+  struct ReflowState;
+  nscoord ReflowColumns(const ReflowState& aReflowState,
                         nsReflowStatus& aStatus);
-
-  nscoord ReflowSpans(nsPresContext* aPresContext,
-                      const nsHTMLReflowState& aReflowState,
-                      nsTArray<nsHTMLReflowState*>& aReflowStates);
+  nscoord ReflowOneColumn(const ReflowState& aReflowState,
+                          uint32_t aColumnIndex,
+                          const mozilla::RubyColumn& aColumn,
+                          nsReflowStatus& aStatus);
+  nscoord ReflowSpans(const ReflowState& aReflowState);
 
   struct PullFrameState;
 
   // Pull ruby base and corresponding ruby text frames from
   // continuations after them.
-  void PullOnePair(nsLineLayout* aLineLayout,
-                   PullFrameState& aPullFrameState,
-                   nsIFrame*& aBaseFrame,
-                   nsTArray<nsIFrame*>& aTextFrames,
-                   bool& aIsComplete);
-
-  /**
-   * The arrays of ruby text containers below are filled before the ruby
-   * frame (parent) starts reflowing this ruby segment, and cleared when
-   * the reflow finishes.
-   */
-
-  // The text containers that contain a span, which spans all ruby
-  // pairs in the ruby segment.
-  nsTArray<nsRubyTextContainerFrame*> mSpanContainers;
-  // Normal text containers that do not contain spans.
-  nsTArray<nsRubyTextContainerFrame*> mTextContainers;
+  void PullOneColumn(nsLineLayout* aLineLayout,
+                     PullFrameState& aPullFrameState,
+                     mozilla::RubyColumn& aColumn,
+                     bool& aIsComplete);
 
   nscoord mBaseline;
-  uint32_t mPairCount;
 };
 
 #endif /* nsRubyBaseContainerFrame_h___ */
--- a/layout/generic/nsRubyFrame.cpp
+++ b/layout/generic/nsRubyFrame.cpp
@@ -5,16 +5,18 @@
  * http://mozilla.org/MPL/2.0/. */
 
 /* rendering object for CSS "display: ruby" */
 #include "nsRubyFrame.h"
 #include "nsLineLayout.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 #include "WritingModes.h"
+#include "RubyUtils.h"
+#include "RubyReflowState.h"
 #include "nsRubyBaseContainerFrame.h"
 #include "nsRubyTextContainerFrame.h"
 
 using namespace mozilla;
 
 //----------------------------------------------------------------------
 
 // Frame class boilerplate
@@ -54,81 +56,16 @@ nsRubyFrame::IsFrameOfType(uint32_t aFla
 #ifdef DEBUG_FRAME_DUMP
 nsresult
 nsRubyFrame::GetFrameName(nsAString& aResult) const
 {
   return MakeFrameName(NS_LITERAL_STRING("Ruby"), aResult);
 }
 #endif
 
-class MOZ_STACK_CLASS TextContainerIterator
-{
-public:
-  explicit TextContainerIterator(nsRubyBaseContainerFrame* aBaseContainer);
-  void Next();
-  bool AtEnd() const { return !mFrame; }
-  nsRubyTextContainerFrame* GetTextContainer() const
-  {
-    return static_cast<nsRubyTextContainerFrame*>(mFrame);
-  }
-
-private:
-  nsIFrame* mFrame;
-};
-
-TextContainerIterator::TextContainerIterator(
-    nsRubyBaseContainerFrame* aBaseContainer)
-{
-  mFrame = aBaseContainer;
-  Next();
-}
-
-void
-TextContainerIterator::Next()
-{
-  if (mFrame) {
-    mFrame = mFrame->GetNextSibling();
-    if (mFrame && mFrame->GetType() != nsGkAtoms::rubyTextContainerFrame) {
-      mFrame = nullptr;
-    }
-  }
-}
-
-/**
- * This class is responsible for appending and clearing
- * text container list of the base container.
- */
-class MOZ_STACK_CLASS AutoSetTextContainers
-{
-public:
-  explicit AutoSetTextContainers(nsRubyBaseContainerFrame* aBaseContainer);
-  ~AutoSetTextContainers();
-
-private:
-  nsRubyBaseContainerFrame* mBaseContainer;
-};
-
-AutoSetTextContainers::AutoSetTextContainers(
-    nsRubyBaseContainerFrame* aBaseContainer)
-  : mBaseContainer(aBaseContainer)
-{
-#ifdef DEBUG
-  aBaseContainer->AssertTextContainersEmpty();
-#endif
-  for (TextContainerIterator iter(aBaseContainer);
-       !iter.AtEnd(); iter.Next()) {
-    aBaseContainer->AppendTextContainer(iter.GetTextContainer());
-  }
-}
-
-AutoSetTextContainers::~AutoSetTextContainers()
-{
-  mBaseContainer->ClearTextContainers();
-}
-
 /**
  * This enumerator enumerates each segment.
  */
 class MOZ_STACK_CLASS SegmentEnumerator
 {
 public:
   explicit SegmentEnumerator(nsRubyFrame* aRubyFrame);
 
@@ -164,29 +101,27 @@ SegmentEnumerator::Next()
 }
 
 /* virtual */ void
 nsRubyFrame::AddInlineMinISize(nsRenderingContext *aRenderingContext,
                                nsIFrame::InlineMinISizeData *aData)
 {
   nscoord max = 0;
   for (SegmentEnumerator e(this); !e.AtEnd(); e.Next()) {
-    AutoSetTextContainers holder(e.GetBaseContainer());
     max = std::max(max, e.GetBaseContainer()->GetMinISize(aRenderingContext));
   }
   aData->currentLine += max;
 }
 
 /* virtual */ void
 nsRubyFrame::AddInlinePrefISize(nsRenderingContext *aRenderingContext,
                                 nsIFrame::InlinePrefISizeData *aData)
 {
   nscoord sum = 0;
   for (SegmentEnumerator e(this); !e.AtEnd(); e.Next()) {
-    AutoSetTextContainers holder(e.GetBaseContainer());
     sum += e.GetBaseContainer()->GetPrefISize(aRenderingContext);
   }
   aData->currentLine += sum;
 }
 
 /* virtual */ LogicalSize
 nsRubyFrame::ComputeSize(nsRenderingContext *aRenderingContext,
                            WritingMode aWM,
@@ -289,29 +224,30 @@ SanityCheckRubyPosition(int8_t aRubyPosi
 #endif
 
 void
 nsRubyFrame::ReflowSegment(nsPresContext* aPresContext,
                            const nsHTMLReflowState& aReflowState,
                            nsRubyBaseContainerFrame* aBaseContainer,
                            nsReflowStatus& aStatus)
 {
-  AutoSetTextContainers holder(aBaseContainer);
   WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
   LogicalSize availSize(lineWM, aReflowState.AvailableISize(),
                         aReflowState.AvailableBSize());
 
   nsAutoTArray<nsRubyTextContainerFrame*, RTC_ARRAY_SIZE> textContainers;
-  for (TextContainerIterator iter(aBaseContainer); !iter.AtEnd(); iter.Next()) {
+  for (RubyTextContainerIterator iter(aBaseContainer); !iter.AtEnd(); iter.Next()) {
     textContainers.AppendElement(iter.GetTextContainer());
   }
   const uint32_t rtcCount = textContainers.Length();
+  RubyReflowState rubyReflowState(lineWM, textContainers);
 
   nsHTMLReflowMetrics baseMetrics(aReflowState);
   bool pushedFrame;
+  aReflowState.mLineLayout->SetRubyReflowState(&rubyReflowState);
   aReflowState.mLineLayout->ReflowFrame(aBaseContainer, aStatus,
                                         &baseMetrics, pushedFrame);
 
   if (NS_INLINE_IS_BREAK_BEFORE(aStatus)) {
     if (aBaseContainer != mFrames.FirstChild()) {
       // Some segments may have been reflowed before, hence it is not
       // a break-before for the ruby container.
       aStatus = NS_INLINE_LINE_BREAK_AFTER(NS_FRAME_NOT_COMPLETE);
@@ -380,20 +316,23 @@ nsRubyFrame::ReflowSegment(nsPresContext
   // lines, so we use 0 instead. (i.e. we assume that the base container
   // is adjacent to the ruby frame's block-start edge.)
   // XXX We may need to add border/padding here. See bug 1055667.
   (lineWM.IsVertical() ? baseRect.x : baseRect.y) = 0;
   // The rect for offsets of text containers.
   nsRect offsetRect = baseRect;
   for (uint32_t i = 0; i < rtcCount; i++) {
     nsRubyTextContainerFrame* textContainer = textContainers[i];
+    rubyReflowState.AdvanceCurrentContainerIndex();
+
     nsReflowStatus textReflowStatus;
     nsHTMLReflowMetrics textMetrics(aReflowState);
     nsHTMLReflowState textReflowState(aPresContext, aReflowState,
                                       textContainer, availSize);
+    textReflowState.mRubyReflowState = &rubyReflowState;
     // FIXME We probably shouldn't be using the same nsLineLayout for
     //       the text containers. But it should be fine now as we are
     //       not actually using this line layout to reflow something,
     //       but just read the writing mode from it.
     textReflowState.mLineLayout = aReflowState.mLineLayout;
     textContainer->Reflow(aPresContext, textMetrics,
                           textReflowState, textReflowStatus);
     // Ruby text containers always return NS_FRAME_COMPLETE even when
--- a/layout/generic/nsRubyTextContainerFrame.cpp
+++ b/layout/generic/nsRubyTextContainerFrame.cpp
@@ -5,16 +5,17 @@
  * http://mozilla.org/MPL/2.0/. */
 
 /* rendering object for CSS "display: ruby-text-container" */
 
 #include "nsRubyTextContainerFrame.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 #include "WritingModes.h"
+#include "RubyReflowState.h"
 #include "mozilla/UniquePtr.h"
 
 using namespace mozilla;
 
 //----------------------------------------------------------------------
 
 // Frame class boilerplate
 // =======================
@@ -57,28 +58,86 @@ nsRubyTextContainerFrame::IsFrameOfType(
 {
   if (aFlags & eSupportsCSSTransforms) {
     return false;
   }
   return nsRubyTextContainerFrameSuper::IsFrameOfType(aFlags);
 }
 
 /* virtual */ void
+nsRubyTextContainerFrame::SetInitialChildList(ChildListID aListID,
+                                              nsFrameList& aChildList)
+{
+  nsRubyTextContainerFrameSuper::SetInitialChildList(aListID, aChildList);
+  UpdateSpanFlag();
+}
+
+/* virtual */ void
+nsRubyTextContainerFrame::AppendFrames(ChildListID aListID,
+                                       nsFrameList& aFrameList)
+{
+  nsRubyTextContainerFrameSuper::AppendFrames(aListID, aFrameList);
+  UpdateSpanFlag();
+}
+
+/* virtual */ void
+nsRubyTextContainerFrame::InsertFrames(ChildListID aListID,
+                                       nsIFrame* aPrevFrame,
+                                       nsFrameList& aFrameList)
+{
+  nsRubyTextContainerFrameSuper::InsertFrames(aListID, aPrevFrame, aFrameList);
+  UpdateSpanFlag();
+}
+
+/* virtual */ void
+nsRubyTextContainerFrame::RemoveFrame(ChildListID aListID,
+                                      nsIFrame* aOldFrame)
+{
+  nsRubyTextContainerFrameSuper::RemoveFrame(aListID, aOldFrame);
+  UpdateSpanFlag();
+}
+
+void
+nsRubyTextContainerFrame::UpdateSpanFlag()
+{
+  bool isSpan = false;
+  // The continuation checks are safe here because spans never break.
+  if (!GetPrevContinuation() && !GetNextContinuation()) {
+    nsIFrame* onlyChild = mFrames.OnlyChild();
+    if (onlyChild && onlyChild->IsPseudoFrame(GetContent())) {
+      // Per CSS Ruby spec, if the only child of an rtc frame is
+      // a pseudo rt frame, it spans all bases in the segment.
+      isSpan = true;
+    }
+  }
+
+  if (isSpan) {
+    AddStateBits(NS_RUBY_TEXT_CONTAINER_IS_SPAN);
+  } else {
+    RemoveStateBits(NS_RUBY_TEXT_CONTAINER_IS_SPAN);
+  }
+}
+
+/* virtual */ void
 nsRubyTextContainerFrame::Reflow(nsPresContext* aPresContext,
                                  nsHTMLReflowMetrics& aDesiredSize,
                                  const nsHTMLReflowState& aReflowState,
                                  nsReflowStatus& aStatus)
 {
   DO_GLOBAL_REFLOW_COUNT("nsRubyTextContainerFrame");
   DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
 
+  MOZ_ASSERT(aReflowState.mRubyReflowState, "No ruby reflow state provided");
+
   // All rt children have already been reflowed. All we need to do is
   // to report complete and return the desired size provided by the
   // ruby base container.
 
   // Although a ruby text container may have continuations, returning
   // NS_FRAME_COMPLETE here is still safe, since its parent, ruby frame,
   // ignores the status, and continuations of the ruby base container
   // will take care of our continuations.
   aStatus = NS_FRAME_COMPLETE;
   WritingMode lineWM = aReflowState.mLineLayout->GetWritingMode();
-  aDesiredSize.SetSize(lineWM, mLineSize);
+  const RubyReflowState::TextContainerInfo& info =
+    aReflowState.mRubyReflowState->GetCurrentTextContainerInfo(this);
+  aDesiredSize.SetSize(lineWM, info.mLineSize);
 }
--- a/layout/generic/nsRubyTextContainerFrame.h
+++ b/layout/generic/nsRubyTextContainerFrame.h
@@ -37,26 +37,37 @@ public:
                       nsHTMLReflowMetrics& aDesiredSize,
                       const nsHTMLReflowState& aReflowState,
                       nsReflowStatus& aStatus) MOZ_OVERRIDE;
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const MOZ_OVERRIDE;
 #endif
 
+  // nsContainerFrame overrides
+  virtual void SetInitialChildList(ChildListID aListID,
+                                   nsFrameList& aChildList) MOZ_OVERRIDE;
+  virtual void AppendFrames(ChildListID aListID,
+                            nsFrameList& aFrameList) MOZ_OVERRIDE;
+  virtual void InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+                            nsFrameList& aFrameList) MOZ_OVERRIDE;
+  virtual void RemoveFrame(ChildListID aListID,
+                           nsIFrame* aOldFrame) MOZ_OVERRIDE;
+
+  bool IsSpanContainer() const
+  {
+    return GetStateBits() & NS_RUBY_TEXT_CONTAINER_IS_SPAN;
+  }
+
 protected:
   friend nsContainerFrame*
     NS_NewRubyTextContainerFrame(nsIPresShell* aPresShell,
                                  nsStyleContext* aContext);
   explicit nsRubyTextContainerFrame(nsStyleContext* aContext)
-    : nsRubyTextContainerFrameSuper(aContext)
-    , mLineSize(mozilla::WritingMode(aContext)) {}
+    : nsRubyTextContainerFrameSuper(aContext) {}
 
+  void UpdateSpanFlag();
+
+  // For MoveOverflowToChildList
   friend class nsRubyBaseContainerFrame;
-  void SetLineSize(const mozilla::LogicalSize& aSize) { mLineSize = aSize; }
-
-  // The intended dimensions of the ruby text container. It is set by
-  // the corresponding ruby base container when the segment is reflowed,
-  // and used when the ruby text container is reflowed by its parent.
-  mozilla::LogicalSize mLineSize;
 };
 
 #endif /* nsRubyTextContainerFrame_h___ */
--- a/media/libpng/CHANGES
+++ b/media/libpng/CHANGES
@@ -5092,16 +5092,43 @@ Version 1.6.15rc02 [November 14, 2014]
     is different from POSIX make and other make programs.  Surround the
     macro definitions with ifndef guards (Cosmin).
 
 Version 1.6.15rc03 [November 16, 2014]
   Added "-D_CRT_SECURE_NO_WARNINGS" to CFLAGS in scripts/makefile.vcwin32.
   Removed the obsolete $ARCH variable from scripts/makefile.darwin.
 
 Version 1.6.15 [November 20, 2014]
+  No changes.
+
+Version 1.6.16beta01 [December 14, 2014]
+  Added ".align 2" to arm/filter_neon.S to support old GAS assemblers that
+    don't do alignment correctly.
+  Revised Makefile.am and scripts/symbols.dfn to work with MinGW/MSYS
+    (Bob Friesenhahn).
+
+Version 1.6.16beta02 [December 15, 2014]
+  Revised Makefile.am and scripts/*.dfn again to work with MinGW/MSYS;
+    renamed scripts/*.dfn to scripts/*.c (John Bowler).
+
+Version 1.6.16beta03 [December 21, 2014]
+  Quiet a "comparison always true" warning in pngstest.c (John Bowler).
+
+Version 1.6.16rc01 [December 21, 2014]
+  Restored a test on width that was removed from png.c at libpng-1.6.9
+    (Bug report by Alex Eubanks).
+
+Version 1.6.16rc02 [December 21, 2014]
+  Undid the update to pngrutil.c in 1.6.16rc01.
+
+Version 1.6.16rc03 [December 21, 2014]
+  Fixed an overflow in png_combine_row with very wide interlaced images.
+
+Version 1.6.16 [December 22, 2014]
+  No changes.
 
 Send comments/corrections/commendations to png-mng-implement at lists.sf.net
 (subscription required; visit
 https://lists.sourceforge.net/lists/listinfo/png-mng-implement
 to subscribe)
 or to glennrp at users.sourceforge.net
 
 Glenn R-P
--- a/media/libpng/LICENSE
+++ b/media/libpng/LICENSE
@@ -5,17 +5,17 @@ included in the libpng distribution, the
 
 COPYRIGHT NOTICE, DISCLAIMER, and LICENSE:
 
 If you modify libpng you may insert additional notices immediately following
 this sentence.
 
 This code is released under the libpng license.
 
-libpng versions 1.2.6, August 15, 2004, through 1.6.15, November 20, 2014, are
+libpng versions 1.2.6, August 15, 2004, through 1.6.16, December 22, 2014, are
 Copyright (c) 2004, 2006-2014 Glenn Randers-Pehrson, and are
 distributed according to the same disclaimer and license as libpng-1.2.5
 with the following individual added to the list of Contributing Authors
 
    Cosmin Truta
 
 libpng versions 1.0.7, July 1, 2000, through 1.2.5 - October 3, 2002, are
 Copyright (c) 2000-2002 Glenn Randers-Pehrson, and are
@@ -103,9 +103,9 @@ boxes and the like:
 Also, the PNG logo (in PNG format, of course) is supplied in the
 files "pngbar.png" and "pngbar.jpg (88x31) and "pngnow.png" (98x31).
 
 Libpng is OSI Certified Open Source Software.  OSI Certified Open Source is a
 certification mark of the Open Source Initiative.
 
 Glenn Randers-Pehrson
 glennrp at users.sourceforge.net
-November 20, 2014
+December 22, 2014
--- a/media/libpng/MOZCHANGES
+++ b/media/libpng/MOZCHANGES
@@ -1,37 +1,47 @@
 
 Changes made to pristine libpng source by mozilla.org developers.
 
+2014/12/22  -- Synced with libpng-1.6.16 (bug #1114360).
+               Added arm.patch file.
+
 2014/11/20  -- Synced with libpng-1.6.15 (bug #1102523).
 
 2014/10/23  -- Synced with libpng-1.6.14 (bug #1087841).
 
 2014/08/21  -- Synced with libpng-1.6.13 (bug #1021713).
                Renamed mozpngconf.h to pnglibconf.h.
+               Revised system arm/filter_neon.S to include the lines
+               #define PNG_READ_SUPPORTED and  #define MOZ_PNG_HAVE_ARM_NEON
+               because the moz.build system doesn't yet pass -DDefined
+               macros to the assembler.
 
 2014/03/21  -- Synced with libpng-1.6.10 (bug #980488) and disabled
                PNG_WARNINGS and PNG_ERROR_TEXT in non-debug builds.
                Limit image dimensions to 32767 (bug #251381, #591822,
                and #967656).
 
 2014/03/04  -- Enabled PNG_WARNINGS and other stuff in mozpngconf.h
                as part of enabling Freetype2 with PNG (bug #969814).
 
-2014/03/03  -- Fixed hang with empty IDAT (bug #974825).
+2014/03/03  -- Fixed hang with empty IDAT (bug #974825). This will be
+               fixed in libpng-1.6.10.
 
 2014/02/18  -- Exposed png_error(), etc. redefinitions to applications
-               (moved them from pngpriv.h to png.h).
+               (moved them from pngpriv.h to png.h). This will be fixed
+               in libpng-1.6.10.
 
 2014/02/18  -- Disabled PNG_FIXED_POINT_SUPPORTED in mozpngconf.h (we
                don't need both FIXED and FLOATING_POINT support).
 
 2014/02/15  -- Synced with libpng-1.6.9 (bug #952505).
 
 2014/02/11  -- Fixed crash with empty PLTE, CVE-2013-6954 (bug #945912).
+               This will be fixed in libpng-1.6.9.
 
 2013/12/11  -- Enable ARM support (bug #832390).
 
 2013/11/17  -- Synced with libpng-1.6.7 (bug #938740).
 
 2013/09/21  -- Synced with libpng-1.6.6 (bug #841734).
 
 2013/07/17  -- Synced with libpng-1.5.17 (bug #886499).
--- a/media/libpng/README
+++ b/media/libpng/README
@@ -1,9 +1,9 @@
-README for libpng version 1.6.15 - November 20, 2014 (shared library 16.0)
+README for libpng version 1.6.16 - December 22, 2014 (shared library 16.0)
 See the note about version numbers near the top of png.h
 
 See INSTALL for instructions on how to install libpng.
 
 Libpng comes in several distribution formats.  Get libpng-*.tar.gz or
 libpng-*.tar.xz or if you want UNIX-style line endings in the text files,
 or lpng*.7z or lpng*.zip if you want DOS-style line endings.
 
--- a/media/libpng/apng.patch
+++ b/media/libpng/apng.patch
@@ -273,28 +273,28 @@ Index: pngget.c
 +    return 0;
 +}
 +#endif /* APNG */
  #endif /* READ || WRITE */
 Index: png.h
 ===================================================================
 --- png.h
 +++ png.h
-@@ -473,6 +473,10 @@
+@@ -476,6 +476,10 @@
  #   include "pnglibconf.h"
  #endif
  
 +#define PNG_APNG_SUPPORTED
 +#define PNG_READ_APNG_SUPPORTED
 +#define PNG_WRITE_APNG_SUPPORTED
 +
  #ifndef PNG_VERSION_INFO_ONLY
     /* Machine specific configuration. */
  #  include "pngconf.h"
-@@ -563,6 +567,17 @@
+@@ -566,6 +570,17 @@
   * See pngconf.h for base types that vary by machine/system
   */
  
 +#ifdef PNG_APNG_SUPPORTED
 +/* dispose_op flags from inside fcTL */
 +#define PNG_DISPOSE_OP_NONE        0x00
 +#define PNG_DISPOSE_OP_BACKGROUND  0x01
 +#define PNG_DISPOSE_OP_PREVIOUS    0x02
@@ -302,39 +302,39 @@ Index: png.h
 +/* blend_op flags from inside fcTL */
 +#define PNG_BLEND_OP_SOURCE        0x00
 +#define PNG_BLEND_OP_OVER          0x01
 +#endif /* APNG */
 +
  /* This triggers a compiler error in png.c, if png.c and png.h
   * do not agree upon the version number.
   */
-@@ -883,6 +898,10 @@
+@@ -886,6 +901,10 @@
  #define PNG_INFO_sPLT 0x2000   /* ESR, 1.0.6 */
  #define PNG_INFO_sCAL 0x4000   /* ESR, 1.0.6 */
  #define PNG_INFO_IDAT 0x8000   /* ESR, 1.0.6 */
 +#ifdef PNG_APNG_SUPPORTED
 +#define PNG_INFO_acTL 0x10000
 +#define PNG_INFO_fcTL 0x20000
 +#endif
  
  /* This is used for the transformation routines, as some of them
   * change these values for the row.  It also should enable using
-@@ -920,6 +939,10 @@
+@@ -923,6 +942,10 @@
  #ifdef PNG_PROGRESSIVE_READ_SUPPORTED
  typedef PNG_CALLBACK(void, *png_progressive_info_ptr, (png_structp, png_infop));
  typedef PNG_CALLBACK(void, *png_progressive_end_ptr, (png_structp, png_infop));
 +#ifdef PNG_APNG_SUPPORTED
 +typedef PNG_CALLBACK(void, *png_progressive_frame_ptr, (png_structp,
 +    png_uint_32));
 +#endif
  
  /* The following callback receives png_uint_32 row_number, int pass for the
   * png_bytep data of the row.  When transforming an interlaced image the
-@@ -3259,6 +3282,75 @@
+@@ -3262,6 +3285,75 @@
   *  END OF HARDWARE AND SOFTWARE OPTIONS
   ******************************************************************************/
  
 +#ifdef PNG_APNG_SUPPORTED
 +PNG_EXPORT(245, png_uint_32, png_get_acTL, (png_structp png_ptr,
 +   png_infop info_ptr, png_uint_32 *num_frames, png_uint_32 *num_plays));
 +
 +PNG_EXPORT(246, png_uint_32, png_set_acTL, (png_structp png_ptr,
@@ -400,17 +400,17 @@ Index: png.h
 +PNG_EXPORT(264, void, png_write_frame_tail, (png_structp png_ptr,
 +   png_infop info_ptr));
 +#endif /* WRITE_APNG */
 +#endif /* APNG */
 +
  /* Maintainer: Put new public prototypes here ^, in libpng.3, in project
   * defs, and in scripts/symbols.def.
   */
-@@ -3267,7 +3359,11 @@
+@@ -3270,7 +3362,11 @@
   * one to use is one more than this.)
   */
  #ifdef PNG_EXPORT_LAST_ORDINAL
 +#ifdef PNG_APNG_SUPPORTED
 +  PNG_EXPORT_LAST_ORDINAL(264);
 +#else
    PNG_EXPORT_LAST_ORDINAL(244);
 +#endif /* APNG */
@@ -1018,17 +1018,17 @@ Index: pngset.c
 +
  #ifdef PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED
  static png_byte
  check_location(png_const_structrp png_ptr, int location)
 Index: pngrutil.c
 ===================================================================
 --- pngrutil.c
 +++ pngrutil.c
-@@ -818,6 +818,11 @@
+@@ -819,6 +819,11 @@
     filter_type = buf[11];
     interlace_type = buf[12];
  
 +#ifdef PNG_READ_APNG_SUPPORTED
 +   png_ptr->first_frame_width = width;
 +   png_ptr->first_frame_height = height;
 +#endif
 +
new file mode 100644
--- /dev/null
+++ b/media/libpng/arm.patch
@@ -0,0 +1,48 @@
+diff --git ../../../libpng-1.6.16/arm/arm_init.c arm/arm_init.c
+--- ../../../libpng-1.6.16/arm/arm_init.c	2014-12-21 22:08:08.000000000 -0500
++++ arm/arm_init.c	2014-12-22 17:33:57.556305506 -0500
+@@ -29,17 +29,17 @@
+  * You may set the macro PNG_ARM_NEON_FILE to the file name of file containing
+  * a fragment of C source code which defines the png_have_neon function.  There
+  * are a number of implementations in contrib/arm-neon, but the only one that
+  * has partial support is contrib/arm-neon/linux.c - a generic Linux
+  * implementation which reads /proc/cpufino.
+  */
+ #ifndef PNG_ARM_NEON_FILE
+ #  ifdef __linux__
+-#     define PNG_ARM_NEON_FILE "contrib/arm-neon/linux.c"
++#     define PNG_ARM_NEON_FILE "linux.c"
+ #  endif
+ #endif
+ 
+ #ifdef PNG_ARM_NEON_FILE
+ 
+ #include <signal.h> /* for sig_atomic_t */
+ static int png_have_neon(png_structp png_ptr);
+ #include PNG_ARM_NEON_FILE
+diff --git ../../../libpng-1.6.16/arm/filter_neon.S arm/filter_neon.S
+--- ../../../libpng-1.6.16/arm/filter_neon.S	2014-12-21 22:08:08.000000000 -0500
++++ arm/filter_neon.S	2014-12-22 17:43:31.588323649 -0500
+@@ -5,16 +5,22 @@
+  * Written by Mans Rullgard, 2011.
+  * Last changed in libpng 1.6.16 [December 22, 2014]
+  *
+  * This code is released under the libpng license.
+  * For conditions of distribution and use, see the disclaimer
+  * and license in png.h
+  */
+ 
++/* These are required because Mozilla's moz.build system doesn't pass
++ * -DDefined macros to the assembler.
++ */
++#define PNG_READ_SUPPORTED
++#define MOZ_PNG_HAVE_ARM_NEON
++
+ /* This is required to get the symbol renames, which are #defines, and the
+  * definitions (or not) of PNG_ARM_NEON_OPT and PNG_ARM_NEON_IMPLEMENTATION.
+  */
+ #define PNG_VERSION_INFO_ONLY
+ #include "../pngpriv.h"
+ 
+ #if defined(__linux__) && defined(__ELF__)
+ .section .note.GNU-stack,"",%progbits /* mark stack as non-executable */
--- a/media/libpng/arm/arm_init.c
+++ b/media/libpng/arm/arm_init.c
@@ -1,33 +1,34 @@
 
 /* arm_init.c - NEON optimised filter functions
  *
  * Copyright (c) 2014 Glenn Randers-Pehrson
  * Written by Mans Rullgard, 2011.
- * Last changed in libpng 1.6.10 [March 6, 2014]
+ * Last changed in libpng 1.6.16 [December 22, 2014]
  *
  * This code is released under the libpng license.
  * For conditions of distribution and use, see the disclaimer
  * and license in png.h
  */
 /* Below, after checking __linux__, various non-C90 POSIX 1003.1 functions are
  * called.
  */
 #define _POSIX_SOURCE 1
 
 #include "../pngpriv.h"
 
 #ifdef PNG_READ_SUPPORTED
+
 #if PNG_ARM_NEON_OPT > 0
 #ifdef PNG_ARM_NEON_CHECK_SUPPORTED /* Do run-time checks */
 /* WARNING: it is strongly recommended that you do not build libpng with
  * run-time checks for CPU features if at all possible.  In the case of the ARM
  * NEON instructions there is no processor-specific way of detecting the
- * presense of the required support, therefore run-time detectioon is extremely
+ * presence of the required support, therefore run-time detection is extremely
  * OS specific.
  *
  * You may set the macro PNG_ARM_NEON_FILE to the file name of file containing
  * a fragment of C source code which defines the png_have_neon function.  There
  * are a number of implementations in contrib/arm-neon, but the only one that
  * has partial support is contrib/arm-neon/linux.c - a generic Linux
  * implementation which reads /proc/cpufino.
  */
@@ -125,9 +126,9 @@ png_init_filter_functions_neon(png_struc
    {
       pp->read_filter[PNG_FILTER_VALUE_SUB-1] = png_read_filter_row_sub4_neon;
       pp->read_filter[PNG_FILTER_VALUE_AVG-1] = png_read_filter_row_avg4_neon;
       pp->read_filter[PNG_FILTER_VALUE_PAETH-1] =
           png_read_filter_row_paeth4_neon;
    }
 }
 #endif /* PNG_ARM_NEON_OPT > 0 */
-#endif /* PNG_READ_SUPPORTED */
+#endif /* READ */
--- a/media/libpng/arm/filter_neon.S
+++ b/media/libpng/arm/filter_neon.S
@@ -1,45 +1,45 @@
 
 /* filter_neon.S - NEON optimised filter functions
  *
- * Copyright (c) 2013 Glenn Randers-Pehrson
+ * Copyright (c) 2014 Glenn Randers-Pehrson
  * Written by Mans Rullgard, 2011.
- * Last changed in libpng 1.6.8 [December 19, 2013]
+ * Last changed in libpng 1.6.16 [December 22, 2014]
  *
  * This code is released under the libpng license.
  * For conditions of distribution and use, see the disclaimer
  * and license in png.h
  */
 
 /* These are required because Mozilla's moz.build system doesn't pass
  * -DDefined macros to the assembler.
  */
 #define PNG_READ_SUPPORTED
 #define MOZ_PNG_HAVE_ARM_NEON
 
-/* This is required to get the symbol renames, which are #defines, and also
- * includes the definition (or not) of PNG_ARM_NEON_OPT and
- * PNG_ARM_NEON_IMPLEMENTATION.
+/* This is required to get the symbol renames, which are #defines, and the
+ * definitions (or not) of PNG_ARM_NEON_OPT and PNG_ARM_NEON_IMPLEMENTATION.
  */
 #define PNG_VERSION_INFO_ONLY
 #include "../pngpriv.h"
 
 #if defined(__linux__) && defined(__ELF__)
 .section .note.GNU-stack,"",%progbits /* mark stack as non-executable */
 #endif
 
+#ifdef PNG_READ_SUPPORTED
+
 /* Assembler NEON support - only works for 32-bit ARM (i.e. it does not work for
  * ARM64).  The code in arm/filter_neon_intrinsics.c supports ARM64, however it
  * only works if -mfpu=neon is specified on the GCC command line.  See pngpriv.h
  * for the logic which sets PNG_USE_ARM_NEON_ASM:
  */
 #if PNG_ARM_NEON_IMPLEMENTATION == 2 /* hand-coded assembler */
 
-#ifdef PNG_READ_SUPPORTED
 #if PNG_ARM_NEON_OPT > 0
 
 #ifdef __ELF__
 #   define ELF
 #else
 #   define ELF @
 #endif
 
@@ -50,19 +50,18 @@
     .macro endfunc
 ELF     .size   \name, . - \name
         .endfunc
         .purgem endfunc
     .endm
         .text
 
         /* Explicitly specifying alignment here because some versions of
-           gas don't align code correctly. See
-           http://lists.gnu.org/archive/html/bug-binutils/2011-06/msg00199.html
-           and https://bugzilla.mozilla.org/show_bug.cgi?id=920992
+         * GAS don't align code correctly.  This is harmless in correctly
+         * written versions of GAS.
          */
         .align 2
 
     .if \export
         .global \name
     .endif
 ELF     .type   \name, STT_FUNC
         .func   \name
@@ -251,10 +250,10 @@ 1:
         vadd.u8         d3,  d3,  d7
         vst1.32         {d3[0]},  [r1], r4
         subs            r12, r12, #12
         bgt             1b
 
         pop             {r4,pc}
 endfunc
 #endif /* PNG_ARM_NEON_OPT > 0 */
-#endif /* PNG_READ_SUPPORTED */
 #endif /* PNG_ARM_NEON_IMPLEMENTATION == 2 (assembler) */
+#endif /* READ */
--- a/media/libpng/arm/filter_neon_intrinsics.c
+++ b/media/libpng/arm/filter_neon_intrinsics.c
@@ -1,24 +1,26 @@
 
 /* filter_neon_intrinsics.c - NEON optimised filter functions
  *
- * Copyright (c) 2013 Glenn Randers-Pehrson
+ * Copyright (c) 2014 Glenn Randers-Pehrson
  * Written by James Yu <james.yu at linaro.org>, October 2013.
  * Based on filter_neon.S, written by Mans Rullgard, 2011.
  *
- * Last changed in libpng 1.6.8 [December 19, 2013]
+ * Last changed in libpng 1.6.16 [December 22, 2014]
  *
  * This code is released under the libpng license.
  * For conditions of distribution and use, see the disclaimer
  * and license in png.h
  */
 
 #include "../pngpriv.h"
 
+#ifdef PNG_READ_SUPPORTED
+
 /* This code requires -mfpu=neon on the command line: */
 #if PNG_ARM_NEON_IMPLEMENTATION == 1 /* intrinsics code from pngpriv.h */
 
 #include <arm_neon.h>
 
 /* libpng row pointers are not necessarily aligned to any particular boundary,
  * however this code will only work with appropriate alignment.  arm/arm_init.c
  * checks for this (and will not compile unless it is done). This code uses
@@ -30,17 +32,16 @@
 /* The following relies on a variable 'temp_pointer' being declared with type
  * 'type'.  This is written this way just to hide the GCC strict aliasing
  * warning; note that the code is safe because there never is an alias between
  * the input and output pointers.
  */
 #define png_ldr(type,pointer)\
    (temp_pointer = png_ptr(type,pointer), *temp_pointer)
 
-#ifdef PNG_READ_SUPPORTED
 #if PNG_ARM_NEON_OPT > 0
 
 void
 png_read_filter_row_up_neon(png_row_infop row_info, png_bytep row,
    png_const_bytep prev_row)
 {
    png_bytep rp = row;
    png_bytep rp_stop = row + row_info->rowbytes;
@@ -363,10 +364,10 @@ png_read_filter_row_paeth4_neon(png_row_
 
       vlast = vpp.val[3];
 
       vst4_lane_u32(png_ptr(uint32_t,rp), png_ldr(uint32x2x4_t,&vdest), 0);
    }
 }
 
 #endif /* PNG_ARM_NEON_OPT > 0 */
-#endif /* PNG_READ_SUPPORTED */
 #endif /* PNG_ARM_NEON_IMPLEMENTATION == 1 (intrinsics) */
+#endif /* READ */
--- a/media/libpng/arm/linux.c
+++ b/media/libpng/arm/linux.c
@@ -1,28 +1,28 @@
 /* contrib/arm-neon/linux.c
  *
  * Copyright (c) 2014 Glenn Randers-Pehrson
  * Written by John Bowler, 2014.
- * Last changed in libpng 1.6.10 [March 6, 2014]
+ * Last changed in libpng 1.6.16 [December 22, 2014]
  *
  * This code is released under the libpng license.
  * For conditions of distribution and use, see the disclaimer
  * and license in png.h
  *
  * SEE contrib/arm-neon/README before reporting bugs
  *
  * STATUS: SUPPORTED
  * BUG REPORTS: png-mng-implement@sourceforge.net
  *
  * png_have_neon implemented for Linux by reading the widely available
  * pseudo-file /proc/cpuinfo.
  *
- * This code is strict ANSI-C and is probably moderately portable, it does
- * however use <stdio.h> and assumes that /proc/cpuinfo is never localized.
+ * This code is strict ANSI-C and is probably moderately portable; it does
+ * however use <stdio.h> and it assumes that /proc/cpuinfo is never localized.
  */
 #include <stdio.h>
 
 static int
 png_have_neon(png_structp png_ptr)
 {
    FILE *f = fopen("/proc/cpuinfo", "rb");
 
@@ -147,13 +147,15 @@ png_have_neon(png_structp png_ptr)
                break;
 
             default:
                png_error(png_ptr, "png_have_neon: internal error (bug)");
          }
       }
    }
 
+#ifdef PNG_WARNINGS_SUPPORTED
    else
       png_warning(png_ptr, "/proc/cpuinfo open failed");
+#endif
 
    return 0;
 }
--- a/media/libpng/libpng-manual.txt
+++ b/media/libpng/libpng-manual.txt
@@ -1,22 +1,22 @@
 libpng-manual.txt - A description on how to use and modify libpng
 
- libpng version 1.6.15 - November 20, 2014
+ libpng version 1.6.16 - December 22, 2014
  Updated and distributed by Glenn Randers-Pehrson
  <glennrp at users.sourceforge.net>
  Copyright (c) 1998-2014 Glenn Randers-Pehrson
 
  This document is released under the libpng license.
  For conditions of distribution and use, see the disclaimer
  and license in png.h
 
  Based on:
 
- libpng versions 0.97, January 1998, through 1.6.15 - November 20, 2014
+ libpng versions 0.97, January 1998, through 1.6.16 - December 22, 2014
  Updated and distributed by Glenn Randers-Pehrson
  Copyright (c) 1998-2014 Glenn Randers-Pehrson
 
  libpng 1.0 beta 6 - version 0.96 - May 28, 1997
  Updated and distributed by Andreas Dilger
  Copyright (c) 1996, 1997 Andreas Dilger
 
  libpng 1.0 beta 2 - version 0.88 - January 26, 1996
@@ -643,25 +643,22 @@ callback function:
       png_set_keep_unknown_chunks(read_ptr, 1, unused_chunks,
          (int)(sizeof unused_chunks)/5);
     #endif
 
 User limits
 
 The PNG specification allows the width and height of an image to be as
 large as 2^31-1 (0x7fffffff), or about 2.147 billion rows and columns.
-Since very few applications really need to process such large images,
-we have imposed an arbitrary 1-million limit on rows and columns.
 Larger images will be rejected immediately with a png_error() call. If
-you wish to change this limit, you can use
+you wish to reduce these limits, you can use
 
    png_set_user_limits(png_ptr, width_max, height_max);
 
-to set your own limits, or use width_max = height_max = 0x7fffffffL
-to allow all valid dimensions (libpng may reject some very large images
+to set your own limits (libpng may reject some very wide images
 anyway because of potential buffer overflow conditions).
 
 You should put this statement after you create the PNG structure and
 before calling png_read_info(), png_read_png(), or png_process_data().
 
 When writing a PNG datastream, put this statement before calling
 png_write_info() or png_write_png().
 
@@ -5032,29 +5029,29 @@ The following have been removed:
 The signatures of many exported functions were changed, such that
    png_structp became png_structrp or png_const_structrp
    png_infop became png_inforp or png_const_inforp
 where "rp" indicates a "restricted pointer".
 
 Error detection in some chunks has improved; in particular the iCCP chunk
 reader now does pretty complete validation of the basic format.  Some bad
 profiles that were previously accepted are now accepted with a warning or
-rejected, depending upon the png_set_benign_errors() setting, in particular the
-very old broken Microsoft/HP 3144-byte sRGB profile.  Starting with
+rejected, depending upon the png_set_benign_errors() setting, in particular
+the very old broken Microsoft/HP 3144-byte sRGB profile.  Starting with
 libpng-1.6.11, recognizing and checking sRGB profiles can be avoided by
 means of
 
     #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && \
         defined(PNG_SET_OPTION_SUPPORTED)
        png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE,
            PNG_OPTION_ON);
     #endif
 
-It's not a good idea to do this if you are using the "simplified API",
-which needs to be able to recognize an sRGB profile conveyed via the iCCP
+It's not a good idea to do this if you are using the new "simplified API",
+which needs to be able to recognize sRGB profiles conveyed via the iCCP
 chunk.
 
 The PNG spec requirement that only grayscale profiles may appear in images
 with color type 0 or 4 and that even if the image only contains gray pixels,
 only RGB profiles may appear in images with color type 2, 3, or 6, is now
 enforced.  The sRGB chunk is allowed to appear in images with any color type
 and is interpreted by libpng to convey a one-tracer-curve gray profile or a
 three-tracer-curve RGB profile as appropriate.
@@ -5272,23 +5269,23 @@ over "if (something)" and if "(!somethin
 We do not use the TAB character for indentation in the C sources.
 
 Lines do not exceed 80 characters.
 
 Other rules can be inferred by inspecting the libpng source.
 
 XVI. Y2K Compliance in libpng
 
-November 20, 2014
+December 22, 2014
 
 Since the PNG Development group is an ad-hoc body, we can't make
 an official declaration.
 
 This is your unofficial assurance that libpng from version 0.71 and
-upward through 1.6.15 are Y2K compliant.  It is my belief that earlier
+upward through 1.6.16 are Y2K compliant.  It is my belief that earlier
 versions were also Y2K compliant.
 
 Libpng only has two year fields.  One is a 2-byte unsigned integer
 that will hold years up to 65535.  The other, which is deprecated,
 holds the date in text format, and will hold years up to 9999.
 
 The integer is
     "png_uint_16 year" in png_time_struct.
--- a/media/libpng/png.c
+++ b/media/libpng/png.c
@@ -1,25 +1,25 @@
 
 /* png.c - location for general purpose libpng functions
  *
- * Last changed in libpng 1.6.15 [November 20, 2014]
+ * Last changed in libpng 1.6.16 [December 22, 2014]
  * Copyright (c) 1998-2014 Glenn Randers-Pehrson
  * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
  * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
  *
  * This code is released under the libpng license.
  * For conditions of distribution and use, see the disclaimer
  * and license in png.h
  */
 
 #include "pngpriv.h"
 
 /* Generate a compiler error if there is an old png.h in the search path. */
-typedef png_libpng_version_1_6_15 Your_png_h_is_not_version_1_6_15;
+typedef png_libpng_version_1_6_16 Your_png_h_is_not_version_1_6_16;
 
 /* Tells libpng that we have already handled the first "num_bytes" bytes
  * of the PNG file signature.  If the PNG data is embedded into another
  * stream we can set num_bytes = 8 so that libpng will not attempt to read
  * or write any of the magic bytes before it starts on the IHDR.
  */
 
 #ifdef PNG_READ_SUPPORTED
@@ -764,23 +764,23 @@ png_const_charp PNGAPI
 png_get_copyright(png_const_structrp png_ptr)
 {
    PNG_UNUSED(png_ptr)  /* Silence compiler warning about unused png_ptr */
 #ifdef PNG_STRING_COPYRIGHT
    return PNG_STRING_COPYRIGHT
 #else
 #  ifdef __STDC__
    return PNG_STRING_NEWLINE \
-     "libpng version 1.6.15 - November 20, 2014" PNG_STRING_NEWLINE \
+     "libpng version 1.6.16 - December 22, 2014" PNG_STRING_NEWLINE \
      "Copyright (c) 1998-2014 Glenn Randers-Pehrson" PNG_STRING_NEWLINE \
      "Copyright (c) 1996-1997 Andreas Dilger" PNG_STRING_NEWLINE \
      "Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc." \
      PNG_STRING_NEWLINE;
 #  else
-      return "libpng version 1.6.15 - November 20, 2014\
+      return "libpng version 1.6.16 - December 22, 2014\
       Copyright (c) 1998-2014 Glenn Randers-Pehrson\
       Copyright (c) 1996-1997 Andreas Dilger\
       Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc.";
 #  endif
 #endif
 }
 
 /* The following return the library version as a short string in the
@@ -1070,17 +1070,17 @@ png_colorspace_check_gamma(png_const_str
    return 1;
 }
 
 void /* PRIVATE */
 png_colorspace_set_gamma(png_const_structrp png_ptr,
    png_colorspacerp colorspace, png_fixed_point gAMA)
 {
    /* Changed in libpng-1.5.4 to limit the values to ensure overflow can't
-    * occur.  Since the fixed point representation is assymetrical it is
+    * occur.  Since the fixed point representation is asymetrical it is
     * possible for 1/gamma to overflow the limit of 21474 and this means the
     * gamma value must be at least 5/100000 and hence at most 20000.0.  For
     * safety the limits here are a little narrower.  The values are 0.00016 to
     * 6250.0, which are truly ridiculous gamma values (and will produce
     * displays that are all black or all white.)
     *
     * In 1.6.0 this test replaces the ones in pngrutil.c, in the gAMA chunk
     * handling code, which only required the value to be >0.
@@ -1319,17 +1319,17 @@ png_XYZ_from_xy(png_XYZ *XYZ, const png_
     *
     * So this code uses the perhaps slightly less optimal but more
     * understandable and totally obvious approach of calculating color-scale.
     *
     * This algorithm depends on the precision in white-scale and that is
     * (1/white-y), so we can immediately see that as white-y approaches 0 the
     * accuracy inherent in the cHRM chunk drops off substantially.
     *
-    * libpng arithmetic: a simple invertion of the above equations
+    * libpng arithmetic: a simple inversion of the above equations
     * ------------------------------------------------------------
     *
     *    white_scale = 1/white-y
     *    white-X = white-x * white-scale
     *    white-Y = 1.0
     *    white-Z = (1 - white-x - white-y) * white_scale
     *
     *    white-C = red-C + green-C + blue-C
@@ -1812,17 +1812,17 @@ png_icc_profile_error(png_const_structrp
          pos = png_safecat(message, (sizeof message), pos, "h: "); /*+2 = 116*/
       }
 #  endif
    /* The 'reason' is an arbitrary message, allow +79 maximum 195 */
    pos = png_safecat(message, (sizeof message), pos, reason);
    PNG_UNUSED(pos)
 
    /* This is recoverable, but make it unconditionally an app_error on write to
-    * avoid writing invalid ICC profiles into PNG files.  (I.e.  we handle them
+    * avoid writing invalid ICC profiles into PNG files (i.e., we handle them
     * on read, with a warning, but on write unless the app turns off
     * application errors the PNG won't be written.)
     */
    png_chunk_report(png_ptr, message,
       (colorspace != NULL) ? PNG_CHUNK_ERROR : PNG_CHUNK_WRITE_ERROR);
 
    return 0;
 }
@@ -1831,17 +1831,17 @@ png_icc_profile_error(png_const_structrp
 #ifdef PNG_sRGB_SUPPORTED
 int /* PRIVATE */
 png_colorspace_set_sRGB(png_const_structrp png_ptr, png_colorspacerp colorspace,
    int intent)
 {
    /* sRGB sets known gamma, end points and (from the chunk) intent. */
    /* IMPORTANT: these are not necessarily the values found in an ICC profile
     * because ICC profiles store values adapted to a D50 environment; it is
-    * expected that the ICC profile mediaWhitePointTag will be D50, see the
+    * expected that the ICC profile mediaWhitePointTag will be D50; see the
     * checks and code elsewhere to understand this better.
     *
     * These XYZ values, which are accurate to 5dp, produce rgb to gray
     * coefficients of (6968,23435,2366), which are reduced (because they add up
     * to 32769 not 32768) to (6968,23434,2366).  These are the values that
     * libpng has traditionally used (and are the best values given the 15bit
     * algorithm used by the rgb to gray code.)
     */
@@ -2456,16 +2456,27 @@ png_colorspace_set_rgb_coefficients(png_
       else
          png_error(png_ptr, "internal error handling cHRM->XYZ");
    }
 }
 #endif
 
 #endif /* COLORSPACE */
 
+#ifdef __GNUC__
+/* This exists solely to work round a warning from GNU C. */
+static int /* PRIVATE */
+png_gt(size_t a, size_t b)
+{
+    return a > b;
+}
+#else
+#   define png_gt(a,b) ((a) > (b))
+#endif
+
 void /* PRIVATE */
 png_check_IHDR(png_const_structrp png_ptr,
    png_uint_32 width, png_uint_32 height, int bit_depth,
    int color_type, int interlace_type, int compression_type,
    int filter_type)
 {
    int error = 0;
 
@@ -2475,16 +2486,38 @@ png_check_IHDR(png_const_structrp png_pt
       png_warning(png_ptr, "Image width is zero in IHDR");
       error = 1;
    }
    else if (width > PNG_UINT_31_MAX)
    {
       png_warning(png_ptr, "Invalid image width in IHDR");
       error = 1;
    }
+
+   else if (png_gt(width,
+                   (PNG_SIZE_MAX >> 3) /* 8-byte RGBA pixels */
+                   - 48                /* big_row_buf hack */
+                   - 1                 /* filter byte */
+                   - 7*8               /* rounding width to multiple of 8 pix */
+                   - 8))               /* extra max_pixel_depth pad */
+   {
+      /* The size of the row must be within the limits of this architecture.
+       * Because the read code can perform arbitrary transformations the
+       * maximum size is checked here.  Because the code in png_read_start_row
+       * adds extra space "for safety's sake" in several places a conservative
+       * limit is used here.
+       *
+       * NOTE: it would be far better to check the size that is actually used,
+       * but the effect in the real world is minor and the changes are more
+       * extensive, therefore much more dangerous and much more difficult to
+       * write in a way that avoids compiler warnings.
+       */
+      png_warning(png_ptr, "Image width is too large for this architecture");
+      error = 1;
+   }
    else
    {
 #     ifdef PNG_SET_USER_LIMITS_SUPPORTED
       if (width > png_ptr->user_width_max)
 #     else
       if (width > PNG_USER_WIDTH_MAX)
 #     endif
       {
--- a/media/libpng/png.h
+++ b/media/libpng/png.h
@@ -1,22 +1,22 @@
 
 /* png.h - header file for PNG reference library
  *
- * libpng version 1.6.15, November 20, 2014
+ * libpng version 1.6.16, December 22, 2014
  * Copyright (c) 1998-2014 Glenn Randers-Pehrson
  * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
  * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
  *
  * This code is released under the libpng license (See LICENSE, below)
  *
  * Authors and maintainers:
  *   libpng versions 0.71, May 1995, through 0.88, January 1996: Guy Schalnat
  *   libpng versions 0.89c, June 1996, through 0.96, May 1997: Andreas Dilger
- *   libpng versions 0.97, January 1998, through 1.6.15, November 20, 2014: Glenn
+ *   libpng versions 0.97, January 1998, through 1.6.16, December 22, 2014: Glenn
  *   See also "Contributing Authors", below.
  *
  * Note about libpng version numbers:
  *
  *   Due to various miscommunications, unforeseen code incompatibilities
  *   and occasional factors outside the authors' control, version numbering
  *   on the library has not always been consistent and straightforward.
  *   The following table summarizes matters since version 0.89c, which was
@@ -204,16 +204,19 @@
  *    1.6.13rc01-02           16    10613  16.so.16.13[.0]
  *    1.6.13                  16    10613  16.so.16.13[.0]
  *    1.6.14beta01-07         16    10614  16.so.16.14[.0]
  *    1.6.14rc01-02           16    10614  16.so.16.14[.0]
  *    1.6.14                  16    10614  16.so.16.14[.0]
  *    1.6.15beta01-08         16    10615  16.so.16.15[.0]
  *    1.6.15rc01-03           16    10615  16.so.16.15[.0]
  *    1.6.15                  16    10615  16.so.16.15[.0]
+ *    1.6.16beta01-03         16    10616  16.so.16.16[.0]
+ *    1.6.16rc01-02           16    10616  16.so.16.16[.0]
+ *    1.6.16                  16    10616  16.so.16.16[.0]
  *
  *   Henceforth the source version will match the shared-library major
  *   and minor numbers; the shared-library major version number will be
  *   used for changes in backward compatibility, as it is intended.  The
  *   PNG_LIBPNG_VER macro, which is not used within libpng but is available
  *   for applications, is an unsigned integer of the form xyyzz corresponding
  *   to the source version x.y.z (leading zeros in y and z).  Beta versions
  *   were given the previous public release number plus a letter, until
@@ -235,17 +238,17 @@
 /*
  * COPYRIGHT NOTICE, DISCLAIMER, and LICENSE:
  *
  * If you modify libpng you may insert additional notices immediately following
  * this sentence.
  *
  * This code is released under the libpng license.
  *
- * libpng versions 1.2.6, August 15, 2004, through 1.6.15, November 20, 2014, are
+ * libpng versions 1.2.6, August 15, 2004, through 1.6.16, December 22, 2014, are
  * Copyright (c) 2004, 2006-2014 Glenn Randers-Pehrson, and are
  * distributed according to the same disclaimer and license as libpng-1.2.5
  * with the following individual added to the list of Contributing Authors:
  *
  *    Cosmin Truta
  *
  * libpng versions 1.0.7, July 1, 2000, through 1.2.5, October 3, 2002, are
  * Copyright (c) 2000-2002 Glenn Randers-Pehrson, and are
@@ -347,23 +350,23 @@
  *
  * Thanks to Frank J. T. Wojcik for helping with the documentation.
  */
 
 /*
  * Y2K compliance in libpng:
  * =========================
  *
- *    November 20, 2014
+ *    December 22, 2014
  *
  *    Since the PNG Development group is an ad-hoc body, we can't make
  *    an official declaration.
  *
  *    This is your unofficial assurance that libpng from version 0.71 and
- *    upward through 1.6.15 are Y2K compliant.  It is my belief that
+ *    upward through 1.6.16 are Y2K compliant.  It is my belief that
  *    earlier versions were also Y2K compliant.
  *
  *    Libpng only has two year fields.  One is a 2-byte unsigned integer
  *    that will hold years up to 65535.  The other, which is deprecated,
  *    holds the date in text format, and will hold years up to 9999.
  *
  *    The integer is
  *        "png_uint_16 year" in png_time_struct.
@@ -415,27 +418,27 @@
  * file has been stripped from your copy of libpng, you can find it at
  * <http://www.libpng.org/pub/png/libpng-manual.txt>
  *
  * If you just need to read a PNG file and don't want to read the documentation
  * skip to the end of this file and read the section entitled 'simplified API'.
  */
 
 /* Version information for png.h - this should match the version in png.c */
-#define PNG_LIBPNG_VER_STRING "1.6.15"
+#define PNG_LIBPNG_VER_STRING "1.6.16"
 #define PNG_HEADER_VERSION_STRING \
-     " libpng version 1.6.15 - November 20, 2014\n"
+     " libpng version 1.6.16 - December 22, 2014\n"
 
 #define PNG_LIBPNG_VER_SONUM   16
 #define PNG_LIBPNG_VER_DLLNUM  16
 
 /* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */
 #define PNG_LIBPNG_VER_MAJOR   1
 #define PNG_LIBPNG_VER_MINOR   6
-#define PNG_LIBPNG_VER_RELEASE 15
+#define PNG_LIBPNG_VER_RELEASE 16
 
 /* This should match the numeric part of the final component of
  * PNG_LIBPNG_VER_STRING, omitting any leading zero:
  */
 
 #define PNG_LIBPNG_VER_BUILD  0
 
 /* Release Status */
@@ -456,17 +459,17 @@
 #define PNG_LIBPNG_BUILD_BASE_TYPE PNG_LIBPNG_BUILD_STABLE
 
 /* Careful here.  At one time, Guy wanted to use 082, but that would be octal.
  * We must not include leading zeros.
  * Versions 0.7 through 1.0.0 were in the range 0 to 100 here (only
  * version 1.0.0 was mis-numbered 100 instead of 10000).  From
  * version 1.0.1 it's    xxyyzz, where x=major, y=minor, z=release
  */
-#define PNG_LIBPNG_VER 10615 /* 1.6.15 */
+#define PNG_LIBPNG_VER 10616 /* 1.6.16 */
 
 /* Library configuration: these options cannot be changed after
  * the library has been built.
  */
 #ifndef PNGLCONF_H
     /* If pnglibconf.h is missing, you can
      * copy scripts/pnglibconf.h.prebuilt to pnglibconf.h
      */
@@ -576,17 +579,17 @@ extern "C" {
 /* blend_op flags from inside fcTL */
 #define PNG_BLEND_OP_SOURCE        0x00
 #define PNG_BLEND_OP_OVER          0x01
 #endif /* APNG */
 
 /* This triggers a compiler error in png.c, if png.c and png.h
  * do not agree upon the version number.
  */
-typedef char* png_libpng_version_1_6_15;
+typedef char* png_libpng_version_1_6_16;
 
 /* Basic control structions.  Read libpng-manual.txt or libpng.3 for more info.
  *
  * png_struct is the cache of information used while reading or writing a single
  * PNG file.  One of these is always required, although the simplified API
  * (below) hides the creation and destruction of it.
  */
 typedef struct png_struct_def png_struct;
--- a/media/libpng/pngconf.h
+++ b/media/libpng/pngconf.h
@@ -1,12 +1,12 @@
 
 /* pngconf.h - machine configurable file for libpng
  *
- * libpng version 1.6.15,November 20, 2014
+ * libpng version 1.6.16,December 22, 2014
  *
  * Copyright (c) 1998-2014 Glenn Randers-Pehrson
  * (Version 0.96 Copyright (c) 1996, 1997 Andreas Dilger)
  * (Version 0.88 Copyright (c) 1995, 1996 Guy Eric Schalnat, Group 42, Inc.)
  *
  * This code is released under the libpng license.
  * For conditions of distribution and use, see the disclaimer
  * and license in png.h
--- a/media/libpng/pngrutil.c
+++ b/media/libpng/pngrutil.c
@@ -783,16 +783,17 @@ png_inflate_read(png_structrp png_ptr, p
    {
       png_ptr->zstream.msg = PNGZ_MSG_CAST("zstream unclaimed");
       return Z_STREAM_ERROR;
    }
 }
 #endif
 
 /* Read and check the IDHR chunk */
+
 void /* PRIVATE */
 png_handle_IHDR(png_structrp png_ptr, png_inforp info_ptr, png_uint_32 length)
 {
    png_byte buf[13];
    png_uint_32 width, height;
    int bit_depth, color_type, compression_type, filter_type;
    int interlace_type;
 
@@ -852,18 +853,17 @@ png_handle_IHDR(png_structrp png_ptr, pn
          break;
 
       case PNG_COLOR_TYPE_RGB_ALPHA:
          png_ptr->channels = 4;
          break;
    }
 
    /* Set up other useful info */
-   png_ptr->pixel_depth = (png_byte)(png_ptr->bit_depth *
-   png_ptr->channels);
+   png_ptr->pixel_depth = (png_byte)(png_ptr->bit_depth * png_ptr->channels);
    png_ptr->rowbytes = PNG_ROWBYTES(png_ptr->pixel_depth, png_ptr->width);
    png_debug1(3, "bit_depth = %d", png_ptr->bit_depth);
    png_debug1(3, "channels = %d", png_ptr->channels);
    png_debug1(3, "rowbytes = %lu", (unsigned long)png_ptr->rowbytes);
    png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth,
        color_type, interlace_type, compression_type, filter_type);
 }
 
@@ -3177,17 +3177,17 @@ png_check_chunk_name(png_structrp png_pt
  * (dp) is filled from the start by replicating the available pixels.  If
  * 'display' is false only those pixels present in the pass are filled in.
  */
 void /* PRIVATE */
 png_combine_row(png_const_structrp png_ptr, png_bytep dp, int display)
 {
    unsigned int pixel_depth = png_ptr->transformed_pixel_depth;
    png_const_bytep sp = png_ptr->row_buf + 1;
-   png_uint_32 row_width = png_ptr->width;
+   png_alloc_size_t row_width = png_ptr->width;
    unsigned int pass = png_ptr->pass;
    png_bytep end_ptr = 0;
    png_byte end_byte = 0;
    unsigned int end_mask;
 
    png_debug(1, "in png_combine_row");
 
    /* Added in 1.5.6: it should not be possible to enter this routine until at
@@ -3452,17 +3452,17 @@ png_combine_row(png_const_structrp png_p
             /* When doing the 'block' algorithm the pixel in the pass gets
              * replicated to adjacent pixels.  This is why the even (0,2,4,6)
              * passes are skipped above - the entire expanded row is copied.
              */
             bytes_to_copy = (1<<((6-pass)>>1)) * pixel_depth;
 
             /* But don't allow this number to exceed the actual row width. */
             if (bytes_to_copy > row_width)
-               bytes_to_copy = row_width;
+               bytes_to_copy = (unsigned int)/*SAFE*/row_width;
          }
 
          else /* normal row; Adam7 only ever gives us one pixel to copy. */
             bytes_to_copy = pixel_depth;
 
          /* In Adam7 there is a constant offset between where the pixels go. */
          bytes_to_jump = PNG_PASS_COL_OFFSET(pass) * pixel_depth;
 
@@ -3632,17 +3632,17 @@ png_combine_row(png_const_structrp png_p
 
                   if (row_width <= bytes_to_jump)
                      return;
 
                   sp += bytes_to_jump;
                   dp += bytes_to_jump;
                   row_width -= bytes_to_jump;
                   if (bytes_to_copy > row_width)
-                     bytes_to_copy = row_width;
+                     bytes_to_copy = (unsigned int)/*SAFE*/row_width;
                }
          }
 
          /* NOT REACHED*/
       } /* pixel_depth >= 8 */
 
       /* Here if pixel_depth < 8 to check 'end_ptr' below. */
    }
@@ -4438,17 +4438,17 @@ png_read_start_row(png_structrp png_ptr)
    else
    {
       png_ptr->num_rows = png_ptr->height;
       png_ptr->iwidth = png_ptr->width;
    }
 
    max_pixel_depth = png_ptr->pixel_depth;
 
-   /* WARNING: * png_read_transform_info (pngrtran.c) performs a simpliar set of
+   /* WARNING: * png_read_transform_info (pngrtran.c) performs a simpler set of
     * calculations to calculate the final pixel depth, then
     * png_do_read_transforms actually does the transforms.  This means that the
     * code which effectively calculates this value is actually repeated in three
     * separate places.  They must all match.  Innocent changes to the order of
     * transformations can and will break libpng in a way that causes memory
     * overwrites.
     *
     * TODO: fix this.
--- a/mobile/android/base/ChromeCast.java
+++ b/mobile/android/base/ChromeCast.java
@@ -164,18 +164,21 @@ class ChromeCast implements GeckoMediaPl
             throw new IllegalStateException("Play services are required for Chromecast support (got status code " + status + ")");
         }
 
         this.context = context;
         this.route = route;
         this.canMirror = route.supportsControlCategory(CastMediaControlIntent.categoryForCast(MIRROR_RECEIVER_APP_ID));
     }
 
-    // This dumps everything we can find about the device into JSON. This will hopefully make it
-    // easier to filter out duplicate devices from different sources in js.
+    /**
+     *  This dumps everything we can find about the device into JSON. This will hopefully make it
+     *  easier to filter out duplicate devices from different sources in JS.
+     *  Returns null if the device can't be found.
+     */
     @Override
     public JSONObject toJSON() {
         final JSONObject obj = new JSONObject();
         try {
             final CastDevice device = CastDevice.getFromBundle(route.getExtras());
             if (device == null) {
                 return null;
             }
@@ -183,17 +186,17 @@ class ChromeCast implements GeckoMediaPl
             obj.put("uuid", route.getId());
             obj.put("version", device.getDeviceVersion());
             obj.put("friendlyName", device.getFriendlyName());
             obj.put("location", device.getIpAddress().toString());
             obj.put("modelName", device.getModelName());
             obj.put("mirror", canMirror);
             // For now we just assume all of these are Google devices
             obj.put("manufacturer", "Google Inc.");
-        } catch(JSONException ex) {
+        } catch (JSONException ex) {
             debug("Error building route", ex);
         }
 
         return obj;
     }
 
     @Override
     public void load(final String title, final String url, final String type, final EventCallback callback) {
--- a/mobile/android/base/GeckoMediaPlayer.java
+++ b/mobile/android/base/GeckoMediaPlayer.java
@@ -3,18 +3,23 @@
  * 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.json.JSONObject;
 import org.mozilla.gecko.util.EventCallback;
 
-/* Wrapper for MediaRouter types supported by Android, such as Chromecast, Miracast, etc. */
+/**
+ * Wrapper for MediaRouter types supported by Android, such as Chromecast, Miracast, etc.
+ */
 interface GeckoMediaPlayer {
+    /**
+     * Can return null.
+     */
     JSONObject toJSON();
     void load(String title, String url, String type, EventCallback callback);
     void play(EventCallback callback);
     void pause(EventCallback callback);
     void stop(EventCallback callback);
     void start(EventCallback callback);
     void end(EventCallback callback);
     void mirror(EventCallback callback);
--- a/mobile/android/base/MediaPlayerManager.java
+++ b/mobile/android/base/MediaPlayerManager.java
@@ -7,17 +7,19 @@ package org.mozilla.gecko;
 
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.support.v7.media.MediaControlIntent;
 import android.support.v7.media.MediaRouteSelector;
 import android.support.v7.media.MediaRouter;
 import android.support.v7.media.MediaRouter.RouteInfo;
 import android.util.Log;
+
 import com.google.android.gms.cast.CastMediaControlIntent;
+
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.mozglue.JNITarget;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSObject;
 
@@ -151,32 +153,40 @@ public class MediaPlayerManager extends 
             @Override
             public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
             }
 
             @Override
             public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) {
                 debug("onRouteAdded: route=" + route);
                 final GeckoMediaPlayer display = getMediaPlayerForRoute(route);
-                if (display != null) {
-                    displays.put(route.getId(), display);
-                    GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
-                            "MediaPlayer:Added", display.toJSON().toString()));
-                }
+                saveAndNotifyOfDisplay("MediaPlayer:Added", route, display);
             }
 
             @Override
             public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
                 debug("onRouteChanged: route=" + route);
                 final GeckoMediaPlayer display = displays.get(route.getId());
-                if (display != null) {
-                    displays.put(route.getId(), display);
-                    GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
-                            "MediaPlayer:Changed", display.toJSON().toString()));
+                saveAndNotifyOfDisplay("MediaPlayer:Changed", route, display);
+            }
+
+            private void saveAndNotifyOfDisplay(final String eventName,
+                    MediaRouter.RouteInfo route, final GeckoMediaPlayer display) {
+                if (display == null) {
+                    return;
                 }
+
+                final JSONObject json = display.toJSON();
+                if (json == null) {
+                    return;
+                }
+
+                displays.put(route.getId(), display);
+                final GeckoEvent event = GeckoEvent.createBroadcastEvent(eventName, json.toString());
+                GeckoAppShell.sendEventToGecko(event);
             }
         };
 
     private GeckoMediaPlayer getMediaPlayerForRoute(MediaRouter.RouteInfo route) {
         try {
             if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
                 return new ChromeCast(getActivity(), route);
             }
--- a/mobile/android/base/home/RecentTabsPanel.java
+++ b/mobile/android/base/home/RecentTabsPanel.java
@@ -391,16 +391,17 @@ public class RecentTabsPanel extends Hom
             final Cursor c = getCursor(position);
 
             if (itemType == ROW_HEADER) {
                 final String title = c.getString(c.getColumnIndexOrThrow(RecentTabs.TITLE));
                 final TextView textView = (TextView) view;
                 textView.setText(title);
             } else if (itemType == ROW_STANDARD) {
                 final TwoLinePageRow pageRow = (TwoLinePageRow) view;
+                pageRow.setShowIcons(false);
                 pageRow.updateFromCursor(c);
             }
          }
     }
 
     private class CursorLoaderCallbacks extends TransitionAwareCursorLoaderCallbacks {
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -1342,17 +1342,17 @@ Sync11Service.prototype = {
     }
     if (!this.identity.syncKeyBundle) {
       this._log.error("Can't get migration sentinel: no syncKeyBundle.");
       return Promise.resolve(null);
     }
     try {
       let collectionURL = this.storageURL + "meta/fxa_credentials";
       let cryptoWrapper = this.recordManager.get(collectionURL);
-      if (!cryptoWrapper.payload) {
+      if (!cryptoWrapper || !cryptoWrapper.payload) {
         // nothing to decrypt - .decrypt is noisy in that case, so just bail
         // now.
         return Promise.resolve(null);
       }
       // If the payload has a sentinel it means we must have put back the
       // decrypted version last time we were called.
       if (cryptoWrapper.payload.sentinel) {
         return Promise.resolve(cryptoWrapper.payload.sentinel);
--- a/toolkit/devtools/server/actors/memory.js
+++ b/toolkit/devtools/server/actors/memory.js
@@ -4,16 +4,18 @@
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
 let protocol = require("devtools/server/protocol");
 let { method, RetVal, Arg, types } = protocol;
 const { reportException } = require("devtools/toolkit/DevToolsUtils");
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
+loader.lazyRequireGetter(this, "StackFrameCache",
+                         "devtools/server/actors/utils/stack", true);
 
 /**
  * A method decorator that ensures the actor is in the expected state before
  * proceeding. If the actor is not in the expected state, the decorated method
  * returns a rejected promise.
  *
  * @param String expectedState
  *        The expected state.
@@ -55,27 +57,24 @@ let MemoryActor = protocol.ActorClass({
 
   get dbg() {
     if (!this._dbg) {
       this._dbg = this.parent.makeDebugger();
     }
     return this._dbg;
   },
 
-  initialize: function(conn, parent) {
+  initialize: function(conn, parent, frameCache = new StackFrameCache()) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.parent = parent;
     this._mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
                   .getService(Ci.nsIMemoryReporterManager);
     this.state = "detached";
     this._dbg = null;
-    this._framesToCounts = null;
-    this._framesToIndices = null;
-    this._framesToForms = null;
-
+    this._frameCache = frameCache;
     this._onWindowReady = this._onWindowReady.bind(this);
 
     events.on(this.parent, "window-ready", this._onWindowReady);
   },
 
   destroy: function() {
     events.off(this.parent, "window-ready", this._onWindowReady);
 
@@ -119,46 +118,30 @@ let MemoryActor = protocol.ActorClass({
       if (this.dbg.memory.trackingAllocationSites) {
         this.dbg.memory.drainAllocationsLog();
       }
       this._clearFrames();
       this.dbg.removeAllDebuggees();
     }
   },
 
-  _initFrames: function() {
-    if (this._framesToCounts) {
-      // The maps are already initialized.
-      return;
-    }
-
-    this._framesToCounts = new Map();
-    this._framesToIndices = new Map();
-    this._framesToForms = new Map();
-  },
-
   _clearFrames: function() {
     if (this.dbg.memory.trackingAllocationSites) {
-      this._framesToCounts.clear();
-      this._framesToCounts = null;
-      this._framesToIndices.clear();
-      this._framesToIndices = null;
-      this._framesToForms.clear();
-      this._framesToForms = null;
+      this._frameCache.clearFrames();
     }
   },
 
   /**
    * Handler for the parent actor's "window-ready" event.
    */
   _onWindowReady: function({ isTopLevel }) {
     if (this.state == "attached") {
       if (isTopLevel && this.dbg.memory.trackingAllocationSites) {
         this._clearDebuggees();
-        this._initFrames();
+        nthis._frameCache.initFrames();
       }
       this.dbg.addDebuggees();
     }
   },
 
   /**
    * Take a census of the heap. See js/src/doc/Debugger/Debugger.Memory.md for
    * more information.
@@ -172,17 +155,17 @@ let MemoryActor = protocol.ActorClass({
 
   /**
    * Start recording allocation sites.
    *
    * @param AllocationsRecordingOptions options
    *        See the protocol.js definition of AllocationsRecordingOptions above.
    */
   startRecordingAllocations: method(expectState("attached", function(options = {}) {
-    this._initFrames();
+    this._frameCache.initFrames();
     this.dbg.memory.allocationSamplingProbability = options.probability != null
       ? options.probability
       : 1.0;
     this.dbg.memory.trackingAllocationSites = true;
   }), {
     request: {
       options: Arg(0, "nullable:AllocationsRecordingOptions")
     },
@@ -273,104 +256,28 @@ let MemoryActor = protocol.ActorClass({
 
       // Safe because SavedFrames are frozen/immutable.
       let waived = Cu.waiveXrays(stack);
 
       // Ensure that we have a form, count, and index for new allocations
       // because we potentially haven't seen some or all of them yet. After this
       // loop, we can rely on the fact that every frame we deal with already has
       // its metadata stored.
-      this._assignFrameIndices(waived);
-      this._createFrameForms(waived);
-      this._countFrame(waived);
+      let index = this._frameCache.addFrame(waived);
 
-      packet.allocations.push(this._framesToIndices.get(waived));
+      packet.allocations.push(index);
       packet.allocationsTimestamps.push(timestamp);
     }
 
-    // Now that we are guaranteed to have a form for every frame, we know the
-    // size the "frames" property's array must be. We use that information to
-    // create dense arrays even though we populate them out of order.
-    const size = this._framesToForms.size;
-    packet.frames = Array(size).fill(null);
-    packet.counts = Array(size).fill(0);
-
-    // Populate the "frames" and "counts" properties.
-    for (let [stack, index] of this._framesToIndices) {
-      packet.frames[index] = this._framesToForms.get(stack);
-      packet.counts[index] = this._framesToCounts.get(stack) || 0;
-    }
-
-    return packet;
+    return this._frameCache.updateFramePacket(packet);
   }), {
     request: {},
     response: RetVal("json")
   }),
 
-  /**
-   * Assigns an index to the given frame and its parents, if an index is not
-   * already assigned.
-   *
-   * @param SavedFrame frame
-   *        A frame to assign an index to.
-   */
-  _assignFrameIndices: function(frame) {
-    if (this._framesToIndices.has(frame)) {
-      return;
-    }
-
-    if (frame) {
-      this._assignFrameIndices(frame.parent);
-    }
-
-    const index = this._framesToIndices.size;
-    this._framesToIndices.set(frame, index);
-  },
-
-  /**
-   * Create the form for the given frame, if one doesn't already exist.
-   *
-   * @param SavedFrame frame
-   *        A frame to create a form for.
-   */
-  _createFrameForms: function(frame) {
-    if (this._framesToForms.has(frame)) {
-      return;
-    }
-
-    let form = null;
-    if (frame) {
-      form = {
-        line: frame.line,
-        column: frame.column,
-        source: frame.source,
-        functionDisplayName: frame.functionDisplayName,
-        parent: this._framesToIndices.get(frame.parent)
-      };
-      this._createFrameForms(frame.parent);
-    }
-
-    this._framesToForms.set(frame, form);
-  },
-
-  /**
-   * Increment the allocation count for the provided frame.
-   *
-   * @param SavedFrame frame
-   *        The frame whose allocation count should be incremented.
-   */
-  _countFrame: function(frame) {
-    if (!this._framesToCounts.has(frame)) {
-      this._framesToCounts.set(frame, 1);
-    } else {
-      let count = this._framesToCounts.get(frame);
-      this._framesToCounts.set(frame, count + 1);
-    }
-  },
-
   /*
    * Force a browser-wide GC.
    */
   forceGarbageCollection: method(function() {
     for (let i = 0; i < 3; i++) {
       Cu.forceGC();
     }
   }, {
--- a/toolkit/devtools/server/actors/timeline.js
+++ b/toolkit/devtools/server/actors/timeline.js
@@ -23,16 +23,17 @@
 
 const {Ci, Cu} = require("chrome");
 const protocol = require("devtools/server/protocol");
 const {method, Arg, RetVal, Option} = protocol;
 const events = require("sdk/event/core");
 const {setTimeout, clearTimeout} = require("sdk/timers");
 const {MemoryActor} = require("devtools/server/actors/memory");
 const {FramerateActor} = require("devtools/server/actors/framerate");
+const {StackFrameCache} = require("devtools/server/actors/utils/stack");
 
 // How often do we pull markers from the docShells, and therefore, how often do
 // we send events to the front (knowing that when there are no markers in the
 // docShell, no event is sent).
 const DEFAULT_TIMELINE_DATA_PULL_TIMEOUT = 200; // ms
 
 /**
  * Type representing an array of numbers as strings, serialized fast(er).
@@ -81,25 +82,32 @@ let TimelineActor = exports.TimelineActo
     /**
      * "ticks" events (from the refresh driver) emitted in tandem with "markers",
      * if this was enabled when the recording started.
      */
     "ticks" : {
       type: "ticks",
       delta: Arg(0, "number"),
       timestamps: Arg(1, "array-of-numbers-as-strings")
+    },
+
+    "frames" : {
+      type: "frames",
+      delta: Arg(0, "number"),
+      frames: Arg(1, "json")
     }
   },
 
   initialize: function(conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
 
     this._isRecording = false;
     this._startTime = 0;
+    this._stackFrames = null;
 
     // Make sure to get markers from new windows as they become available
     this._onWindowReady = this._onWindowReady.bind(this);
     events.on(this.tabActor, "window-ready", this._onWindowReady);
   },
 
   /**
    * The timeline actor is the first (and last) in its hierarchy to use protocol.js
@@ -165,16 +173,35 @@ let TimelineActor = exports.TimelineActo
 
     let endTime = this.docShells[0].now();
     let markers = [];
 
     for (let docShell of this.docShells) {
       markers = [...markers, ...docShell.popProfileTimelineMarkers()];
     }
 
+    // The docshell may return markers with stack traces attached.
+    // Here we transform the stack traces via the stack frame cache,
+    // which lets us preserve tail sharing when transferring the
+    // frames to the client.  We must waive xrays here because Firefox
+    // doesn't understand that the Debugger.Frame object is safe to
+    // use from chrome.  See Tutorial-Alloc-Log-Tree.md.
+    for (let marker of markers) {
+      if (marker.stack) {
+        marker.stack = this._stackFrames.addFrame(Cu.waiveXrays(marker.stack));
+      }
+      if (marker.endStack) {
+        marker.endStack = this._stackFrames.addFrame(Cu.waiveXrays(marker.endStack));
+      }
+    }
+
+    let frames = this._stackFrames.makeEvent();
+    if (frames) {
+      events.emit(this, "frames", endTime, frames);
+    }
     if (markers.length > 0) {
       events.emit(this, "markers", markers, endTime);
     }
     if (this._memoryActor) {
       events.emit(this, "memory", endTime, this._memoryActor.measure());
     }
     if (this._framerateActor) {
       events.emit(this, "ticks", endTime, this._framerateActor.getPendingTicks());
@@ -201,23 +228,26 @@ let TimelineActor = exports.TimelineActo
    * Start recording profile markers.
    */
   start: method(function({ withMemory, withTicks }) {
     if (this._isRecording) {
       return;
     }
     this._isRecording = true;
     this._startTime = this.docShells[0].now();
+    this._stackFrames = new StackFrameCache();
+    this._stackFrames.initFrames();
 
     for (let docShell of this.docShells) {
       docShell.recordProfileTimelineMarkers = true;
     }
 
     if (withMemory) {
-      this._memoryActor = new MemoryActor(this.conn, this.tabActor);
+      this._memoryActor = new MemoryActor(this.conn, this.tabActor,
+                                          this._stackFrames);
       events.emit(this, "memory", this._startTime, this._memoryActor.measure());
     }
     if (withTicks) {
       this._framerateActor = new FramerateActor(this.conn, this.tabActor);
       this._framerateActor.startRecording();
     }
 
     this._pullTimelineData();
@@ -235,16 +265,17 @@ let TimelineActor = exports.TimelineActo
   /**
    * Stop recording profile markers.
    */
   stop: method(function() {
     if (!this._isRecording) {
       return;
     }
     this._isRecording = false;
+    this._stackFrames = null;
 
     if (this._memoryActor) {
       this._memoryActor = null;
     }
     if (this._framerateActor) {
       this._framerateActor.stopRecording();
       this._framerateActor = null;
     }
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/utils/stack.js
@@ -0,0 +1,202 @@
+/* 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";
+
+let {Class} = require("sdk/core/heritage");
+
+/**
+ * A helper class that stores stack frame objects.  Each frame is
+ * assigned an index, and if a frame is added more than once, the same
+ * index is used.  Users of the class can get an array of all frames
+ * that have been added.
+ */
+let StackFrameCache = Class({
+  /**
+   * Initialize this object.
+   */
+  initialize: function() {
+    this._framesToCounts = null;
+    this._framesToIndices = null;
+    this._framesToForms = null;
+    this._lastEventSize = -1;
+  },
+
+  /**
+   * Prepare to accept frames.
+   */
+  initFrames: function() {
+    if (this._framesToCounts) {
+      // The maps are already initialized.
+      return;
+    }
+
+    this._framesToCounts = new Map();
+    this._framesToIndices = new Map();
+    this._framesToForms = new Map();
+    this._lastEventSize = -1;
+  },
+
+  /**
+   * Forget all stored frames and reset to the initialized state.
+   */
+  clearFrames: function() {
+    this._framesToCounts.clear();
+    this._framesToCounts = null;
+    this._framesToIndices.clear();
+    this._framesToIndices = null;
+    this._framesToForms.clear();
+    this._framesToForms = null;
+    this._lastEventSize = -1;
+  },
+
+  /**
+   * Add a frame to this stack frame cache, and return the index of
+   * the frame.
+   */
+  addFrame: function(frame) {
+    this._assignFrameIndices(frame);
+    this._createFrameForms(frame);
+    this._countFrame(frame);
+    return this._framesToIndices.get(frame);
+  },
+
+  /**
+   * A helper method for the memory actor.  This populates the packet
+   * object with "frames" and "counts" properties.  Each of these
+   * properties will be an array indexed by frame ID.  "frames" will
+   * contain frame objects (see makeEvent) and "counts" will hold
+   * allocation counts for each frame.
+   *
+   * @param packet
+   *        The packet to update.
+   *
+   * @returns packet
+   */
+  updateFramePacket: function(packet) {
+    // Now that we are guaranteed to have a form for every frame, we know the
+    // size the "frames" property's array must be. We use that information to
+    // create dense arrays even though we populate them out of order.
+    const size = this._framesToForms.size;
+    packet.frames = Array(size).fill(null);
+    packet.counts = Array(size).fill(0);
+
+    // Populate the "frames" and "counts" properties.
+    for (let [stack, index] of this._framesToIndices) {
+      packet.frames[index] = this._framesToForms.get(stack);
+      packet.counts[index] = this._framesToCounts.get(stack) || 0;
+    }
+
+    return packet;
+  },
+
+  /**
+   * If any new stack frames have been added to this cache since the
+   * last call to makeEvent (clearing the cache also resets the "last
+   * call"), then return a new array describing the new frames.  If no
+   * new frames are available, return null.
+   *
+   * The frame cache assumes that the user of the cache keeps track of
+   * all previously-returned arrays and, in theory, concatenates them
+   * all to form a single array holding all frames added to the cache
+   * since the last reset.  This concatenated array can be indexed by
+   * the frame ID.  The array returned by this function, though, is
+   * dense and starts at 0.
+   *
+   * Each element in the array is an object of the form:
+   * {
+   *   line: <line number for this frame>,
+   *   column: <column number for this frame>,
+   *   source: <filename string for this frame>,
+   *   functionDisplayName: <this frame's inferred function name function or null>,
+   *   parent: <frame ID -- an index into the concatenated array mentioned above>
+   * }
+   *
+   * The intent of this approach is to make it simpler to efficiently
+   * send frame information over the debugging protocol, by only
+   * sending new frames.
+   *
+   * @returns array or null
+   */
+  makeEvent: function() {
+    const size = this._framesToForms.size;
+    if (!size || size <= this._lastEventSize) {
+      return null;
+    }
+
+    let packet = Array(size - this._lastEventSize).fill(null);
+    for (let [stack, index] of this._framesToIndices) {
+      if (index > this._lastEventSize) {
+        packet[index - this._lastEventSize - 1] = this._framesToForms.get(stack);
+      }
+    }
+
+    this._lastEventSize = size;
+
+    return packet;
+  },
+
+  /**
+   * Assigns an index to the given frame and its parents, if an index is not
+   * already assigned.
+   *
+   * @param SavedFrame frame
+   *        A frame to assign an index to.
+   */
+  _assignFrameIndices: function(frame) {
+    if (this._framesToIndices.has(frame)) {
+      return;
+    }
+
+    if (frame) {
+      this._assignFrameIndices(frame.parent);
+    }
+
+    const index = this._framesToIndices.size;
+    this._framesToIndices.set(frame, index);
+  },
+
+  /**
+   * Create the form for the given frame, if one doesn't already exist.
+   *
+   * @param SavedFrame frame
+   *        A frame to create a form for.
+   */
+  _createFrameForms: function(frame) {
+    if (this._framesToForms.has(frame)) {
+      return;
+    }
+
+    let form = null;
+    if (frame) {
+      form = {
+        line: frame.line,
+        column: frame.column,
+        source: frame.source,
+        functionDisplayName: frame.functionDisplayName,
+        parent: this._framesToIndices.get(frame.parent)
+      };
+      this._createFrameForms(frame.parent);
+    }
+
+    this._framesToForms.set(frame, form);
+  },
+
+  /**
+   * Increment the allocation count for the provided frame.
+   *
+   * @param SavedFrame frame
+   *        The frame whose allocation count should be incremented.
+   */
+  _countFrame: function(frame) {
+    if (!this._framesToCounts.has(frame)) {
+      this._framesToCounts.set(frame, 1);
+    } else {
+      let count = this._framesToCounts.get(frame);
+      this._framesToCounts.set(frame, count + 1);
+    }
+  }
+});
+
+exports.StackFrameCache = StackFrameCache;
--- a/toolkit/devtools/server/moz.build
+++ b/toolkit/devtools/server/moz.build
@@ -68,12 +68,13 @@ EXTRA_JS_MODULES.devtools.server.actors 
     'actors/webconsole.js',
     'actors/webgl.js',
 ]
 
 EXTRA_JS_MODULES.devtools.server.actors.utils += [
     'actors/utils/automation-timeline.js',
     'actors/utils/make-debugger.js',
     'actors/utils/map-uri-to-addon-id.js',
-    'actors/utils/ScriptStore.js'
+    'actors/utils/ScriptStore.js',
+    'actors/utils/stack.js',
 ]
 
 FAIL_ON_WARNINGS = True