Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 17 Feb 2014 13:36:51 +0100
changeset 169534 ae17e187ef30af614a8736b10e4c68136d4beb1f
parent 169533 405e36bb103f1211aaa32292517da76c241074d3 (current diff)
parent 169491 339f0d450d46594be03f9214a42fa0fa14704af1 (diff)
child 169535 0d20d42887660ddd12a660bab079688e3226f4fc
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
milestone30.0a1
Merge mozilla-central to b2g-inbound
--- a/content/html/document/src/MediaDocument.h
+++ b/content/html/document/src/MediaDocument.h
@@ -80,17 +80,17 @@ private:
 
 class MediaDocumentStreamListener: public nsIStreamListener
 {
 public:
   MediaDocumentStreamListener(MediaDocument *aDocument);
   virtual ~MediaDocumentStreamListener();
   void SetStreamListener(nsIStreamListener *aListener);
 
-  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_ISUPPORTS
 
   NS_DECL_NSIREQUESTOBSERVER
 
   NS_DECL_NSISTREAMLISTENER
 
   nsRefPtr<MediaDocument>      mDocument;
   nsCOMPtr<nsIStreamListener>  mNextStream;
 };
--- a/content/media/AudioNodeStream.cpp
+++ b/content/media/AudioNodeStream.cpp
@@ -55,19 +55,17 @@ AudioNodeStream::SetStreamTimeParameter(
       aContext->DestinationStream(),
       aContext->DOMTimeToStreamTime(aStreamTime)));
 }
 
 void
 AudioNodeStream::SetStreamTimeParameterImpl(uint32_t aIndex, MediaStream* aRelativeToStream,
                                             double aStreamTime)
 {
-  TrackTicks ticks =
-      WebAudioUtils::ConvertDestinationStreamTimeToSourceStreamTime(
-          aStreamTime, this, aRelativeToStream);
+  TrackTicks ticks = TicksFromDestinationTime(aRelativeToStream, aStreamTime);
   mEngine->SetStreamTimeParameter(aIndex, ticks);
 }
 
 void
 AudioNodeStream::SetDoubleParameter(uint32_t aIndex, double aValue)
 {
   class Message : public ControlMessage {
   public:
@@ -512,9 +510,41 @@ AudioNodeStream::FinishOutput()
     AudioSegment emptySegment;
     l->NotifyQueuedTrackChanges(Graph(), AUDIO_TRACK,
                                 mSampleRate,
                                 track->GetSegment()->GetDuration(),
                                 MediaStreamListener::TRACK_EVENT_ENDED, emptySegment);
   }
 }
 
+TrackTicks
+AudioNodeStream::TicksFromDestinationTime(MediaStream* aDestination,
+                                          double aSeconds)
+{
+  MOZ_ASSERT(aDestination->AsAudioNodeStream() &&
+             aDestination->AsAudioNodeStream()->SampleRate() == SampleRate());
+
+  double destinationSeconds = std::max(0.0, aSeconds);
+  StreamTime streamTime = SecondsToMediaTime(destinationSeconds);
+  // MediaTime does not have the resolution of double
+  double offset = destinationSeconds - MediaTimeToSeconds(streamTime);
+
+  GraphTime graphTime = aDestination->StreamTimeToGraphTime(streamTime);
+  StreamTime thisStreamTime = GraphTimeToStreamTimeOptimistic(graphTime);
+  double thisSeconds = MediaTimeToSeconds(thisStreamTime) + offset;
+  MOZ_ASSERT(thisSeconds >= 0.0);
+  // Round to nearest
+  TrackTicks ticks = thisSeconds * SampleRate() + 0.5;
+  return ticks;
 }
+
+double
+AudioNodeStream::DestinationTimeFromTicks(AudioNodeStream* aDestination,
+                                          TrackTicks aPosition)
+{
+  MOZ_ASSERT(SampleRate() == aDestination->SampleRate());
+  StreamTime sourceTime = TicksToTimeRoundDown(SampleRate(), aPosition);
+  GraphTime graphTime = StreamTimeToGraphTime(sourceTime);
+  StreamTime destinationTime = aDestination->GraphTimeToStreamTimeOptimistic(graphTime);
+  return MediaTimeToSeconds(destinationTime);
+}
+
+}
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -123,16 +123,29 @@ public:
   {
     return true;
   }
 
   // Any thread
   AudioNodeEngine* Engine() { return mEngine; }
   TrackRate SampleRate() const { return mSampleRate; }
 
+  /**
+   * Convert a time in seconds on the destination stream to TrackTicks
+   * on this stream.
+   */
+  TrackTicks TicksFromDestinationTime(MediaStream* aDestination,
+                                      double aSeconds);
+  /**
+   * Get the destination stream time in seconds corresponding to a position on
+   * this stream.
+   */
+  double DestinationTimeFromTicks(AudioNodeStream* aDestination,
+                                  TrackTicks aPosition);
+
 protected:
   void AdvanceOutputSegment();
   void FinishOutput();
   void AccumulateInputChunk(uint32_t aInputIndex, const AudioChunk& aChunk,
                             AudioChunk* aBlock,
                             nsTArray<float>* aDownmixBuffer);
   void UpMixDownMixChunk(const AudioChunk* aChunk, uint32_t aOutputChannelCount,
                          nsTArray<const void*>& aOutputChannels,
--- a/content/media/webaudio/ScriptProcessorNode.cpp
+++ b/content/media/webaudio/ScriptProcessorNode.cpp
@@ -309,20 +309,19 @@ private:
 
     // we now have a full input buffer ready to be sent to the main thread.
     TrackTicks playbackTick = mSource->GetCurrentPosition();
     // Add the duration of the current sample
     playbackTick += WEBAUDIO_BLOCK_SIZE;
     // Add the delay caused by the main thread
     playbackTick += mSharedBuffers->DelaySoFar();
     // Compute the playback time in the coordinate system of the destination
+    // FIXME: bug 970773
     double playbackTime =
-      WebAudioUtils::StreamPositionToDestinationTime(playbackTick,
-                                                     mSource,
-                                                     mDestination);
+      mSource->DestinationTimeFromTicks(mDestination, playbackTick);
 
     class Command : public nsRunnable
     {
     public:
       Command(AudioNodeStream* aStream,
               InputChannels& aInputChannels,
               double aPlaybackTime,
               bool aNullInput)
--- a/content/media/webaudio/WebAudioUtils.cpp
+++ b/content/media/webaudio/WebAudioUtils.cpp
@@ -18,45 +18,21 @@ struct ConvertTimeToTickHelper
 {
   AudioNodeStream* mSourceStream;
   AudioNodeStream* mDestinationStream;
 
   static int64_t Convert(double aTime, void* aClosure)
   {
     ConvertTimeToTickHelper* This = static_cast<ConvertTimeToTickHelper*> (aClosure);
     MOZ_ASSERT(This->mSourceStream->SampleRate() == This->mDestinationStream->SampleRate());
-    return WebAudioUtils::ConvertDestinationStreamTimeToSourceStreamTime(
-        aTime, This->mSourceStream, This->mDestinationStream);
+    return This->mSourceStream->
+      TicksFromDestinationTime(This->mDestinationStream, aTime);
   }
 };
 
-TrackTicks
-WebAudioUtils::ConvertDestinationStreamTimeToSourceStreamTime(double aTime,
-                                                              AudioNodeStream* aSource,
-                                                              MediaStream* aDestination)
-{
-  StreamTime streamTime = std::max<MediaTime>(0, SecondsToMediaTime(aTime));
-  GraphTime graphTime = aDestination->StreamTimeToGraphTime(streamTime);
-  StreamTime thisStreamTime = aSource->GraphTimeToStreamTimeOptimistic(graphTime);
-  TrackTicks ticks = TimeToTicksRoundUp(aSource->SampleRate(), thisStreamTime);
-  return ticks;
-}
-
-double
-WebAudioUtils::StreamPositionToDestinationTime(TrackTicks aSourcePosition,
-                                               AudioNodeStream* aSource,
-                                               AudioNodeStream* aDestination)
-{
-  MOZ_ASSERT(aSource->SampleRate() == aDestination->SampleRate());
-  StreamTime sourceTime = TicksToTimeRoundDown(aSource->SampleRate(), aSourcePosition);
-  GraphTime graphTime = aSource->StreamTimeToGraphTime(sourceTime);
-  StreamTime destinationTime = aDestination->GraphTimeToStreamTimeOptimistic(graphTime);
-  return MediaTimeToSeconds(destinationTime);
-}
-
 void
 WebAudioUtils::ConvertAudioParamToTicks(AudioParamTimeline& aParam,
                                         AudioNodeStream* aSource,
                                         AudioNodeStream* aDest)
 {
   MOZ_ASSERT(!aSource || aSource->SampleRate() == aDest->SampleRate());
   ConvertTimeToTickHelper ctth;
   ctth.mSourceStream = aSource;
--- a/content/media/webaudio/WebAudioUtils.h
+++ b/content/media/webaudio/WebAudioUtils.h
@@ -46,25 +46,16 @@ struct WebAudioUtils {
    * over aDuration seconds.
    */
   static double ComputeSmoothingRate(double aDuration, double aSampleRate)
   {
     return 1.0 - std::exp(-1.0 / (aDuration * aSampleRate));
   }
 
   /**
-   * Convert a time in second relative to the destination stream to
-   * TrackTicks relative to the source stream.
-   */
-  static TrackTicks
-  ConvertDestinationStreamTimeToSourceStreamTime(double aTime,
-                                                 AudioNodeStream* aSource,
-                                                 MediaStream* aDestination);
-
-  /**
    * Converts AudioParamTimeline floating point time values to tick values
    * with respect to a source and a destination AudioNodeStream.
    *
    * This needs to be called for each AudioParamTimeline that gets sent to an
    * AudioNodeEngine on the engine side where the AudioParamTimeline is
    * received.  This means that such engines need to be aware of their source
    * and destination streams as well.
    */
@@ -110,24 +101,16 @@ struct WebAudioUtils {
   }
 
   static bool IsTimeValid(double aTime)
   {
     return aTime >= 0 &&  aTime <= (MEDIA_TIME_MAX >> MEDIA_TIME_FRAC_BITS);
   }
 
   /**
-   * Convert a stream position into the time coordinate of the destination
-   * stream.
-   */
-  static double StreamPositionToDestinationTime(TrackTicks aSourcePosition,
-                                                AudioNodeStream* aSource,
-                                                AudioNodeStream* aDestination);
-
-  /**
    * Converts a floating point value to an integral type in a safe and
    * platform agnostic way.  The following program demonstrates the kinds
    * of ways things can go wrong depending on the CPU architecture you're
    * compiling for:
    *
    * #include <stdio.h>
    * volatile float r;
    * int main()
--- a/content/media/webaudio/test/mochitest.ini
+++ b/content/media/webaudio/test/mochitest.ini
@@ -58,16 +58,18 @@ support-files =
 [test_bug866737.html]
 [test_bug867089.html]
 [test_bug867104.html]
 [test_bug867174.html]
 [test_bug867203.html]
 [test_bug875221.html]
 [test_bug875402.html]
 [test_bug894150.html]
+[test_bug956489.html]
+[test_bug964376.html]
 [test_channelMergerNode.html]
 [test_channelMergerNodeWithVolume.html]
 [test_channelSplitterNode.html]
 [test_channelSplitterNodeWithVolume.html]
 [test_convolverNode.html]
 [test_convolverNodeChannelCount.html]
 [test_convolverNodeWithGain.html]
 [test_convolverNode_mono_mono.html]
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_bug956489.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test when and currentTime are in the same coordinate system</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+  var freq = 330;
+
+  var context = new AudioContext();
+
+  var buffer = context.createBuffer(1, context.sampleRate / freq, context.sampleRate);
+  for (var i = 0; i < buffer.length; ++i) {
+    buffer.getChannelData(0)[i] = Math.sin(2 * Math.PI * i / buffer.length);
+  }
+
+  var source = context.createBufferSource();
+  source.loop = true;
+  source.buffer = buffer;
+
+  setTimeout(function () {
+      var finished = false;
+
+      source.start(context.currentTime);
+      var processor = context.createScriptProcessor(256, 1, 1);
+      processor.onaudioprocess = function (e) {
+          if (finished) return;
+          var c = e.inputBuffer.getChannelData(0);
+          var result = true;
+
+          for (var i = 0; i < buffer.length; ++i) {
+              if (Math.abs(c[i] - buffer.getChannelData(0)[i]) > 1e-9) {
+                  result = false;
+                  break;
+              }
+          }
+          finished = true;
+          ok(result, "when and currentTime are in same time coordinate system");
+          SimpleTest.finish();
+      }
+      processor.connect(context.destination);
+      source.connect(processor);
+  }, 500);
+});
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_bug964376.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test repeating audio is not distorted</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="webaudio.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function gcd(a, b) {
+  if (b === 0) {
+    return a;
+  }
+  return gcd(b, a % b);
+}
+
+var SAMPLE_PLACEMENT = 128;
+
+var gTest = {
+  length: 2048,
+  numberOfChannels: 1,
+
+  createGraph: function(context) {
+    var freq = Math.round(context.sampleRate / SAMPLE_PLACEMENT);
+    var dur = context.sampleRate / gcd(freq, context.sampleRate);
+    var buffer = context.createBuffer(1, dur, context.sampleRate);
+
+    for (var i = 0; i < context.sampleRate; ++i) {
+      buffer.getChannelData(0)[i] = Math.sin(freq * 2 * Math.PI * i / context.sampleRate);
+    }
+
+    var source = context.createBufferSource();
+    source.buffer = buffer;
+    source.loop = true;
+    source.playbackRate.setValueAtTime(0.5, SAMPLE_PLACEMENT / context.sampleRate);
+    source.start(0);
+
+    return source;
+  },
+
+  createExpectedBuffers: function(context) {
+    var freq = Math.round(context.sampleRate / SAMPLE_PLACEMENT);
+    var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+    var c = expectedBuffer.getChannelData(0);
+    for (var i = 0; i < c.length; ++i) {
+      if (i < SAMPLE_PLACEMENT) {
+        c[i] = Math.sin(freq * 2 * Math.PI * i / context.sampleRate);
+      } else {
+        c[i] = Math.sin(freq / 2 * 2 * Math.PI * (i + SAMPLE_PLACEMENT) / context.sampleRate);
+      }
+    }
+
+    return expectedBuffer;
+  },
+};
+
+runTest();
+</script>
+</pre>
+</body>
+</html>
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -52,21 +52,21 @@ public:
     mPromise->RunTask();
     return NS_OK;
   }
 
 private:
   nsRefPtr<Promise> mPromise;
 };
 
-class WorkerPromiseTask MOZ_FINAL : public WorkerRunnable
+class WorkerPromiseTask MOZ_FINAL : public WorkerSameThreadRunnable
 {
 public:
   WorkerPromiseTask(WorkerPrivate* aWorkerPrivate, Promise* aPromise)
-    : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+    : WorkerSameThreadRunnable(aWorkerPrivate)
     , mPromise(aPromise)
   {
     MOZ_ASSERT(aPromise);
     MOZ_COUNT_CTOR(WorkerPromiseTask);
   }
 
   ~WorkerPromiseTask()
   {
@@ -156,25 +156,25 @@ public:
 
   NS_IMETHOD Run()
   {
     RunInternal();
     return NS_OK;
   }
 };
 
-class WorkerPromiseResolverTask MOZ_FINAL : public WorkerRunnable,
+class WorkerPromiseResolverTask MOZ_FINAL : public WorkerSameThreadRunnable,
                                             public PromiseResolverMixin
 {
 public:
   WorkerPromiseResolverTask(WorkerPrivate* aWorkerPrivate,
                             Promise* aPromise,
                             JS::Handle<JS::Value> aValue,
                             Promise::PromiseState aState)
-    : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),
+    : WorkerSameThreadRunnable(aWorkerPrivate),
       PromiseResolverMixin(aPromise, aValue, aState)
   {}
 
   ~WorkerPromiseResolverTask()
   {}
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
@@ -827,17 +827,17 @@ Promise::AppendCallbacks(PromiseCallback
   if (mState != Pending && !mTaskPending) {
     if (MOZ_LIKELY(NS_IsMainThread())) {
       nsRefPtr<PromiseTask> task = new PromiseTask(this);
       NS_DispatchToCurrentThread(task);
     } else {
       WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
       MOZ_ASSERT(worker);
       nsRefPtr<WorkerPromiseTask> task = new WorkerPromiseTask(worker, this);
-      worker->Dispatch(task);
+      task->Dispatch(worker->GetJSContext());
     }
     mTaskPending = true;
   }
 }
 
 void
 Promise::RunTask()
 {
@@ -1040,17 +1040,17 @@ Promise::RunResolveTask(JS::Handle<JS::V
       nsRefPtr<PromiseResolverTask> task =
         new PromiseResolverTask(this, aValue, aState);
       NS_DispatchToCurrentThread(task);
     } else {
       WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
       MOZ_ASSERT(worker);
       nsRefPtr<WorkerPromiseResolverTask> task =
         new WorkerPromiseResolverTask(worker, this, aValue, aState);
-      worker->Dispatch(task);
+      task->Dispatch(worker->GetJSContext());
     }
     return;
   }
 
   // Promise.all() or Promise.race() implementations will repeatedly call
   // Resolve/RejectInternal rather than using the Maybe... forms. Stop SetState
   // from asserting.
   if (mState != Pending) {
--- a/dom/workers/WorkerRunnable.cpp
+++ b/dom/workers/WorkerRunnable.cpp
@@ -2,20 +2,22 @@
 /* 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 "WorkerRunnable.h"
 
 #include "nsIEventTarget.h"
 #include "nsIRunnable.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/DebugOnly.h"
 
 #include "js/RootingAPI.h"
 #include "js/Value.h"
-#include "nsThreadUtils.h"
 
 #include "WorkerPrivate.h"
 
 USING_WORKERS_NAMESPACE
 
 namespace {
 
 const nsIID kWorkerRunnableIID = {
@@ -473,8 +475,48 @@ MainThreadWorkerControlRunnable::PostDis
   AssertIsOnMainThread();
 
   if (aCx && !aDispatchResult) {
     JS_ReportPendingException(aCx);
   }
 }
 
 NS_IMPL_ISUPPORTS_INHERITED0(WorkerControlRunnable, WorkerRunnable)
+
+bool
+WorkerSameThreadRunnable::PreDispatch(JSContext* aCx,
+                                      WorkerPrivate* aWorkerPrivate)
+{
+  aWorkerPrivate->AssertIsOnWorkerThread();
+  return true;
+}
+
+void
+WorkerSameThreadRunnable::PostDispatch(JSContext* aCx,
+                                       WorkerPrivate* aWorkerPrivate,
+                                       bool aDispatchResult)
+{
+  aWorkerPrivate->AssertIsOnWorkerThread();
+  if (aDispatchResult) {
+    DebugOnly<bool> willIncrement = aWorkerPrivate->ModifyBusyCountFromWorker(aCx, true);
+    // Should never fail since if this thread is still running, so should the
+    // parent and it should be able to process a control runnable.
+    MOZ_ASSERT(willIncrement);
+  }
+}
+
+void
+WorkerSameThreadRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+                                  bool aRunResult)
+{
+  MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aWorkerPrivate);
+
+  aWorkerPrivate->AssertIsOnWorkerThread();
+
+  DebugOnly<bool> willDecrement = aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false);
+  MOZ_ASSERT(willDecrement);
+
+  if (!aRunResult) {
+    JS_ReportPendingException(aCx);
+  }
+}
+
--- a/dom/workers/WorkerRunnable.h
+++ b/dom/workers/WorkerRunnable.h
@@ -309,11 +309,39 @@ protected:
     return true;
   }
 
   virtual void
   PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
                bool aDispatchResult) MOZ_OVERRIDE;
 };
 
+// A WorkerRunnable that should be dispatched from the worker to itself for
+// async tasks. This will increment the busy count PostDispatch() (only if
+// dispatch was successful) and decrement it in PostRun().
+//
+// Async tasks will almost always want to use this since
+// a WorkerSameThreadRunnable keeps the Worker from being GCed.
+class WorkerSameThreadRunnable : public WorkerRunnable
+{
+protected:
+  WorkerSameThreadRunnable(WorkerPrivate* aWorkerPrivate)
+  : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
+  { }
+
+  virtual ~WorkerSameThreadRunnable()
+  { }
+
+  virtual bool
+  PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE;
+
+  virtual void
+  PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+               bool aDispatchResult) MOZ_OVERRIDE;
+
+  virtual void
+  PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+          bool aRunResult) MOZ_OVERRIDE;
+};
+
 END_WORKERS_NAMESPACE
 
 #endif // mozilla_dom_workers_workerrunnable_h__
--- a/editor/libeditor/base/nsEditorEventListener.cpp
+++ b/editor/libeditor/base/nsEditorEventListener.cpp
@@ -1033,16 +1033,22 @@ nsEditorEventListener::ShouldHandleNativ
   }
 
   nsCOMPtr<nsIHTMLEditor> htmlEditor =
     do_QueryInterface(static_cast<nsIEditor*>(mEditor));
   if (!htmlEditor) {
     return false;
   }
 
+  nsCOMPtr<nsIDocument> doc = mEditor->GetDocument();
+  if (doc->HasFlag(NODE_IS_EDITABLE)) {
+    // Don't need to perform any checks in designMode documents.
+    return true;
+  }
+
   nsIContent* editingHost = htmlEditor->GetActiveEditingHost();
   if (!editingHost) {
     return false;
   }
 
   return nsContentUtils::ContentIsDescendantOf(targetContent, editingHost);
 }
 
--- a/editor/libeditor/html/tests/mochitest.ini
+++ b/editor/libeditor/html/tests/mochitest.ini
@@ -140,16 +140,18 @@ skip-if = toolkit == 'android'
 [test_bug757371.html]
 [test_bug767684.html]
 [test_bug780035.html]
 [test_bug787432.html]
 [test_bug790475.html]
 [test_bug796839.html]
 [test_bug832025.html]
 [test_bug857487.html]
+[test_bug966155.html]
+skip-if = os != "win"
 [test_bug966552.html]
 skip-if = os != "win"
 [test_contenteditable_focus.html]
 [test_dom_input_event_on_htmleditor.html]
 [test_keypress_untrusted_event.html]
 [test_root_element_replacement.html]
 [test_select_all_without_body.html]
 [test_spellcheck_pref.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/html/tests/test_bug966155.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=966155
+-->
+<head>
+  <title>Test for Bug 966155</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.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=966155">Mozilla Bug 966155</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+  var win = window.open("data:text/html,<input><iframe onload=\"contentDocument.designMode = 'on';\">", "", "test-966155");
+  win.addEventListener("load", function onLoad() {
+    win.removeEventListener("load", onLoad);
+    runTest(win);
+  }, false);
+});
+
+function runTest(win) {
+  SimpleTest.waitForFocus(function() {
+    var doc = win.document;
+    var iframe = doc.querySelector("iframe");
+    var iframeDoc = iframe.contentDocument;
+    var input = doc.querySelector("input");
+    iframe.focus();
+    iframeDoc.body.focus();
+    // Type some text
+    "test".split("").forEach(function(letter) {
+      synthesizeKey(letter, {}, win);
+    });
+    is(iframeDoc.body.textContent, "test", "entered the text");
+    // focus the input box
+    input.focus();
+    // press tab
+    synthesizeKey("VK_TAB", {}, win);
+    // Now press Ctrl+Backspace
+    synthesizeKey("VK_BACK_SPACE", {ctrlKey: true}, win);
+    is(iframeDoc.body.textContent, "", "deleted the text");
+    win.close();
+    SimpleTest.finish();
+  }, win);
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/editor/libeditor/html/tests/test_bug966552.html
+++ b/editor/libeditor/html/tests/test_bug966552.html
@@ -5,17 +5,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 -->
 <head>
   <title>Test for Bug 966552</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.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=966552">Mozilla Bug 289384</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=966552">Mozilla Bug 966552</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
 addLoadEvent(function() {
--- a/gfx/layers/GrallocImages.cpp
+++ b/gfx/layers/GrallocImages.cpp
@@ -375,17 +375,17 @@ GrallocImage::GetAsSourceSurface()
     return nullptr;
   }
 
   return surface;
 }
 
 
 TextureClient*
-GrallocImage::GetTextureClient()
+GrallocImage::GetTextureClient(CompositableClient* aClient)
 {
   if (!mTextureClient) {
     const SurfaceDescriptor& sd = GetSurfaceDescriptor();
     if (sd.type() != SurfaceDescriptor::TSurfaceDescriptorGralloc) {
       return nullptr;
     }
     const SurfaceDescriptorGralloc& desc = sd.get_SurfaceDescriptorGralloc();
     TextureFlags flags = desc.external() ? TEXTURE_DEALLOCATE_CLIENT : 0;
--- a/gfx/layers/GrallocImages.h
+++ b/gfx/layers/GrallocImages.h
@@ -144,17 +144,17 @@ public:
     if (mGraphicBufferLocked.get()) {
       return mGraphicBufferLocked->GetSurfaceDescriptor();
     }
     return SurfaceDescriptor();
   }
 
   virtual ISharedImage* AsSharedImage() MOZ_OVERRIDE { return this; }
 
-  virtual TextureClient* GetTextureClient() MOZ_OVERRIDE;
+  virtual TextureClient* GetTextureClient(CompositableClient* aClient) MOZ_OVERRIDE;
 
   virtual uint8_t* GetBuffer()
   {
     return static_cast<uint8_t*>(GetNativeBuffer());
   }
 
 private:
   bool mBufferAllocated;
--- a/gfx/layers/ImageContainer.cpp
+++ b/gfx/layers/ImageContainer.cpp
@@ -17,41 +17,39 @@
 #include "mozilla/layers/ImageBridgeChild.h"  // for ImageBridgeChild
 #include "mozilla/layers/ImageClient.h"  // for ImageClient
 #include "nsISupportsUtils.h"           // for NS_IF_ADDREF
 #include "YCbCrUtils.h"                 // for YCbCr conversions
 #ifdef MOZ_WIDGET_GONK
 #include "GrallocImages.h"
 #endif
 #include "gfx2DGlue.h"
+#include "mozilla/gfx/2D.h"
 
 #ifdef XP_MACOSX
 #include "mozilla/gfx/QuartzSupport.h"
 #include "MacIOSurfaceImage.h"
 #endif
 
 #ifdef XP_WIN
 #include "gfxD2DSurface.h"
 #include "gfxWindowsPlatform.h"
 #include <d3d10_1.h>
 #include "d3d10/ImageLayerD3D10.h"
 #include "D3D9SurfaceImage.h"
 #endif
 
 using namespace mozilla::ipc;
 using namespace android;
-using mozilla::gfx::DataSourceSurface;
-using mozilla::gfx::SourceSurface;
+using namespace mozilla::gfx;
 
 
 namespace mozilla {
 namespace layers {
 
-class DataSourceSurface;
-class SourceSurface;
 
 Atomic<int32_t> Image::sSerialCounter(0);
 
 TemporaryRef<gfx::SourceSurface>
 Image::GetAsSourceSurface()
 {
   nsRefPtr<gfxASurface> surface = DeprecatedGetAsSurface();
   return gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(nullptr, surface);
@@ -706,10 +704,50 @@ RemoteBitmapImage::GetAsSourceSurface()
     memcpy(newSurf->GetData() + newSurf->Stride() * y,
            mData + mStride * y,
            mSize.width * 4);
   }
 
   return newSurf;
 }
 
+CairoImage::CairoImage()
+  : Image(nullptr, ImageFormat::CAIRO_SURFACE)
+{}
+
+CairoImage::~CairoImage()
+{
+}
+
+TextureClient*
+CairoImage::GetTextureClient(CompositableClient *aClient)
+{
+  CompositableForwarder* forwarder = aClient->GetForwarder();
+  RefPtr<TextureClient> textureClient = mTextureClients.Get(forwarder->GetSerial());
+  if (textureClient) {
+    return textureClient;
+  }
+
+  RefPtr<SourceSurface> surface = GetAsSourceSurface();
+  MOZ_ASSERT(surface);
+
+  textureClient = aClient->CreateTextureClientForDrawing(surface->GetFormat(),
+                                                         TEXTURE_FLAGS_DEFAULT);
+  MOZ_ASSERT(textureClient->AsTextureClientDrawTarget());
+  if (!textureClient->AsTextureClientDrawTarget()->AllocateForSurface(surface->GetSize()) ||
+      !textureClient->Lock(OPEN_WRITE_ONLY)) {
+    return nullptr;
+  }
+
+  {
+    // We must not keep a reference to the DrawTarget after it has been unlocked.
+    RefPtr<DrawTarget> dt = textureClient->AsTextureClientDrawTarget()->GetAsDrawTarget();
+    dt->CopySurface(surface, IntRect(IntPoint(), surface->GetSize()), IntPoint());
+  }
+
+  textureClient->Unlock();
+
+  mTextureClients.Put(forwarder->GetSerial(), textureClient);
+  return textureClient;
+}
+
 } // namespace
 } // namespace
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -23,16 +23,17 @@
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsISupportsImpl.h"            // for Image::Release, etc
 #include "nsRect.h"                     // for nsIntRect
 #include "nsSize.h"                     // for nsIntSize
 #include "nsTArray.h"                   // for nsTArray
 #include "mozilla/Atomics.h"
 #include "nsThreadUtils.h"
 #include "mozilla/gfx/2D.h"
+#include "nsDataHashtable.h"
 
 #ifndef XPCOM_GLUE_AVOID_NSPR
 /**
  * We need to be able to hold a reference to a gfxASurface from Image
  * subclasses. This is potentially a problem since Images can be addrefed
  * or released off the main thread. We can ensure that we never AddRef
  * a gfxASurface off the main thread, but we might want to Release due
  * to an Image being destroyed off the main thread.
@@ -141,16 +142,18 @@ namespace mozilla {
 class CrossProcessMutex;
 
 namespace layers {
 
 class ImageClient;
 class SharedPlanarYCbCrImage;
 class DeprecatedSharedPlanarYCbCrImage;
 class TextureClient;
+class CompositableClient;
+class CompositableForwarder;
 class SurfaceDescriptor;
 
 struct ImageBackendData
 {
   virtual ~ImageBackendData() {}
 
 protected:
   ImageBackendData() {}
@@ -160,17 +163,17 @@ protected:
 class ISharedImage {
 public:
     virtual uint8_t* GetBuffer() = 0;
 
     /**
      * For use with the CompositableClient only (so that the later can
      * synchronize the TextureClient with the TextureHost).
      */
-    virtual TextureClient* GetTextureClient() = 0;
+    virtual TextureClient* GetTextureClient(CompositableClient* aClient) = 0;
 };
 
 /**
  * A class representing a buffer of pixel data. The data can be in one
  * of various formats including YCbCr.
  * 
  * Create an image using an ImageContainer. Fill the image with data, and
  * then call ImageContainer::SetImage to display it. An image must not be
@@ -900,17 +903,18 @@ protected:
   nsRefPtr<BufferRecycleBin> mRecycleBin;
 };
 
 /**
  * Currently, the data in a CairoImage surface is treated as being in the
  * device output color space. This class is very simple as all backends
  * have to know about how to deal with drawing a cairo image.
  */
-class CairoImage : public Image {
+class CairoImage : public Image,
+                   public ISharedImage {
 public:
   struct Data {
     gfxASurface* mDeprecatedSurface;
     gfx::IntSize mSize;
 
     // mSourceSurface wraps mDeprrecatedSurface's data, therefore it should not
     // outlive mDeprecatedSurface
     RefPtr<gfx::SourceSurface> mSourceSurface;
@@ -934,26 +938,32 @@ public:
   }
 
   virtual already_AddRefed<gfxASurface> DeprecatedGetAsSurface()
   {
     nsRefPtr<gfxASurface> surface = mDeprecatedSurface.get();
     return surface.forget();
   }
 
+  virtual ISharedImage* AsSharedImage() { return this; }
+  virtual uint8_t* GetBuffer() { return nullptr; }
+  virtual TextureClient* GetTextureClient(CompositableClient* aClient);
+
   gfx::IntSize GetSize() { return mSize; }
 
-  CairoImage() : Image(nullptr, ImageFormat::CAIRO_SURFACE) {}
+  CairoImage();
+  ~CairoImage();
 
   nsCountedRef<nsMainThreadSurfaceRef> mDeprecatedSurface;
   gfx::IntSize mSize;
 
   // mSourceSurface wraps mDeprrecatedSurface's data, therefore it should not
   // outlive mDeprecatedSurface
   nsCountedRef<nsMainThreadSourceSurfaceRef> mSourceSurface;
+  nsDataHashtable<nsUint32HashKey, RefPtr<TextureClient> >  mTextureClients;
 };
 
 class RemoteBitmapImage : public Image {
 public:
   RemoteBitmapImage() : Image(nullptr, ImageFormat::REMOTE_IMAGE_BITMAP) {}
 
   already_AddRefed<gfxASurface> DeprecatedGetAsSurface();
   TemporaryRef<gfx::SourceSurface> GetAsSourceSurface();
--- a/gfx/layers/MacIOSurfaceImage.cpp
+++ b/gfx/layers/MacIOSurfaceImage.cpp
@@ -5,17 +5,17 @@
 
 #include "MacIOSurfaceImage.h"
 #include "mozilla/layers/MacIOSurfaceTextureClientOGL.h"
 
 using namespace mozilla;
 using namespace mozilla::layers;
 
 TextureClient*
-MacIOSurfaceImage::GetTextureClient()
+MacIOSurfaceImage::GetTextureClient(CompositableClient* aClient)
 {
   if (!mTextureClient) {
     RefPtr<MacIOSurfaceTextureClientOGL> buffer =
       new MacIOSurfaceTextureClientOGL(TEXTURE_FLAGS_DEFAULT);
     buffer->InitWith(mSurface);
     mTextureClient = buffer;
   }
   return mTextureClient;
--- a/gfx/layers/MacIOSurfaceImage.h
+++ b/gfx/layers/MacIOSurfaceImage.h
@@ -46,17 +46,17 @@ public:
 
     mSurface->Unlock();
 
     return imgSurface.forget();
   }
 
   virtual TemporaryRef<gfx::SourceSurface> GetAsSourceSurface();
 
-  virtual TextureClient* GetTextureClient() MOZ_OVERRIDE;
+  virtual TextureClient* GetTextureClient(CompositableClient* aClient) MOZ_OVERRIDE;
   virtual uint8_t* GetBuffer() MOZ_OVERRIDE { return nullptr; }
 
   MacIOSurfaceImage() : Image(nullptr, ImageFormat::MAC_IOSURFACE) {}
 
 private:
   RefPtr<MacIOSurface> mSurface;
   RefPtr<TextureClient> mTextureClient;
 };
--- a/gfx/layers/client/ImageClient.cpp
+++ b/gfx/layers/client/ImageClient.cpp
@@ -140,19 +140,19 @@ ImageClientSingle::UpdateImageInternal(I
   if (!image) {
     return false;
   }
 
   if (mLastPaintedImageSerial == image->GetSerial()) {
     return true;
   }
 
-  if (image->AsSharedImage() && image->AsSharedImage()->GetTextureClient()) {
+  if (image->AsSharedImage() && image->AsSharedImage()->GetTextureClient(this)) {
     // fast path: no need to allocate and/or copy image data
-    RefPtr<TextureClient> texture = image->AsSharedImage()->GetTextureClient();
+    RefPtr<TextureClient> texture = image->AsSharedImage()->GetTextureClient(this);
 
 
     if (mFrontBuffer) {
       GetForwarder()->RemoveTextureFromCompositable(this, mFrontBuffer);
     }
     mFrontBuffer = texture;
     if (!AddTextureClient(texture)) {
       mFrontBuffer = nullptr;
--- a/gfx/layers/composite/CompositableHost.h
+++ b/gfx/layers/composite/CompositableHost.h
@@ -260,17 +260,16 @@ public:
   // not async.
   // Only force detach if the IPDL tree is being shutdown.
   void Detach(Layer* aLayer = nullptr, AttachFlags aFlags = NO_FLAGS)
   {
     if (!mKeepAttached ||
         aLayer == mLayer ||
         aFlags & FORCE_DETACH) {
       SetLayer(nullptr);
-      SetCompositor(nullptr);
       mAttached = false;
       mKeepAttached = false;
       if (mBackendData) {
         mBackendData->ClearData();
       }
     }
   }
   bool IsAttached() { return mAttached; }
--- a/gfx/layers/ipc/CompositableForwarder.h
+++ b/gfx/layers/ipc/CompositableForwarder.h
@@ -43,17 +43,18 @@ class PTextureChild;
  */
 class CompositableForwarder : public ISurfaceAllocator
 {
   friend class AutoOpenSurface;
   friend class DeprecatedTextureClientShmem;
 public:
 
   CompositableForwarder()
-    : mMultiProcess(false)
+    : mSerial(++sSerialCounter)
+    , mMultiProcess(false)
   {}
 
   /**
    * Setup the IPDL actor for aCompositable to be part of layers
    * transactions.
    */
   virtual void Connect(CompositableClient* aCompositable) = 0;
 
@@ -241,18 +242,22 @@ public:
     return mMultiProcess;
   }
 
   const TextureFactoryIdentifier& GetTextureFactoryIdentifier() const
   {
     return mTextureFactoryIdentifier;
   }
 
+  int32_t GetSerial() { return mSerial; }
+
 protected:
   TextureFactoryIdentifier mTextureFactoryIdentifier;
+  nsTArray<RefPtr<TextureClient> > mTexturesToRemove;
+  const int32_t mSerial;
+  static mozilla::Atomic<int32_t> sSerialCounter;
   bool mMultiProcess;
-  nsTArray<RefPtr<TextureClient> > mTexturesToRemove;
 };
 
 } // namespace
 } // namespace
 
 #endif
--- a/gfx/layers/ipc/CompositorChild.cpp
+++ b/gfx/layers/ipc/CompositorChild.cpp
@@ -23,16 +23,18 @@
 
 using mozilla::layers::LayerTransactionChild;
 
 namespace mozilla {
 namespace layers {
 
 /*static*/ CompositorChild* CompositorChild::sCompositor;
 
+Atomic<int32_t> CompositableForwarder::sSerialCounter(0);
+
 CompositorChild::CompositorChild(ClientLayerManager *aLayerManager)
   : mLayerManager(aLayerManager)
 {
   MOZ_COUNT_CTOR(CompositorChild);
 }
 
 CompositorChild::~CompositorChild()
 {
--- a/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp
+++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.cpp
@@ -58,17 +58,17 @@ DeprecatedSharedPlanarYCbCrImage::~Depre
   if (mAllocated) {
     SurfaceDescriptor desc;
     DropToSurfaceDescriptor(desc);
     mSurfaceAllocator->DestroySharedSurface(&desc);
   }
 }
 
 TextureClient*
-SharedPlanarYCbCrImage::GetTextureClient()
+SharedPlanarYCbCrImage::GetTextureClient(CompositableClient* aClient)
 {
   return mTextureClient.get();
 }
 
 uint8_t*
 SharedPlanarYCbCrImage::GetBuffer()
 {
   return mTextureClient->GetBuffer();
--- a/gfx/layers/ipc/SharedPlanarYCbCrImage.h
+++ b/gfx/layers/ipc/SharedPlanarYCbCrImage.h
@@ -92,17 +92,17 @@ private:
 class SharedPlanarYCbCrImage : public PlanarYCbCrImage
                              , public ISharedImage
 {
 public:
   SharedPlanarYCbCrImage(ImageClient* aCompositable);
   ~SharedPlanarYCbCrImage();
 
   virtual ISharedImage* AsSharedImage() MOZ_OVERRIDE { return this; }
-  virtual TextureClient* GetTextureClient() MOZ_OVERRIDE;
+  virtual TextureClient* GetTextureClient(CompositableClient* aClient) MOZ_OVERRIDE;
   virtual uint8_t* GetBuffer() MOZ_OVERRIDE;
 
   virtual already_AddRefed<gfxASurface> DeprecatedGetAsSurface() MOZ_OVERRIDE;
   virtual TemporaryRef<gfx::SourceSurface> GetAsSourceSurface() MOZ_OVERRIDE;
   virtual void SetData(const PlanarYCbCrData& aData) MOZ_OVERRIDE;
   virtual void SetDataNoCopy(const Data &aData) MOZ_OVERRIDE;
 
   virtual bool Allocate(PlanarYCbCrData& aData);
--- a/gfx/layers/ipc/SharedRGBImage.cpp
+++ b/gfx/layers/ipc/SharedRGBImage.cpp
@@ -229,17 +229,17 @@ SharedRGBImage::GetSize()
 size_t
 SharedRGBImage::GetBufferSize()
 {
   return mTextureClient ? mTextureClient->GetBufferSize()
                         : 0;
 }
 
 TextureClient*
-SharedRGBImage::GetTextureClient()
+SharedRGBImage::GetTextureClient(CompositableClient* aClient)
 {
   return mTextureClient.get();
 }
 
 already_AddRefed<gfxASurface>
 SharedRGBImage::DeprecatedGetAsSurface()
 {
   return nullptr;
--- a/gfx/layers/ipc/SharedRGBImage.h
+++ b/gfx/layers/ipc/SharedRGBImage.h
@@ -81,17 +81,17 @@ public:
   /**
    * Returns a DeprecatedSharedRGBImage* iff the descriptor was initialized with
    * ToSurfaceDescriptor.
    */
   static DeprecatedSharedRGBImage* FromSurfaceDescriptor(const SurfaceDescriptor& aDescriptor);
 
   bool AllocateBuffer(nsIntSize aSize, gfxImageFormat aImageFormat);
 
-  TextureClient* GetTextureClient() MOZ_OVERRIDE { return nullptr; }
+  TextureClient* GetTextureClient(CompositableClient* aClient) MOZ_OVERRIDE { return nullptr; }
 
 protected:
   gfx::IntSize mSize;
   gfxImageFormat mImageFormat;
   RefPtr<ISurfaceAllocator> mSurfaceAllocator;
 
   bool mAllocated;
   ipc::Shmem *mShmem;
@@ -105,17 +105,17 @@ class SharedRGBImage : public Image
                      , public ISharedImage
 {
 public:
   SharedRGBImage(ImageClient* aCompositable);
   ~SharedRGBImage();
 
   virtual ISharedImage* AsSharedImage() MOZ_OVERRIDE { return this; }
 
-  virtual TextureClient* GetTextureClient() MOZ_OVERRIDE;
+  virtual TextureClient* GetTextureClient(CompositableClient* aClient) MOZ_OVERRIDE;
 
   virtual uint8_t* GetBuffer() MOZ_OVERRIDE;
 
   gfx::IntSize GetSize();
 
   size_t GetBufferSize();
 
   already_AddRefed<gfxASurface> DeprecatedGetAsSurface();
--- a/gfx/thebes/gfxFT2Utils.cpp
+++ b/gfx/thebes/gfxFT2Utils.cpp
@@ -76,44 +76,51 @@ gfxFT2LockedFace::GetMetrics(gfxFont::Me
 
         return;
     }
 
     const FT_Size_Metrics& ftMetrics = mFace->size->metrics;
 
     gfxFloat emHeight;
     // Scale for vertical design metric conversion: pixels per design unit.
-    gfxFloat yScale;
+    // If this remains at 0.0, we can't use metrics from OS/2 etc.
+    gfxFloat yScale = 0.0;
     if (FT_IS_SCALABLE(mFace)) {
         // Prefer FT_Size_Metrics::x_scale to x_ppem as x_ppem does not
         // have subpixel accuracy.
         //
         // FT_Size_Metrics::y_scale is in 16.16 fixed point format.  Its
         // (fractional) value is a factor that converts vertical metrics from
         // design units to units of 1/64 pixels, so that the result may be
         // interpreted as pixels in 26.6 fixed point format.
         yScale = FLOAT_FROM_26_6(FLOAT_FROM_16_16(ftMetrics.y_scale));
         emHeight = mFace->units_per_EM * yScale;
     } else { // Not scalable.
-        // FT_Size_Metrics doc says x_scale is "only relevant for scalable
-        // font formats".
-        gfxFloat emUnit = mFace->units_per_EM;
-        emHeight = ftMetrics.y_ppem;
-        yScale = emHeight / emUnit;
+        // FT_Face doc says units_per_EM and a bunch of following fields
+        // are "only relevant to scalable outlines". If it's an sfnt,
+        // we can get units_per_EM from the 'head' table instead; otherwise,
+        // we don't have a unitsPerEm value so we can't compute/use yScale.
+        const TT_Header* head =
+            static_cast<TT_Header*>(FT_Get_Sfnt_Table(mFace, ft_sfnt_head));
+        if (head) {
+            gfxFloat emUnit = head->Units_Per_EM;
+            emHeight = ftMetrics.y_ppem;
+            yScale = emHeight / emUnit;
+        }
     }
 
     TT_OS2 *os2 =
         static_cast<TT_OS2*>(FT_Get_Sfnt_Table(mFace, ft_sfnt_os2));
 
     aMetrics->maxAscent = FLOAT_FROM_26_6(ftMetrics.ascender);
     aMetrics->maxDescent = -FLOAT_FROM_26_6(ftMetrics.descender);
     aMetrics->maxAdvance = FLOAT_FROM_26_6(ftMetrics.max_advance);
 
     gfxFloat lineHeight;
-    if (os2 && os2->sTypoAscender) {
+    if (os2 && os2->sTypoAscender && yScale > 0.0) {
         aMetrics->emAscent = os2->sTypoAscender * yScale;
         aMetrics->emDescent = -os2->sTypoDescender * yScale;
         FT_Short typoHeight =
             os2->sTypoAscender - os2->sTypoDescender + os2->sTypoLineGap;
         lineHeight = typoHeight * yScale;
 
         // maxAscent/maxDescent get used for frame heights, and some fonts
         // don't have the HHEA table ascent/descent set (bug 279032).
@@ -145,17 +152,17 @@ gfxFT2LockedFace::GetMetrics(gfxFont::Me
     // Prefering a measured x over sxHeight because sxHeight doesn't consider
     // hinting, but maybe the x extents are not quite right in some fancy
     // script fonts.  CSS 2.1 suggests possibly using the height of an "o",
     // which would have a more consistent glyph across fonts.
     if (GetCharExtents('x', &extents) && extents.y_bearing < 0.0) {
         aMetrics->xHeight = -extents.y_bearing;
         aMetrics->aveCharWidth = extents.x_advance;
     } else {
-        if (os2 && os2->sxHeight) {
+        if (os2 && os2->sxHeight && yScale > 0.0) {
             aMetrics->xHeight = os2->sxHeight * yScale;
         } else {
             // CSS 2.1, section 4.3.2 Lengths: "In the cases where it is
             // impossible or impractical to determine the x-height, a value of
             // 0.5em should be used."
             aMetrics->xHeight = 0.5 * emHeight;
         }
         aMetrics->aveCharWidth = 0.0; // updated below
@@ -190,33 +197,33 @@ gfxFT2LockedFace::GetMetrics(gfxFont::Me
     // of the PostScript metric, but in the PostScript table of OpenType
     // fonts the metric is "the top of the underline"
     // (http://www.microsoft.com/typography/otspec/post.htm), and FreeType
     // (up to version 2.3.7) doesn't make any adjustment.
     //
     // Therefore get the underline position directly from the table
     // ourselves when this table exists.  Use FreeType's metrics for
     // other (including older PostScript) fonts.
-    if (mFace->underline_position && mFace->underline_thickness) {
+    if (mFace->underline_position && mFace->underline_thickness && yScale > 0.0) {
         aMetrics->underlineSize = mFace->underline_thickness * yScale;
         TT_Postscript *post = static_cast<TT_Postscript*>
             (FT_Get_Sfnt_Table(mFace, ft_sfnt_post));
         if (post && post->underlinePosition) {
             aMetrics->underlineOffset = post->underlinePosition * yScale;
         } else {
             aMetrics->underlineOffset = mFace->underline_position * yScale
                 + 0.5 * aMetrics->underlineSize;
         }
     } else { // No underline info.
         // Imitate Pango.
         aMetrics->underlineSize = emHeight / 14.0;
         aMetrics->underlineOffset = -aMetrics->underlineSize;
     }
 
-    if (os2 && os2->yStrikeoutSize && os2->yStrikeoutPosition) {
+    if (os2 && os2->yStrikeoutSize && os2->yStrikeoutPosition && yScale > 0.0) {
         aMetrics->strikeoutSize = os2->yStrikeoutSize * yScale;
         aMetrics->strikeoutOffset = os2->yStrikeoutPosition * yScale;
     } else { // No strikeout info.
         aMetrics->strikeoutSize = aMetrics->underlineSize;
         // Use OpenType spec's suggested position for Roman font.
         aMetrics->strikeoutOffset = emHeight * 409.0 / 2048.0
             + 0.5 * aMetrics->strikeoutSize;
     }
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -570,20 +570,18 @@ nsCSSRendering::PaintBorderWithStyleBord
 
 static nsRect
 GetOutlineInnerRect(nsIFrame* aFrame)
 {
   nsRect* savedOutlineInnerRect = static_cast<nsRect*>
     (aFrame->Properties().Get(nsIFrame::OutlineInnerRectProperty()));
   if (savedOutlineInnerRect)
     return *savedOutlineInnerRect;
-  // FIXME (bug 599652): We probably want something narrower than either
-  // overflow rect here, but for now use the visual overflow in order to
-  // be consistent with ComputeEffectsRect in nsFrame.cpp.
-  return aFrame->GetVisualOverflowRect();
+  NS_NOTREACHED("we should have saved a frame property");
+  return nsRect(nsPoint(0, 0), aFrame->GetSize());
 }
 
 void
 nsCSSRendering::PaintOutline(nsPresContext* aPresContext,
                              nsRenderingContext& aRenderingContext,
                              nsIFrame* aForFrame,
                              const nsRect& aDirtyRect,
                              const nsRect& aBorderArea,
@@ -603,47 +601,32 @@ nsCSSRendering::PaintOutline(nsPresConte
   }
 
   nsIFrame* bgFrame = nsCSSRendering::FindNonTransparentBackgroundFrame
     (aForFrame, false);
   nsStyleContext* bgContext = bgFrame->StyleContext();
   nscolor bgColor =
     bgContext->GetVisitedDependentColor(eCSSProperty_background_color);
 
-  // When the outline property is set on :-moz-anonymous-block or
-  // :-moz-anonyomus-positioned-block pseudo-elements, it inherited that
-  // outline from the inline that was broken because it contained a
-  // block.  In that case, we don't want a really wide outline if the
-  // block inside the inline is narrow, so union the actual contents of
-  // the anonymous blocks.
-  nsIFrame *frameForArea = aForFrame;
-  do {
-    nsIAtom *pseudoType = frameForArea->StyleContext()->GetPseudo();
-    if (pseudoType != nsCSSAnonBoxes::mozAnonymousBlock &&
-        pseudoType != nsCSSAnonBoxes::mozAnonymousPositionedBlock)
-      break;
-    // If we're done, we really want it and all its later siblings.
-    frameForArea = frameForArea->GetFirstPrincipalChild();
-    NS_ASSERTION(frameForArea, "anonymous block with no children?");
-  } while (frameForArea);
-  nsRect innerRect; // relative to aBorderArea.TopLeft()
-  if (frameForArea == aForFrame) {
+  nsRect innerRect;
+  if (
+#ifdef MOZ_XUL
+      aStyleContext->GetPseudoType() == nsCSSPseudoElements::ePseudo_XULTree
+#else
+      false
+#endif
+     ) {
+    // FIXME: This behavior doesn't make sense; we should switch back to
+    // using aBorderArea.  But since this has been broken since bug
+    // 133165 in August of 2004, that switch should be made in its own
+    // patch changing only that behavior.
+    innerRect = aForFrame->GetVisualOverflowRect();
+  } else {
     innerRect = GetOutlineInnerRect(aForFrame);
-  } else {
-    for (; frameForArea; frameForArea = frameForArea->GetNextSibling()) {
-      // The outline has already been included in aForFrame's overflow
-      // area, but not in those of its descendants, so we have to
-      // include it.  Otherwise we'll end up drawing the outline inside
-      // the border.
-      nsRect r(GetOutlineInnerRect(frameForArea) +
-               frameForArea->GetOffsetTo(aForFrame));
-      innerRect.UnionRect(innerRect, r);
-    }
   }
-
   innerRect += aBorderArea.TopLeft();
   nscoord offset = ourOutline->mOutlineOffset;
   innerRect.Inflate(offset, offset);
   // If the dirty rect is completely inside the border area (e.g., only the
   // content is being painted), then we can skip out now
   // XXX this isn't exactly true for rounded borders, where the inside curves may
   // encroach into the content area.  A safer calculation would be to
   // shorten insideRect by the radius one each side before performing this test.
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1634,36 +1634,60 @@ nsLayoutUtils::ChangeMatrixBasis(const g
   return result; 
 }
 
 /**
  * Given a gfxFloat, constrains its value to be between nscoord_MIN and nscoord_MAX.
  *
  * @param aVal The value to constrain (in/out)
  */
-static void ConstrainToCoordValues(gfxFloat &aVal)
+static void ConstrainToCoordValues(gfxFloat& aVal)
 {
   if (aVal <= nscoord_MIN)
     aVal = nscoord_MIN;
   else if (aVal >= nscoord_MAX)
     aVal = nscoord_MAX;
 }
 
+static void ConstrainToCoordValues(gfxFloat& aStart, gfxFloat& aSize)
+{
+  gfxFloat max = aStart + aSize;
+
+  // Clamp the end points to within nscoord range
+  ConstrainToCoordValues(aStart);
+  ConstrainToCoordValues(max);
+
+  aSize = max - aStart;
+  // If the width if still greater than the max nscoord, then bring both
+  // endpoints in by the same amount until it fits.
+  if (aSize > nscoord_MAX) {
+    gfxFloat excess = aSize - nscoord_MAX;
+    excess /= 2;
+
+    aStart += excess;
+    aSize = nscoord_MAX;
+  } else if (aSize < nscoord_MIN) {
+    gfxFloat excess = aSize - nscoord_MIN;
+    excess /= 2;
+
+    aStart -= excess;
+    aSize = nscoord_MIN;
+  }
+}
+
 nsRect
 nsLayoutUtils::RoundGfxRectToAppRect(const gfxRect &aRect, float aFactor)
 {
   /* Get a new gfxRect whose units are app units by scaling by the specified factor. */
   gfxRect scaledRect = aRect;
   scaledRect.ScaleRoundOut(aFactor);
 
   /* We now need to constrain our results to the max and min values for coords. */
-  ConstrainToCoordValues(scaledRect.x);
-  ConstrainToCoordValues(scaledRect.y);
-  ConstrainToCoordValues(scaledRect.width);
-  ConstrainToCoordValues(scaledRect.height);
+  ConstrainToCoordValues(scaledRect.x, scaledRect.width);
+  ConstrainToCoordValues(scaledRect.y, scaledRect.height);
 
   /* Now typecast everything back.  This is guaranteed to be safe. */
   return nsRect(nscoord(scaledRect.X()), nscoord(scaledRect.Y()),
                 nscoord(scaledRect.Width()), nscoord(scaledRect.Height()));
 }
 
 
 nsRegion
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -4971,37 +4971,16 @@ ComputeEffectsRect(nsIFrame* aFrame, con
       r = nsSVGUtils::GetPostFilterVisualOverflowRect(aFrame, aOverflowRect);
     }
     return r;
   }
 
   // box-shadow
   r.UnionRect(r, nsLayoutUtils::GetBoxShadowRectForFrame(aFrame, aNewSize));
 
-  const nsStyleOutline* outline = aFrame->StyleOutline();
-  uint8_t outlineStyle = outline->GetOutlineStyle();
-  if (outlineStyle != NS_STYLE_BORDER_STYLE_NONE) {
-    nscoord width;
-    DebugOnly<bool> result = outline->GetOutlineWidth(width);
-    NS_ASSERTION(result, "GetOutlineWidth had no cached outline width");
-    if (width > 0) {
-      aFrame->Properties().
-        Set(nsIFrame::OutlineInnerRectProperty(), new nsRect(r));
-
-      nscoord offset = outline->mOutlineOffset;
-      nscoord inflateBy = std::max(width + offset, 0);
-      // FIXME (bug 599652): We probably want outline to be drawn around
-      // something smaller than the visual overflow rect (perhaps the
-      // scrollable overflow rect is correct).  When we change that, we
-      // need to keep this code (and the storing of properties just
-      // above) in sync with GetOutlineInnerRect in nsCSSRendering.cpp.
-      r.Inflate(inflateBy, inflateBy);
-    }
-  }
-
   // border-image-outset.
   // We need to include border-image-outset because it can cause the
   // border image to be drawn beyond the border box.
 
   // (1) It's important we not check whether there's a border-image
   //     since the style hint for a change in border image doesn't cause
   //     reflow, and that's probably more important than optimizing the
   //     overflow areas for the silly case of border-image-outset without
@@ -6849,16 +6828,195 @@ nsIFrame::SetOverflowAreas(const nsOverf
 
 inline bool
 IsInlineFrame(nsIFrame *aFrame)
 {
   nsIAtom *type = aFrame->GetType();
   return type == nsGkAtoms::inlineFrame;
 }
 
+/**
+ * Compute the union of the border boxes of aFrame and its descendants,
+ * in aFrame's coordinate space (if aApplyTransform is false) or its
+ * post-transform coordinate space (if aApplyTransform is true).
+ */
+static nsRect
+UnionBorderBoxes(nsIFrame* aFrame, bool aApplyTransform,
+                 const nsSize* aSizeOverride = nullptr,
+                 const nsOverflowAreas* aOverflowOverride = nullptr)
+{
+  const nsRect bounds(nsPoint(0, 0),
+                      aSizeOverride ? *aSizeOverride : aFrame->GetSize());
+
+  // Start from our border-box, transformed.  See comment below about
+  // transform of children.
+  nsRect u;
+  bool doTransform = aApplyTransform && aFrame->IsTransformed();
+  if (doTransform) {
+    u = nsDisplayTransform::TransformRect(bounds, aFrame,
+                                          nsPoint(0, 0), &bounds);
+  } else {
+    u = bounds;
+  }
+
+  // Only iterate through the children if the overflow areas suggest
+  // that we might need to, and if the frame doesn't clip its overflow
+  // anyway.
+  if (aOverflowOverride) {
+    if (!doTransform &&
+        bounds.IsEqualEdges(aOverflowOverride->VisualOverflow()) &&
+        bounds.IsEqualEdges(aOverflowOverride->ScrollableOverflow())) {
+      return u;
+    }
+  } else {
+    if (!doTransform &&
+        bounds.IsEqualEdges(aFrame->GetVisualOverflowRect()) &&
+        bounds.IsEqualEdges(aFrame->GetScrollableOverflowRect())) {
+      return u;
+    }
+  }
+  const nsStyleDisplay* disp = aFrame->StyleDisplay();
+  nsIAtom* fType = aFrame->GetType();
+  if (nsFrame::ShouldApplyOverflowClipping(aFrame, disp) ||
+      fType == nsGkAtoms::scrollFrame ||
+      fType == nsGkAtoms::svgOuterSVGFrame) {
+    return u;
+  }
+
+  nsRect clipPropClipRect;
+  bool hasClipPropClip =
+    aFrame->GetClipPropClipRect(disp, &clipPropClipRect, bounds.Size());
+
+  // Iterate over all children except pop-ups.
+  const nsIFrame::ChildListIDs skip(nsIFrame::kPopupList |
+                                    nsIFrame::kSelectPopupList);
+  for (nsIFrame::ChildListIterator childLists(aFrame);
+       !childLists.IsDone(); childLists.Next()) {
+    if (skip.Contains(childLists.CurrentID())) {
+      continue;
+    }
+
+    nsFrameList children = childLists.CurrentList();
+    for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next()) {
+      nsIFrame* child = e.get();
+      // Note that passing |true| for aApplyTransform when
+      // child->Preserves3D() is incorrect if our aApplyTransform is
+      // false... but the opposite would be as well.  This is because
+      // elements within a preserve-3d scene are always transformed up
+      // to the top of the scene.  This means we don't have a
+      // mechanism for getting a transform up to an intermediate point
+      // within the scene.  We choose to over-transform rather than
+      // under-transform because this is consistent with other
+      // overflow areas.
+      nsRect childRect = UnionBorderBoxes(child, true) +
+                         child->GetPosition();
+
+      if (hasClipPropClip) {
+        // Intersect with the clip before transforming.
+        childRect.IntersectRect(childRect, clipPropClipRect);
+      }
+
+      // Note that we transform each child separately according to
+      // aFrame's transform, and then union, which gives a different
+      // (smaller) result from unioning and then transforming the
+      // union.  This doesn't match the way we handle overflow areas
+      // with 2-D transforms, though it does match the way we handle
+      // overflow areas in preserve-3d 3-D scenes.
+      if (doTransform && !child->Preserves3D()) {
+        childRect = nsDisplayTransform::TransformRect(childRect, aFrame,
+                                                      nsPoint(0, 0), &bounds);
+      }
+      u.UnionRectEdges(u, childRect);
+    }
+  }
+
+  return u;
+}
+
+static void
+ComputeAndIncludeOutlineArea(nsIFrame* aFrame, nsOverflowAreas& aOverflowAreas,
+                             const nsSize& aNewSize)
+{
+  const nsStyleOutline* outline = aFrame->StyleOutline();
+  if (outline->GetOutlineStyle() == NS_STYLE_BORDER_STYLE_NONE) {
+    return;
+  }
+
+  nscoord width;
+  DebugOnly<bool> result = outline->GetOutlineWidth(width);
+  NS_ASSERTION(result, "GetOutlineWidth had no cached outline width");
+  if (width <= 0) {
+    return;
+  }
+
+  // When the outline property is set on :-moz-anonymous-block or
+  // :-moz-anonymous-positioned-block pseudo-elements, it inherited
+  // that outline from the inline that was broken because it
+  // contained a block.  In that case, we don't want a really wide
+  // outline if the block inside the inline is narrow, so union the
+  // actual contents of the anonymous blocks.
+  nsIFrame *frameForArea = aFrame;
+  do {
+    nsIAtom *pseudoType = frameForArea->StyleContext()->GetPseudo();
+    if (pseudoType != nsCSSAnonBoxes::mozAnonymousBlock &&
+        pseudoType != nsCSSAnonBoxes::mozAnonymousPositionedBlock)
+      break;
+    // If we're done, we really want it and all its later siblings.
+    frameForArea = frameForArea->GetFirstPrincipalChild();
+    NS_ASSERTION(frameForArea, "anonymous block with no children?");
+  } while (frameForArea);
+
+  // Find the union of the border boxes of all descendants, or in
+  // the block-in-inline case, all descendants we care about.
+  //
+  // Note that the interesting perspective-related cases are taken
+  // care of by the code that handles those issues for overflow
+  // calling FinishAndStoreOverflow again, which in turn calls this
+  // function again.  We still need to deal with preserve-3d a bit.
+  nsRect innerRect;
+  if (frameForArea == aFrame) {
+    innerRect = UnionBorderBoxes(aFrame, false, &aNewSize, &aOverflowAreas);
+  } else {
+    for (; frameForArea; frameForArea = frameForArea->GetNextSibling()) {
+      nsRect r(UnionBorderBoxes(frameForArea, true));
+
+      // Adjust for offsets transforms up to aFrame's pre-transform
+      // (i.e., normal) coordinate space; see comments in
+      // UnionBorderBoxes for some of the subtlety here.
+      for (nsIFrame *f = frameForArea, *parent = f->GetParent();
+           /* see middle of loop */;
+           f = parent, parent = f->GetParent()) {
+        r += f->GetPosition();
+        if (parent == aFrame) {
+          break;
+        }
+        if (parent->IsTransformed() && !f->Preserves3D()) {
+          r = nsDisplayTransform::TransformRect(r, parent, nsPoint(0, 0));
+        }
+      }
+
+      innerRect.UnionRect(innerRect, r);
+    }
+  }
+
+  aFrame->Properties().Set(nsIFrame::OutlineInnerRectProperty(),
+                           new nsRect(innerRect));
+
+  nscoord offset = outline->mOutlineOffset;
+  nscoord inflateBy = std::max(width + offset, 0);
+
+  // Keep this code (and the storing of properties just above) in
+  // sync with GetOutlineInnerRect in nsCSSRendering.cpp.
+  nsRect outerRect(innerRect);
+  outerRect.Inflate(inflateBy, inflateBy);
+
+  nsRect& vo = aOverflowAreas.VisualOverflow();
+  vo.UnionRectEdges(vo, outerRect);
+}
+
 bool
 nsIFrame::FinishAndStoreOverflow(nsOverflowAreas& aOverflowAreas,
                                  nsSize aNewSize, nsSize* aOldSize)
 {
   NS_ASSERTION(FrameMaintainsOverflow(this),
                "Don't call - overflow rects not maintained on these SVG frames");
 
   nsRect bounds(nsPoint(0, 0), aNewSize);
@@ -6928,16 +7086,18 @@ nsIFrame::FinishAndStoreOverflow(nsOverf
     if (presContext->GetTheme()->
           GetWidgetOverflow(presContext->DeviceContext(), this,
                             disp->mAppearance, &r)) {
       nsRect& vo = aOverflowAreas.VisualOverflow();
       vo.UnionRectEdges(vo, r);
     }
   }
 
+  ComputeAndIncludeOutlineArea(this, aOverflowAreas, aNewSize);
+
   // Nothing in here should affect scrollable overflow.
   aOverflowAreas.VisualOverflow() =
     ComputeEffectsRect(this, aOverflowAreas.VisualOverflow(), aNewSize);
 
   // Absolute position clipping
   nsRect clipPropClipRect;
   bool hasClipPropClip = GetClipPropClipRect(disp, &clipPropClipRect, aNewSize);
   if (hasClipPropClip) {
new file mode 100644
--- /dev/null
+++ b/layout/reftests/outline/outline-and-3d-transform-1-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<title>Testcase for outline around 3-D transform</title>
+<style>
+
+html, body { margin: 0; padding: 0; border: none }
+
+div {
+  width: 100px;
+  height: 100px;
+}
+
+body > div {
+  margin-top: 200px;
+  margin-left: 200px;
+  transform-style: flat;
+  position: relative;
+}
+
+body > div > div {
+  position: absolute; top: 0; left: 0;
+  height: 150px; width: 150px; top: -25px; left: -25px;
+  background: rgba(255, 255, 0, 0.4);
+  outline: 2px dashed blue;
+}
+
+</style>
+
+<div><div></div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/outline/outline-and-3d-transform-1a.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<title>Testcase for outline around 3-D transform</title>
+<style>
+
+html, body { margin: 0; padding: 0; border: none }
+
+div {
+  width: 100px;
+  height: 100px;
+}
+
+body > div {
+  margin-top: 200px;
+  margin-left: 200px;
+  transform-style: flat;
+  outline: 2px dashed blue;
+}
+
+body > div > div {
+  transform: rotateX(30deg);
+  transform-origin: 50% 50%;
+  transform-style: preserve-3d;
+}
+
+body > div > div > div {
+  transform: rotateX(30deg);
+  transform-origin: 50% 50%;
+  transform-style: preserve-3d;
+}
+
+body > div > div > div > div {
+  transform: scale(1.5, 3);
+  transform-origin: 50% 50%;
+  background: rgba(255, 255, 0, 0.4);
+}
+
+
+</style>
+
+<div><div><div><div></div></div></div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/outline/outline-and-3d-transform-1b.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<title>Testcase for outline around 3-D transform</title>
+<style>
+
+html, body { margin: 0; padding: 0; border: none }
+
+div {
+  width: 100px;
+  height: 100px;
+}
+
+body > div {
+  margin-top: 200px;
+  margin-left: 200px;
+  transform-style: flat;
+  outline: 2px dashed blue;
+}
+
+body > div > div {
+  transform: rotateX(90deg);
+  transform-origin: 50% 50%;
+  transform-style: preserve-3d;
+}
+
+body > div > div > div {
+  transform: rotateX(-30deg);
+  transform-origin: 50% 50%;
+  transform-style: preserve-3d;
+}
+
+body > div > div > div > div {
+  transform: scale(1.5, 3);
+  transform-origin: 50% 50%;
+  background: rgba(255, 255, 0, 0.4);
+}
+
+
+</style>
+
+<div><div><div><div></div></div></div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/outline/outline-and-3d-transform-2-ref.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<title>Testcase for outline around 3-D transform</title>
+<style>
+
+html, body { margin: 0; padding: 0; border: none }
+
+div {
+  width: 100px;
+  height: 100px;
+}
+
+body > div {
+  margin-top: 200px;
+  margin-left: 200px;
+  transform-style: flat;
+}
+
+body > div > div {
+  transform: rotateX(30deg);
+  transform-origin: 50% 50%;
+  transform-style: preserve-3d;
+  border: 5px solid green;
+  margin: -5px;
+}
+
+body > div > div > div {
+  transform: rotateX(30deg);
+  width: 50px; margin-left: 20px; margin-top: -5px;
+  transform-origin: 50% 50%;
+  transform-style: preserve-3d;
+  border: 5px solid blue;
+}
+
+</style>
+
+<div><div><div></div></div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/outline/outline-and-3d-transform-2.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<title>Testcase for outline around 3-D transform</title>
+<style>
+
+html, body { margin: 0; padding: 0; border: none }
+
+div {
+  width: 100px;
+  height: 100px;
+}
+
+body > div {
+  margin-top: 200px;
+  margin-left: 200px;
+  transform-style: flat;
+}
+
+body > div > div {
+  transform: rotateX(30deg);
+  transform-origin: 50% 50%;
+  transform-style: preserve-3d;
+  outline: 5px solid green;
+}
+
+body > div > div > div {
+  transform: rotateX(30deg);
+  width: 50px; margin-left: 25px;
+  transform-origin: 50% 50%;
+  transform-style: preserve-3d;
+  outline: 5px solid blue;
+}
+
+</style>
+
+<div><div><div></div></div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/outline/outline-and-box-shadow-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<title>outline and box-shadow</title>
+<style>
+html, body { margin: 0; padding: 0 }
+p {
+  margin: 48px;
+  border: 2px solid blue;
+  padding: 5px; /* ensure no font overhang */
+  background: yellow; color: black;
+  box-shadow: 10px 10px 10px 0px black;
+}
+</style>
+<p>The outline should be adjacent to the background.</p>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/outline/outline-and-box-shadow.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<title>outline and box-shadow</title>
+<style>
+html, body { margin: 0; padding: 0 }
+p {
+  margin: 50px;
+  outline: 2px solid blue;
+  padding: 5px; /* ensure no font overhang */
+  background: yellow; color: black;
+  box-shadow: 10px 10px 10px 2px black;
+}
+</style>
+<p>The outline should be adjacent to the background.</p>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/outline/reftest.list
@@ -0,0 +1,4 @@
+== outline-and-box-shadow.html outline-and-box-shadow-ref.html
+== outline-and-3d-transform-1a.html outline-and-3d-transform-1-ref.html
+== outline-and-3d-transform-1b.html outline-and-3d-transform-1-ref.html
+== outline-and-3d-transform-2.html outline-and-3d-transform-2-ref.html
--- a/layout/reftests/reftest.list
+++ b/layout/reftests/reftest.list
@@ -214,16 +214,18 @@ skip-if(B2G) include margin-collapsing/r
 skip-if(B2G) include marquee/reftest.list
 
 # native-theme/
 skip-if(Android||B2G) include native-theme/reftest.list
 
 # netwerk/
 skip-if(B2G) include ../../netwerk/test/reftest/reftest.list
 
+include outline/reftest.list
+
 # object/
 skip-if(B2G) include object/reftest.list
 
 # ogg-video/
 include ogg-video/reftest.list
 
 # webm-video/
 include webm-video/reftest.list
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1538,17 +1538,17 @@ nsStyleGradient::IsOpaque()
 bool
 nsStyleGradient::HasCalc()
 {
   for (uint32_t i = 0; i < mStops.Length(); i++) {
     if (mStops[i].mLocation.IsCalcUnit())
       return true;
   }
   return mBgPosX.IsCalcUnit() || mBgPosY.IsCalcUnit() || mAngle.IsCalcUnit() ||
-         mRadiusX.IsCalcUnit() || mRadiusX.IsCalcUnit();
+         mRadiusX.IsCalcUnit() || mRadiusY.IsCalcUnit();
 }
 
 // --------------------
 // nsStyleImage
 //
 
 nsStyleImage::nsStyleImage()
   : mType(eStyleImageType_Null)
--- a/layout/tools/reftest/reftest-analyzer.xhtml
+++ b/layout/tools/reftest/reftest-analyzer.xhtml
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- -*- Mode: HTML; tab-width: 4; indent-tabs-mode: nil; -*- -->
-<!-- vim: set shiftwidth=4 tabstop=4 autoindent noexpandtab: -->
+<!-- -*- Mode: HTML; tab-width: 2; indent-tabs-mode: nil; -*- -->
+<!-- vim: set shiftwidth=2 tabstop=2 autoindent expandtab: -->
 <!-- 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/. -->
 <!--
 
 Features to add:
 * make the left and right parts of the viewer independently scrollable
 * make the test list filterable
@@ -174,17 +174,17 @@ function fileentry_changed() {
     // The parts of the log we care about are ASCII-only.  Since we
     // can ignore lines we don't care about, best to read in as
     // iso-8859-1, which guarantees we don't get decoding errors.
     var fileReader = new FileReader();
     fileReader.onload = function(e) {
       var log = null;
 
       log = e.target.result;
-      
+
       if (log)
         process_log(log);
       else
         show_phase("entry");
     }
     fileReader.readAsText(files[0], "iso-8859-1");
   }
   // So the user can process the same filename again (after
@@ -212,17 +212,17 @@ function process_log(contents) {
     if (!match)
       continue;
     line = match[1];
     match = line.match(/^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL|TEST-DEBUG-INFO)(\(EXPECTED RANDOM\)|) \| ([^\|]+) \|(.*)/);
     if (match) {
       var state = match[1];
       var random = match[2];
       var url = match[3];
-                        var extra = match[4];
+      var extra = match[4];
       gTestItems.push(
         {
           pass: !state.match(/DEBUG-INFO$|FAIL$/),
           // only one of the following three should ever be true
           unexpected: !!state.match(/^TEST-UNEXPECTED/),
           random: (random == "(EXPECTED RANDOM)"),
           skip: (extra == " (SKIP)"),
           url: url,
--- a/layout/tools/reftest/reftest.js
+++ b/layout/tools/reftest/reftest.js
@@ -1650,17 +1650,17 @@ function RecordResult(testRunTime, error
                     !test_passed && expected == EXPECTED_FUZZY ||
                     test_passed && expected == EXPECTED_FAIL) {
                     if (!equal) {
                         result += ", max difference: " + maxDifference.value + ", number of differing pixels: " + differences + "\n";
                         result += "REFTEST   IMAGE 1 (TEST): " + gCanvas1.toDataURL() + "\n";
                         result += "REFTEST   IMAGE 2 (REFERENCE): " + gCanvas2.toDataURL() + "\n";
                     } else {
                         result += "\n";
-                        gDumpLog("REFTEST   IMAGE: " + gCanvas1.toDataURL() + "\n");
+                        result += "REFTEST   IMAGE: " + gCanvas1.toDataURL() + "\n";
                     }
                 } else {
                     result += "\n";
                 }
 
                 gDumpLog(result);
             }
 
--- a/python/lldbutils/lldbutils/utils.py
+++ b/python/lldbutils/lldbutils/utils.py
@@ -14,17 +14,17 @@ def format_char(c):
     elif c == 0x09:
         return "\\t"
     elif c == 0x0b:
         return "\\v"
     elif c == 0x5c:
         return "\\"
     elif c == 0x22:
         return "\\\""
-    elif c == 0x39:
+    elif c == 0x27:
         return "\\'"
     elif c < 0x20 or c >= 0x80 and c <= 0xff:
         return "\\x%02x" % c
     elif c >= 0x0100:
         return "\\u%04x" % c
     else:
         return chr(c)