merge mozilla-inbound to mozilla-central on a CLOSED TREE a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 15 May 2015 17:39:23 +0200
changeset 244040 1a8343f8ed8336cbba1b236ab9725012c6c73179
parent 244039 c0e709a5baca045cadd9f3bd39441cf79ab9aee8 (current diff)
parent 243989 17042d4111bb1079e6a214bc4e245b210bb1fc29 (diff)
child 244041 5943d32f35155feb6144f5b06d7e413888d9072e
child 244105 e0eae4ec22fab6f2c9b5dfcb6524d8dc07a24611
child 244112 e0db8169a12397230057d7cd5695148f6dfdf169
push id59820
push usercbook@mozilla.com
push dateFri, 15 May 2015 15:41:47 +0000
treeherdermozilla-inbound@5943d32f3515 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone41.0a1
first release with
nightly linux32
1a8343f8ed83 / 41.0a1 / 20150515084717 / files
nightly linux64
1a8343f8ed83 / 41.0a1 / 20150515084717 / files
nightly mac
1a8343f8ed83 / 41.0a1 / 20150515084717 / files
nightly win32
1a8343f8ed83 / 41.0a1 / 20150515084717 / files
nightly win64
1a8343f8ed83 / 41.0a1 / 20150515084717 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central on a CLOSED TREE a=merge
dom/media/webspeech/synth/ipc/test/file_ipc.html
dom/media/webspeech/synth/ipc/test/mochitest.ini
dom/media/webspeech/synth/ipc/test/test_ipc.html
layout/base/nsPresShell.cpp
modules/libpref/init/all.js
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3098,23 +3098,16 @@ Element::AttrValueToCORSMode(const nsAtt
   }
 
   return CORSMode(aValue->GetEnumValue());
 }
 
 static const char*
 GetFullScreenError(nsIDocument* aDoc)
 {
-  // Block fullscreen requests in the chrome document when the fullscreen API
-  // is configured for content only.
-  if (nsContentUtils::IsFullscreenApiContentOnly() &&
-      nsContentUtils::IsChromeDoc(aDoc)) {
-    return "FullScreenDeniedContentOnly";
-  }
-
   nsCOMPtr<nsPIDOMWindow> win = aDoc->GetWindow();
   if (aDoc->NodePrincipal()->GetAppStatus() >= nsIPrincipal::APP_STATUS_INSTALLED) {
     // Request is in a web app and in the same origin as the web app.
     // Don't enforce as strict security checks for web apps, the user
     // is supposed to have trust in them. However documents cross-origin
     // to the web app must still confirm to the normal security checks.
     return nullptr;
   }
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -242,17 +242,16 @@ nsString* nsContentUtils::sControlText =
 nsString* nsContentUtils::sMetaText = nullptr;
 nsString* nsContentUtils::sOSText = nullptr;
 nsString* nsContentUtils::sAltText = nullptr;
 nsString* nsContentUtils::sModifierSeparator = nullptr;
 
 bool nsContentUtils::sInitialized = false;
 bool nsContentUtils::sIsFullScreenApiEnabled = false;
 bool nsContentUtils::sTrustedFullScreenOnly = true;
-bool nsContentUtils::sFullscreenApiIsContentOnly = false;
 bool nsContentUtils::sIsPerformanceTimingEnabled = false;
 bool nsContentUtils::sIsResourceTimingEnabled = false;
 bool nsContentUtils::sIsUserTimingLoggingEnabled = false;
 bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false;
 bool nsContentUtils::sEncodeDecodeURLHash = false;
 
 uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
 
@@ -507,22 +506,16 @@ nsContentUtils::Init()
   sBlockedScriptRunners = new nsTArray< nsCOMPtr<nsIRunnable> >;
 
   Preferences::AddBoolVarCache(&sAllowXULXBL_for_file,
                                "dom.allow_XUL_XBL_for_file");
 
   Preferences::AddBoolVarCache(&sIsFullScreenApiEnabled,
                                "full-screen-api.enabled");
 
-  // Note: We deliberately read this pref here because this code runs
-  // before the profile loads, so users' changes to this pref in about:config
-  // won't have any effect on behaviour. We don't really want users messing
-  // with this pref, as it affects the security model of the fullscreen API.
-  sFullscreenApiIsContentOnly = Preferences::GetBool("full-screen-api.content-only", false);
-
   Preferences::AddBoolVarCache(&sTrustedFullScreenOnly,
                                "full-screen-api.allow-trusted-requests-only");
 
   Preferences::AddBoolVarCache(&sIsPerformanceTimingEnabled,
                                "dom.enable_performance", true);
 
   Preferences::AddBoolVarCache(&sIsResourceTimingEnabled,
                                "dom.enable_resource_timing", true);
@@ -6632,23 +6625,16 @@ nsContentUtils::IsRequestFullScreenAllow
 {
   return !sTrustedFullScreenOnly ||
          EventStateManager::IsHandlingUserInput() ||
          IsCallerChrome();
 }
 
 /* static */
 bool
-nsContentUtils::IsFullscreenApiContentOnly()
-{
-  return sFullscreenApiIsContentOnly;
-}
-
-/* static */
-bool
 nsContentUtils::HaveEqualPrincipals(nsIDocument* aDoc1, nsIDocument* aDoc2)
 {
   if (!aDoc1 || !aDoc2) {
     return false;
   }
   bool principalsEqual = false;
   aDoc1->NodePrincipal()->Equals(aDoc2->NodePrincipal(), &principalsEqual);
   return principalsEqual;
@@ -6747,26 +6733,26 @@ nsContentUtils::FireMutationEventsForDir
       childNodes.AppendElement(child);
     }
     FragmentOrElement::FireNodeInserted(aDoc, aDest, childNodes);
   }
 }
 
 /* static */
 nsIDocument*
-nsContentUtils::GetFullscreenAncestor(nsIDocument* aDoc)
-{
+nsContentUtils::GetRootDocument(nsIDocument* aDoc)
+{
+  if (!aDoc) {
+    return nullptr;
+  }
   nsIDocument* doc = aDoc;
-  while (doc) {
-    if (doc->IsFullScreenDoc()) {
-      return doc;
-    }
+  while (doc->GetParentDocument()) {
     doc = doc->GetParentDocument();
   }
-  return nullptr;
+  return doc;
 }
 
 /* static */
 bool
 nsContentUtils::IsInPointerLockContext(nsIDOMWindow* aWin)
 {
   if (!aWin) {
     return false;
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1855,42 +1855,16 @@ public:
   /**
    * Returns true if requests for full-screen are allowed in the current
    * context. Requests are only allowed if the user initiated them (like with
    * a mouse-click or key press), unless this check has been disabled by
    * setting the pref "full-screen-api.allow-trusted-requests-only" to false.
    */
   static bool IsRequestFullScreenAllowed();
 
-  /**
-   * Returns true if the DOM fullscreen API is restricted to content only.
-   * This mirrors the pref "full-screen-api.content-only". If this is true,
-   * fullscreen requests in chrome are denied, and fullscreen requests in
-   * content stop percolating upwards before they reach chrome documents.
-   * That is, when an element in content requests fullscreen, only its
-   * containing frames that are in content are also made fullscreen, not
-   * the containing frame in the chrome document.
-   *
-   * Note if the fullscreen API is running in content only mode then multiple
-   * branches of a doctree can be fullscreen at the same time, but no fullscreen
-   * document will have a common ancestor with another fullscreen document
-   * that is also fullscreen (since the only common ancestor they can have
-   * is the chrome document, and that can't be fullscreen). i.e. multiple
-   * child documents of the chrome document can be fullscreen, but the chrome
-   * document won't be fullscreen.
-   *
-   * Making the fullscreen API content only is useful on platforms where we
-   * still want chrome to be visible or accessible while content is
-   * fullscreen.
-   *
-   * Note that if the fullscreen API is content only, chrome can still go
-   * fullscreen by setting the "fullScreen" attribute on its XUL window.
-   */
-  static bool IsFullscreenApiContentOnly();
-
   /*
    * Returns true if the performance timing APIs are enabled.
    */
   static bool IsPerformanceTimingEnabled()
   {
     return sIsPerformanceTimingEnabled;
   }
   
@@ -1943,22 +1917,20 @@ public:
    * Returns true if the content is in a document and contains a plugin
    * which we don't control event dispatch for, i.e. do any plugins in this
    * doc tree receive key events outside of our control? This always returns
    * false on MacOSX.
    */
   static bool HasPluginWithUncontrolledEventDispatch(nsIContent* aContent);
 
   /**
-   * Returns the document that is the closest ancestor to aDoc that is
-   * fullscreen. If aDoc is fullscreen this returns aDoc. If aDoc is not
-   * fullscreen and none of aDoc's ancestors are fullscreen this returns
-   * nullptr.
+   * Returns the root document in a document hierarchy. Normally this
+   * will be the chrome document.
    */
-  static nsIDocument* GetFullscreenAncestor(nsIDocument* aDoc);
+  static nsIDocument* GetRootDocument(nsIDocument* aDoc);
 
   /**
    * Returns true if aWin and the current pointer lock document
    * have common scriptable top window.
    */
   static bool IsInPointerLockContext(nsIDOMWindow* aWin);
 
   /**
@@ -2447,17 +2419,16 @@ private:
   static uint32_t sScriptBlockerCountWhereRunnersPrevented;
 
   static nsIInterfaceRequestor* sSameOriginChecker;
 
   static bool sIsHandlingKeyBoardEvent;
   static bool sAllowXULXBL_for_file;
   static bool sIsFullScreenApiEnabled;
   static bool sTrustedFullScreenOnly;
-  static bool sFullscreenApiIsContentOnly;
   static uint32_t sHandlingInputTimeout;
   static bool sIsPerformanceTimingEnabled;
   static bool sIsResourceTimingEnabled;
   static bool sIsUserTimingLoggingEnabled;
   static bool sIsExperimentalAutocompleteEnabled;
   static bool sEncodeDecodeURLHash;
 
   static nsHtml5StringParser* sHTMLFragmentParser;
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -11046,45 +11046,27 @@ public:
   }
 
 private:
   nsCOMPtr<nsIDocument> mDoc;
   bool mValue;
   nsRefPtr<gfx::VRHMDInfo> mHMD;
 };
 
-static nsIDocument*
-GetFullscreenRootDocument(nsIDocument* aDoc)
-{
-  if (!aDoc) {
-    return nullptr;
-  }
-  nsIDocument* doc = aDoc;
-  nsIDocument* parent;
-  while ((parent = doc->GetParentDocument()) &&
-         (!nsContentUtils::IsFullscreenApiContentOnly() ||
-          !nsContentUtils::IsChromeDoc(parent))) {
-    doc = parent;
-  }
-  return doc;
-}
-
 static void
 SetWindowFullScreen(nsIDocument* aDoc, bool aValue, gfx::VRHMDInfo *aVRHMD = nullptr)
 {
   // Maintain list of fullscreen root documents.
-  nsCOMPtr<nsIDocument> root = GetFullscreenRootDocument(aDoc);
+  nsCOMPtr<nsIDocument> root = nsContentUtils::GetRootDocument(aDoc);
   if (aValue) {
     FullscreenRoots::Add(root);
   } else {
     FullscreenRoots::Remove(root);
   }
-  if (!nsContentUtils::IsFullscreenApiContentOnly()) {
-    nsContentUtils::AddScriptRunner(new nsSetWindowFullScreen(aDoc, aValue, aVRHMD));
-  }
+  nsContentUtils::AddScriptRunner(new nsSetWindowFullScreen(aDoc, aValue, aVRHMD));
 }
 
 class nsCallExitFullscreen : public nsRunnable {
 public:
   explicit nsCallExitFullscreen(nsIDocument* aDoc)
     : mDoc(aDoc) {}
   NS_IMETHOD Run()
   {
@@ -11279,35 +11261,31 @@ GetFullscreenLeaf(nsIDocument* aDoc)
 {
   nsIDocument* leaf = nullptr;
   GetFullscreenLeaf(aDoc, &leaf);
   if (leaf) {
     return leaf;
   }
   // Otherwise we could be either in a non-fullscreen doc tree, or we're
   // below the fullscreen doc. Start the search from the root.
-  nsIDocument* root = GetFullscreenRootDocument(aDoc);
+  nsIDocument* root = nsContentUtils::GetRootDocument(aDoc);
   // Check that the root is actually fullscreen so we don't waste time walking
   // around its descendants.
   if (!root->IsFullScreenDoc()) {
     return nullptr;
   }
   GetFullscreenLeaf(root, &leaf);
   return leaf;
 }
 
 void
 nsDocument::RestorePreviousFullScreenState()
 {
   NS_ASSERTION(!IsFullScreenDoc() || !FullscreenRoots::IsEmpty(),
     "Should have at least 1 fullscreen root when fullscreen!");
-  NS_ASSERTION(!nsContentUtils::IsFullscreenApiContentOnly() ||
-               !nsContentUtils::IsChromeDoc(this),
-               "Should not run RestorePreviousFullScreenState() on "
-               "chrome document when fullscreen is content only");
 
   if (!IsFullScreenDoc() || !GetWindow() || FullscreenRoots::IsEmpty()) {
     return;
   }
 
   // If fullscreen mode is updated the pointer should be unlocked
   nsCOMPtr<Element> pointerLockedElement =
     do_QueryReferent(EventStateManager::sPointerLockedElement);
@@ -11372,30 +11350,30 @@ nsDocument::RestorePreviousFullScreenSta
       }
 
       if (!nsContentUtils::HaveEqualPrincipals(doc, fullScreenDoc)) {
         // The origin which is fullscreen changed. Send a notification to
         // the root process so that a warning or approval UI can be shown
         // as necessary.
         nsAutoString origin;
         nsContentUtils::GetUTFOrigin(doc->NodePrincipal(), origin);
-        nsIDocument* root = GetFullscreenRootDocument(doc);
+        nsIDocument* root = nsContentUtils::GetRootDocument(doc);
         nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
         os->NotifyObservers(root, "fullscreen-origin-change", origin.get());
       }
 
       break;
     }
   }
 
   if (doc == nullptr) {
     // We moved all documents in this doctree out of fullscreen mode,
     // move the top-level window out of fullscreen mode.
-    NS_ASSERTION(!GetFullscreenRootDocument(this)->IsFullScreenDoc(),
-      "Should have cleared all docs' stacks");
+    NS_ASSERTION(!nsContentUtils::GetRootDocument(this)->IsFullScreenDoc(),
+                 "Should have cleared all docs' stacks");
     nsRefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
       this, NS_LITERAL_STRING("MozExitedDomFullscreen"), true, true);
     asyncDispatcher->PostDOMEvent();
     SetWindowFullScreen(this, false);
   }
 }
 
 bool
@@ -11472,18 +11450,17 @@ LogFullScreenDenied(bool aLogFailure, co
                                   NS_LITERAL_CSTRING("DOM"), aDoc,
                                   nsContentUtils::eDOM_PROPERTIES,
                                   aMessage);
 }
 
 nsresult
 nsDocument::AddFullscreenApprovedObserver()
 {
-  if (mHasFullscreenApprovedObserver ||
-      !Preferences::GetBool("full-screen-api.approval-required")) {
+  if (mHasFullscreenApprovedObserver) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
 
   nsresult res = os->AddObserver(this, "fullscreen-approved", true);
   NS_ENSURE_SUCCESS(res, res);
@@ -11661,17 +11638,17 @@ nsresult nsDocument::RemoteFrameFullscre
   // Origin changed in child process, send notifiction, so that chrome can
   // update the UI to reflect the fullscreen origin change if necessary.
   // The BrowserElementChild listens on this, and forwards it over its
   // parent process, where it is redispatched. Chrome (in the root process,
   // which could be *this* process) listens for this notification so that
   // it can show a warning or approval UI.
   if (!aOrigin.IsEmpty()) {
     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-    os->NotifyObservers(GetFullscreenRootDocument(this),
+    os->NotifyObservers(nsContentUtils::GetRootDocument(this),
                         "fullscreen-origin-change",
                         PromiseFlatString(aOrigin).get());
   }
 
   return NS_OK;
 }
 
 nsresult nsDocument::RemoteFrameFullscreenReverted()
@@ -11706,23 +11683,16 @@ nsDocument::RequestFullScreen(Element* a
   if (aElement->OwnerDoc() != this) {
     LogFullScreenDenied(true, "FullScreenDeniedMovedDocument", this);
     return;
   }
   if (!GetWindow()) {
     LogFullScreenDenied(true, "FullScreenDeniedLostWindow", this);
     return;
   }
-  if (nsContentUtils::IsFullscreenApiContentOnly() &&
-      nsContentUtils::IsChromeDoc(this)) {
-    // Block fullscreen requests in the chrome document when the fullscreen API
-    // is configured for content only.
-    LogFullScreenDenied(true, "FullScreenDeniedContentOnly", this);
-    return;
-  }
   if (!IsFullScreenEnabled(aWasCallerChrome, true)) {
     // IsFullScreenEnabled calls LogFullScreenDenied, no need to log.
     return;
   }
   if (GetFullScreenElement() &&
       !nsContentUtils::ContentIsDescendantOf(aElement, GetFullScreenElement())) {
     // If this document is full-screen, only grant full-screen requests from
     // a descendant of the current full-screen element.
@@ -11759,17 +11729,17 @@ nsDocument::RequestFullScreen(Element* a
   // too. We're required by the spec to dispatch the events in root-to-leaf
   // order, but we traverse the doctree in a leaf-to-root order, so we save
   // references to the documents we must dispatch to so that we get the order
   // as specified.
   nsAutoTArray<nsIDocument*, 8> changed;
 
   // Remember the root document, so that if a full-screen document is hidden
   // we can reset full-screen state in the remaining visible full-screen documents.
-  nsIDocument* fullScreenRootDoc = GetFullscreenRootDocument(this);
+  nsIDocument* fullScreenRootDoc = nsContentUtils::GetRootDocument(this);
   if (fullScreenRootDoc->IsFullScreenDoc()) {
     // A document is already in fullscreen, unlock the mouse pointer
     // before setting a new document to fullscreen
     UnlockPointer();
   }
 
   // If a document is already in fullscreen, then unlock the mouse pointer
   // before setting a new document to fullscreen
@@ -11873,17 +11843,17 @@ nsDocument::RequestFullScreen(Element* a
   // root document knows the origin of the document which requested fullscreen.
   // This is used for the fullscreen approval UI. If we're in a child
   // process, the root BrowserElementChild listens for this notification,
   // and forwards it across to its BrowserElementParent, which
   // re-broadcasts the message for the root document in its process.
   if (aNotifyOnOriginChange &&
       !nsContentUtils::HaveEqualPrincipals(previousFullscreenDoc, this)) {
     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
-    nsIDocument* root = GetFullscreenRootDocument(this);
+    nsIDocument* root = nsContentUtils::GetRootDocument(this);
     nsAutoString origin;
     nsContentUtils::GetUTFOrigin(NodePrincipal(), origin);
     os->NotifyObservers(root, "fullscreen-origin-change", origin.get());
   }
 
   // Make the window full-screen. Note we must make the state changes above
   // before making the window full-screen, as then the document reports as
   // being in full-screen mode when the chrome "fullscreen" event fires,
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -1226,26 +1226,25 @@ nsFocusManager::SetFocusInner(nsIContent
   }
 
   // Exit fullscreen if we're focusing a windowed plugin on a non-MacOSX
   // system. We don't control event dispatch to windowed plugins on non-MacOSX,
   // so we can't display the "Press ESC to leave fullscreen mode" warning on
   // key input if a windowed plugin is focused, so just exit fullscreen
   // to guard against phishing.
 #ifndef XP_MACOSX
-  nsIDocument* fullscreenAncestor;
   if (contentToFocus &&
-      (fullscreenAncestor = nsContentUtils::GetFullscreenAncestor(contentToFocus->OwnerDoc())) &&
+      nsContentUtils::GetRootDocument(contentToFocus->OwnerDoc())->IsFullScreenDoc() &&
       nsContentUtils::HasPluginWithUncontrolledEventDispatch(contentToFocus)) {
     nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                     NS_LITERAL_CSTRING("DOM"),
                                     contentToFocus->OwnerDoc(),
                                     nsContentUtils::eDOM_PROPERTIES,
                                     "FocusedWindowedPluginWhileFullScreen");
-    nsIDocument::ExitFullscreen(fullscreenAncestor, /* async */ true);
+    nsIDocument::ExitFullscreen(contentToFocus->OwnerDoc(), /* async */ true);
   }
 #endif
 
   // if the FLAG_NOSWITCHFRAME flag is used, only allow the focus to be
   // shifted away from the current element if the new shell to focus is
   // the same or an ancestor shell of the currently focused shell.
   bool allowFrameSwitch = !(aFlags & FLAG_NOSWITCHFRAME) ||
                             IsSameOrAncestor(newWindow, mFocusedWindow);
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -1156,20 +1156,17 @@ public:
    * If aDocument is null, all fullscreen documents in all browser windows
    * exit fullscreen.
    *
    * If aDocument is non null, all documents from aDocument's fullscreen root
    * to the fullscreen leaf exit fullscreen. 
    *
    * Note that the fullscreen leaf is the bottom-most document which is
    * fullscreen, it may have non-fullscreen child documents. The fullscreen
-   * root is usually the chrome document, but if fullscreen is content-only,
-   * (see the comment in nsContentUtils.h on IsFullscreenApiContentOnly())
-   * the fullscreen root will be a direct child of the chrome document, and
-   * there may be other branches of the same doctree that are fullscreen.
+   * root is normally the chrome document.
    *
    * If aRunAsync is true, fullscreen is executed asynchronously.
    *
    * Note if aDocument is not fullscreen this function has no effect, even if
    * aDocument has fullscreen ancestors.
    */
   static void ExitFullscreen(nsIDocument* aDocument, bool aRunAsync);
 
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -1048,16 +1048,17 @@ ContentEventHandler::OnQueryCaretRect(Wi
     if (offset == aEvent->mInput.mOffset) {
       if (!caretFrame) {
         return NS_ERROR_FAILURE;
       }
       rv = ConvertToRootViewRelativeOffset(caretFrame, caretRect);
       NS_ENSURE_SUCCESS(rv, rv);
       aEvent->mReply.mRect = LayoutDevicePixel::FromUntyped(
         caretRect.ToOutsidePixels(caretFrame->PresContext()->AppUnitsPerDevPixel()));
+      aEvent->mReply.mWritingMode = caretFrame->GetWritingMode();
       aEvent->mReply.mOffset = aEvent->mInput.mOffset;
       aEvent->mSucceeded = true;
       return NS_OK;
     }
   }
 
   // Otherwise, we should set the guessed caret rect.
   nsRefPtr<nsRange> range = new nsRange(mRootContent);
@@ -1081,16 +1082,17 @@ ContentEventHandler::OnQueryCaretRect(Wi
   rect.width = caretRect.width;
   rect.height = frame->GetSize().height;
 
   rv = ConvertToRootViewRelativeOffset(frame, rect);
   NS_ENSURE_SUCCESS(rv, rv);
 
   aEvent->mReply.mRect = LayoutDevicePixel::FromUntyped(
       rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()));
+  aEvent->mReply.mWritingMode = frame->GetWritingMode();
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 nsresult
 ContentEventHandler::OnQueryContentState(WidgetQueryContentEvent* aEvent)
 {
   nsresult rv = Init(aEvent);
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -27,16 +27,17 @@
 #include "nsIPresShell.h"
 #include "nsISelectionController.h"
 #include "nsISelectionPrivate.h"
 #include "nsISupports.h"
 #include "nsIWidget.h"
 #include "nsPresContext.h"
 #include "nsThreadUtils.h"
 #include "nsWeakReference.h"
+#include "WritingModes.h"
 
 namespace mozilla {
 
 using namespace widget;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(IMEContentObserver)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IMEContentObserver)
@@ -242,16 +243,22 @@ IMEContentObserver::UnregisterObservers(
   }
 
   if (mUpdatePreference.WantPositionChanged() && mDocShell) {
     mDocShell->RemoveWeakScrollObserver(this);
     mDocShell->RemoveWeakReflowObserver(this);
   }
 }
 
+nsPresContext*
+IMEContentObserver::GetPresContext() const
+{
+  return mESM ? mESM->GetPresContext() : nullptr;
+}
+
 void
 IMEContentObserver::Destroy()
 {
   // WARNING: When you change this method, you have to check Unlink() too.
 
   UnregisterObservers(false);
 
   mEditor = nullptr;
@@ -326,22 +333,49 @@ public:
     : mDispatcher(aDispatcher)
     , mCausedByComposition(aCausedByComposition)
   {
     MOZ_ASSERT(mDispatcher);
   }
 
   NS_IMETHOD Run()
   {
-    if (mDispatcher->GetWidget()) {
-      IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
-      notification.mSelectionChangeData.mCausedByComposition =
-         mCausedByComposition;
-      mDispatcher->GetWidget()->NotifyIME(notification);
+    nsCOMPtr<nsIWidget> widget = mDispatcher->GetWidget();
+    nsPresContext* presContext = mDispatcher->GetPresContext();
+    if (!widget || !presContext) {
+      return NS_OK;
+    }
+
+    // XXX Cannot we cache some information for reducing the cost to compute
+    //     selection offset and writing mode?
+    WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, widget);
+    ContentEventHandler handler(presContext);
+    handler.OnQuerySelectedText(&selection);
+    if (NS_WARN_IF(!selection.mSucceeded)) {
+      return NS_OK;
     }
+
+    // The widget might be destroyed during querying the content since it
+    // causes flushing layout.
+    widget = mDispatcher->GetWidget();
+    if (!widget || NS_WARN_IF(widget->Destroyed())) {
+      return NS_OK;
+    }
+
+    IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
+    notification.mSelectionChangeData.mOffset =
+      selection.mReply.mOffset;
+    notification.mSelectionChangeData.mLength =
+      selection.mReply.mString.Length();
+    notification.mSelectionChangeData.SetWritingMode(
+                                        selection.GetWritingMode());
+    notification.mSelectionChangeData.mReversed = selection.mReply.mReversed;
+    notification.mSelectionChangeData.mCausedByComposition =
+      mCausedByComposition;
+    widget->NotifyIME(notification);
     return NS_OK;
   }
 
 private:
   nsRefPtr<IMEContentObserver> mDispatcher;
   bool mCausedByComposition;
 };
 
--- a/dom/events/IMEContentObserver.h
+++ b/dom/events/IMEContentObserver.h
@@ -74,16 +74,17 @@ public:
   void DisconnectFromEventStateManager();
   bool IsManaging(nsPresContext* aPresContext, nsIContent* aContent);
   bool IsEditorHandlingEventForComposition() const;
   bool KeepAliveDuringDeactive() const
   {
     return mUpdatePreference.WantDuringDeactive();
   }
   nsIWidget* GetWidget() const { return mWidget; }
+  nsPresContext* GetPresContext() const;
   nsresult GetSelectionAndRoot(nsISelection** aSelection,
                                nsIContent** aRoot) const;
 
   struct TextChangeData
   {
     // mStartOffset is the start offset of modified or removed text in
     // original content and inserted text in new content.
     uint32_t mStartOffset;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1889,16 +1889,25 @@ TabParent::RecvNotifyIMESelection(const 
     mIMESelectionFocus = aFocus;
     mWritingMode = aWritingMode;
     const nsIMEUpdatePreference updatePreference =
       widget->GetIMEUpdatePreference();
     if (updatePreference.WantSelectionChange() &&
         (updatePreference.WantChangesCausedByComposition() ||
          !aCausedByComposition)) {
       IMENotification notification(NOTIFY_IME_OF_SELECTION_CHANGE);
+      notification.mSelectionChangeData.mOffset =
+        std::min(mIMESelectionAnchor, mIMESelectionFocus);
+      notification.mSelectionChangeData.mLength =
+        mIMESelectionAnchor > mIMESelectionFocus ?
+          mIMESelectionAnchor - mIMESelectionFocus :
+          mIMESelectionFocus - mIMESelectionAnchor;
+      notification.mSelectionChangeData.mReversed =
+        mIMESelectionFocus < mIMESelectionAnchor;
+      notification.mSelectionChangeData.SetWritingMode(mWritingMode);
       notification.mSelectionChangeData.mCausedByComposition =
         aCausedByComposition;
       widget->NotifyIME(notification);
     }
   }
   return true;
 }
 
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -74,17 +74,16 @@ FullScreenDeniedHidden=Request for full-
 FullScreenDeniedIframeNotAllowed=Request for full-screen was denied because at least one of the document's containing iframes does not have an "allowfullscreen" attribute.
 FullScreenDeniedNotInputDriven=Request for full-screen was denied because Element.mozRequestFullScreen() was not called from inside a short running user-generated event handler.
 FullScreenDeniedNotInDocument=Request for full-screen was denied because requesting element is no longer in its document.
 FullScreenDeniedMovedDocument=Request for full-screen was denied because requesting element has moved document.
 FullScreenDeniedLostWindow=Request for full-screen was denied because we no longer have a window.
 FullScreenDeniedSubDocFullScreen=Request for full-screen was denied because a subdocument of the document requesting full-screen is already full-screen.
 FullScreenDeniedNotDescendant=Request for full-screen was denied because requesting element is not a descendant of the current full-screen element.
 FullScreenDeniedNotFocusedTab=Request for full-screen was denied because requesting element is not in the currently focused tab.
-FullScreenDeniedContentOnly=Request for full-screen was denied because requesting element is in the chrome document and the fullscreen API is configured for content only.
 RemovedFullScreenElement=Exited full-screen because full-screen element was removed from document.
 FocusedWindowedPluginWhileFullScreen=Exited full-screen because windowed plugin was focused.
 HTMLSyncXHRWarning=HTML parsing in XMLHttpRequest is not supported in the synchronous mode.
 InvalidRedirectChannelWarning=Unable to redirect to %S because the channel doesn't implement nsIWritablePropertyBag2.
 ResponseTypeSyncXHRWarning=Use of XMLHttpRequest's responseType attribute is no longer supported in the synchronous mode in window context.
 WithCredentialsSyncXHRWarning=Use of XMLHttpRequest's withCredentials attribute is no longer supported in the synchronous mode in window context.
 TimeoutSyncXHRWarning=Use of XMLHttpRequest's timeout attribute is not supported in the synchronous mode in window context.
 JSONCharsetWarning=An attempt was made to declare a non-UTF-8 encoding for JSON retrieved using XMLHttpRequest. Only UTF-8 is supported for decoding JSON.
--- a/dom/media/fmp4/wmf/WMFAudioMFTManager.cpp
+++ b/dom/media/fmp4/wmf/WMFAudioMFTManager.cpp
@@ -200,24 +200,27 @@ WMFAudioMFTManager::UpdateOutputType()
 
 HRESULT
 WMFAudioMFTManager::Output(int64_t aStreamOffset,
                            nsRefPtr<MediaData>& aOutData)
 {
   aOutData = nullptr;
   RefPtr<IMFSample> sample;
   HRESULT hr;
+  bool alreadyDidTypeChange = false;
   while (true) {
     hr = mDecoder->Output(&sample);
     if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
       return hr;
     }
     if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
       hr = UpdateOutputType();
       NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+      NS_ENSURE_FALSE(alreadyDidTypeChange, MF_E_TRANSFORM_STREAM_CHANGE);
+      alreadyDidTypeChange = true;
       continue;
     }
     break;
   }
 
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   RefPtr<IMFMediaBuffer> buffer;
--- a/dom/media/fmp4/wmf/WMFMediaDataDecoder.cpp
+++ b/dom/media/fmp4/wmf/WMFMediaDataDecoder.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WMFMediaDataDecoder.h"
 #include "VideoUtils.h"
 #include "WMFUtils.h"
 #include "nsTArray.h"
+#include "mozilla/Telemetry.h"
 
 #include "prlog.h"
 
 PRLogModuleInfo* GetDemuxerLog();
 #define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__))
 
 namespace mozilla {
 
@@ -39,16 +40,49 @@ WMFMediaDataDecoder::Init()
   MOZ_ASSERT(!mIsShutDown);
 
   mDecoder = mMFTManager->Init();
   NS_ENSURE_TRUE(mDecoder, NS_ERROR_FAILURE);
 
   return NS_OK;
 }
 
+// A single telemetry sample is reported for each MediaDataDecoder object
+// that has detected error or produced output successfully.
+static void
+SendTelemetry(HRESULT hr)
+{
+  // Collapse the error codes into a range of 0-0xff that can be viewed in
+  // telemetry histograms.  For most MF_E_* errors, unique samples are used,
+  // retaining the least significant 7 or 8 bits.  Other error codes are
+  // bucketed.
+  uint32_t sample;
+  if (SUCCEEDED(hr)) {
+    sample = 0;
+  } else if (hr < 0xc00d36b0) {
+    sample = 1; // low bucket
+  } else if (hr < 0xc00d3700) {
+    sample = hr & 0xffU; // MF_E_*
+  } else if (hr <= 0xc00d3705) {
+    sample = 0x80 + (hr & 0xfU); // more MF_E_*
+  } else if (hr < 0xc00d6d60) {
+    sample = 2; // mid bucket
+  } else if (hr <= 0xc00d6d78) {
+    sample = hr & 0xffU; // MF_E_TRANSFORM_*
+  } else {
+    sample = 3; // high bucket
+  }
+
+  nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+    [sample] {
+      Telemetry::Accumulate(Telemetry::MEDIA_WMF_DECODE_ERROR, sample);
+    });
+  NS_DispatchToMainThread(runnable);
+}
+
 nsresult
 WMFMediaDataDecoder::Shutdown()
 {
   MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
 
   if (mTaskQueue) {
     mTaskQueue->Dispatch(
       NS_NewRunnableMethod(this, &WMFMediaDataDecoder::ProcessShutdown));
@@ -60,16 +94,19 @@ WMFMediaDataDecoder::Shutdown()
 }
 
 void
 WMFMediaDataDecoder::ProcessShutdown()
 {
   if (mMFTManager) {
     mMFTManager->Shutdown();
     mMFTManager = nullptr;
+    if (!mRecordedError && mHasSuccessfulOutput) {
+      SendTelemetry(S_OK);
+    }
   }
   mDecoder = nullptr;
 }
 
 // Inserts data into the decoder's pipeline.
 nsresult
 WMFMediaDataDecoder::Input(MediaRawData* aSample)
 {
@@ -95,40 +132,49 @@ WMFMediaDataDecoder::ProcessDecode(Media
       return;
     }
   }
 
   HRESULT hr = mMFTManager->Input(aSample);
   if (FAILED(hr)) {
     NS_WARNING("MFTManager rejected sample");
     mCallback->Error();
+    if (!mRecordedError) {
+      SendTelemetry(hr);
+      mRecordedError = true;
+    }
     return;
   }
 
   mLastStreamOffset = aSample->mOffset;
 
   ProcessOutput();
 }
 
 void
 WMFMediaDataDecoder::ProcessOutput()
 {
   nsRefPtr<MediaData> output;
   HRESULT hr = S_OK;
   while (SUCCEEDED(hr = mMFTManager->Output(mLastStreamOffset, output)) &&
          output) {
+    mHasSuccessfulOutput = true;
     mCallback->Output(output);
   }
   if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
     if (mTaskQueue->IsEmpty()) {
       mCallback->InputExhausted();
     }
   } else if (FAILED(hr)) {
     NS_WARNING("WMFMediaDataDecoder failed to output data");
     mCallback->Error();
+    if (!mRecordedError) {
+      SendTelemetry(hr);
+      mRecordedError = true;
+    }
   }
 }
 
 void
 WMFMediaDataDecoder::ProcessFlush()
 {
   if (mDecoder) {
     mDecoder->Flush();
--- a/dom/media/fmp4/wmf/WMFMediaDataDecoder.h
+++ b/dom/media/fmp4/wmf/WMFMediaDataDecoder.h
@@ -104,13 +104,17 @@ private:
   // For access to and waiting on mIsFlushing
   Monitor mMonitor;
   // Set on reader/decode thread calling Flush() to indicate that output is
   // not required and so input samples on mTaskQueue need not be processed.
   // Cleared on mTaskQueue.
   bool mIsFlushing;
 
   bool mIsShutDown;
+
+  // For telemetry
+  bool mHasSuccessfulOutput = false;
+  bool mRecordedError = false;
 };
 
 } // namespace mozilla
 
 #endif // WMFMediaDataDecoder_h_
--- a/dom/media/fmp4/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/fmp4/wmf/WMFVideoMFTManager.cpp
@@ -477,40 +477,43 @@ WMFVideoMFTManager::CreateD3DVideoFrame(
 // Blocks until decoded sample is produced by the deoder.
 HRESULT
 WMFVideoMFTManager::Output(int64_t aStreamOffset,
                            nsRefPtr<MediaData>& aOutData)
 {
   RefPtr<IMFSample> sample;
   HRESULT hr;
   aOutData = nullptr;
+  bool alreadyDidTypeChange = false;
 
   // Loop until we decode a sample, or an unexpected error that we can't
   // handle occurs.
   while (true) {
     hr = mDecoder->Output(&sample);
     if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
       return MF_E_TRANSFORM_NEED_MORE_INPUT;
     }
     if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
       // Video stream output type change. Probably a geometric apperature
       // change. Reconfigure the video geometry, so that we output the
       // correct size frames.
       MOZ_ASSERT(!sample);
       hr = ConfigureVideoFrameGeometry();
       NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+      NS_ENSURE_FALSE(alreadyDidTypeChange, MF_E_TRANSFORM_STREAM_CHANGE);
       // Loop back and try decoding again...
+      alreadyDidTypeChange = true;
       continue;
     }
     if (SUCCEEDED(hr)) {
       break;
     }
     // Else unexpected error, assert, and bail.
     NS_WARNING("WMFVideoMFTManager::Output() unexpected error");
-    return E_FAIL;
+    return hr;
   }
 
   nsRefPtr<VideoData> frame;
   if (mUseHwAccel) {
     hr = CreateD3DVideoFrame(sample, aStreamOffset, getter_AddRefs(frame));
   } else {
     hr = CreateBasicVideoFrame(sample, aStreamOffset, getter_AddRefs(frame));
   }
--- a/dom/media/omx/AudioOffloadPlayer.cpp
+++ b/dom/media/omx/AudioOffloadPlayer.cpp
@@ -380,23 +380,16 @@ status_t AudioOffloadPlayer::DoSeek()
       MediaDecoder::SeekResolveValue val(mReachedEOS, mSeekTarget.mEventVisibility);
       mSeekPromise.Resolve(val, __func__);
     }
   }
 
   return OK;
 }
 
-double AudioOffloadPlayer::GetMediaTimeSecs()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return (static_cast<double>(GetMediaTimeUs()) /
-      static_cast<double>(USECS_PER_S));
-}
-
 int64_t AudioOffloadPlayer::GetMediaTimeUs()
 {
   android::Mutex::Autolock autoLock(mLock);
 
   int64_t playPosition = 0;
   if (mSeekTarget.IsValid()) {
     return mSeekTarget.mTime;
   }
--- a/dom/media/omx/AudioOffloadPlayer.h
+++ b/dom/media/omx/AudioOffloadPlayer.h
@@ -83,17 +83,17 @@ public:
   // Start the source if it's not already started and open the GonkAudioSink to
   // create an offloaded audio track
   virtual status_t Start(bool aSourceAlreadyStarted = false) override;
 
   virtual status_t ChangeState(MediaDecoder::PlayState aState) override;
 
   virtual void SetVolume(double aVolume) override;
 
-  virtual double GetMediaTimeSecs() override;
+  virtual int64_t GetMediaTimeUs() override;
 
   // To update progress bar when the element is visible
   virtual void SetElementVisibility(bool aIsVisible) override;;
 
   // Update ready state based on current play state. Not checking data
   // availability since offloading is currently done only when whole compressed
   // data is available
   virtual MediaDecoderOwner::NextFrameStatus GetNextFrameStatus() override;
@@ -187,18 +187,16 @@ private:
   // It is triggered in Pause() and canceled when there is a Play() within
   // OFFLOAD_PAUSE_MAX_USECS. Used only from main thread so no lock is needed.
   nsCOMPtr<nsITimer> mResetTimer;
 
   // To avoid device suspend when mResetTimer is going to be triggered.
   // Used only from main thread so no lock is needed.
   nsRefPtr<mozilla::dom::WakeLock> mWakeLock;
 
-  int64_t GetMediaTimeUs();
-
   // Provide the playback position in microseconds from total number of
   // frames played by audio track
   int64_t GetOutputPlayPositionUs_l() const;
 
   // Fill the buffer given by audio sink with data from compressed audio
   // source. Also handles the seek by seeking audio source and stop the sink in
   // case of error
   size_t FillBuffer(void *aData, size_t aSize);
--- a/dom/media/omx/AudioOffloadPlayerBase.h
+++ b/dom/media/omx/AudioOffloadPlayerBase.h
@@ -49,17 +49,17 @@ public:
 
   virtual status_t ChangeState(MediaDecoder::PlayState aState)
   {
     return android::NO_INIT;
   }
 
   virtual void SetVolume(double aVolume) {}
 
-  virtual double GetMediaTimeSecs() { return 0; }
+  virtual int64_t GetMediaTimeUs() { return 0; }
 
   // To update progress bar when the element is visible
   virtual void SetElementVisibility(bool aIsVisible) {}
 
   // Update ready state based on current play state. Not checking data
   // availability since offloading is currently done only when whole compressed
   // data is available
   virtual MediaDecoderOwner::NextFrameStatus GetNextFrameStatus()
--- a/dom/media/omx/MediaOmxCommonDecoder.cpp
+++ b/dom/media/omx/MediaOmxCommonDecoder.cpp
@@ -239,17 +239,17 @@ int64_t
 MediaOmxCommonDecoder::CurrentPosition()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!mAudioOffloadPlayer) {
     return MediaDecoder::CurrentPosition();
   }
 
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  return mAudioOffloadPlayer->GetMediaTimeSecs();
+  return mAudioOffloadPlayer->GetMediaTimeUs();
 }
 
 void
 MediaOmxCommonDecoder::SetElementVisibility(bool aIsVisible)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mAudioOffloadPlayer) {
     mAudioOffloadPlayer->SetElementVisibility(aIsVisible);
deleted file mode 100644
--- a/dom/media/webspeech/synth/ipc/test/file_ipc.html
+++ /dev/null
@@ -1,196 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test for OOP TTS</title>
-  <script type="application/javascript" src="../../test/common.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-  <body>
-
-  <script type="application/javascript;version=1.7">
-    "use strict";
-
-    window.SimpleTest = parent.SimpleTest;
-    window.ok = parent.ok;
-    window.is = parent.is;
-    window.info = parent.info;
-
-    // The crash observer registration functions are stubbed out here to
-    // prevent the iframe test runner from breaking later crash-related tests.
-    function iframeScriptFirst() {
-      SpecialPowers.prototype.registerProcessCrashObservers = () => {};
-      SpecialPowers.prototype.unregisterProcessCrashObservers = () => {};
-
-      content.wrappedJSObject.RunSet.reloadAndRunAll({
-          preventDefault: function() { },
-          __exposedProps__: { preventDefault: 'r' }
-      });
-    }
-
-    function iframeScriptSecond() {
-      let TestRunner = content.wrappedJSObject.TestRunner;
-
-      let oldComplete = TestRunner.onComplete;
-
-      TestRunner.onComplete = function() {
-        TestRunner.onComplete = oldComplete;
-
-        sendAsyncMessage("test:SpeechSynthesis:ipcTestComplete", {
-          result: JSON.stringify(TestRunner._failedTests)
-        });
-
-        if (oldComplete) {
-          oldComplete();
-        }
-      };
-      TestRunner.structuredLogger._dumpMessage = function(msg) {
-        sendAsyncMessage("test:SpeechSynthesis:ipcTestMessage", { msg: msg });
-      }
-    }
-
-    let VALID_ACTIONS = ['suite_start', 'suite_end', 'test_start', 'test_end', 'test_status', 'process_output', 'log'];
-    function validStructuredMessage(message) {
-      return message.action !== undefined && VALID_ACTIONS.indexOf(message.action) >= 0;
-    }
-    function onTestMessage(data) {
-      let message = SpecialPowers.wrap(data).data.msg;
-
-      if (validStructuredMessage(message)) {
-        switch (message.action) {
-          case "test_status":
-          case "test_end":
-            let test_tokens = message.test.split("/");
-            let test_name = test_tokens[test_tokens.length - 1];
-            if (message.subtest) {
-                test_name += " | " + message.subtest;
-            }
-            ok(message.expected === undefined, test_name, message.message);
-            break;
-          case "log":
-            info(message.message);
-            break;
-          default:
-            // nothing
-        }
-      }
-    }
-
-    function onTestComplete() {
-      let comp = SpecialPowers.wrap(SpecialPowers.Components);
-      let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
-      let spObserver = comp.classes["@mozilla.org/special-powers-observer;1"]
-                            .getService(comp.interfaces.nsIMessageListener);
-
-      mm.removeMessageListener("SPPrefService", spObserver);
-      mm.removeMessageListener("SPProcessCrashService", spObserver);
-      mm.removeMessageListener("SPPingService", spObserver);
-      mm.removeMessageListener("SpecialPowers.Quit", spObserver);
-      mm.removeMessageListener("SPPermissionManager", spObserver);
-
-      mm.removeMessageListener("test:SpeechSynthesis:ipcTestMessage", onTestMessage);
-      mm.removeMessageListener("test:SpeechSynthesis:ipcTestComplete", onTestComplete);
-
-      let ppmm = SpecialPowers.Cc["@mozilla.org/parentprocessmessagemanager;1"]
-        .getService(SpecialPowers.Ci.nsIMessageBroadcaster);
-      ppmm.removeMessageListener("test:SpeechSynthesis:ipcSynthAddVoice", onSynthAddVoice);
-      ppmm.removeMessageListener("test:SpeechSynthesis:ipcSynthSetDefault", onSynthSetDefault);
-      ppmm.removeMessageListener("test:SpeechSynthesis:ipcSynthCleanup", onSynthCleanup);
-
-      SimpleTest.executeSoon(function () { SimpleTest.finish(); });
-    }
-
-    function onSynthAddVoice(data) {
-      let message = SpecialPowers.wrap(data).json;
-      return synthAddVoice.apply(synthAddVoice, message);
-    }
-
-    function onSynthSetDefault(data) {
-      let message = SpecialPowers.wrap(data).json;
-      synthSetDefault.apply(synthSetDefault, message);
-    }
-
-    function onSynthCleanup(data) {
-      synthCleanup();
-    }
-
-    function runTests() {
-      let iframe = document.createElement("iframe");
-      SpecialPowers.wrap(iframe).mozbrowser = true;
-      iframe.id = "iframe";
-      iframe.style.width = "100%";
-      iframe.style.height = "1000px";
-
-      function iframeLoadSecond() {
-        ok(true, "Got second iframe load event.");
-        iframe.removeEventListener("mozbrowserloadend", iframeLoadSecond);
-        let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
-        mm.loadFrameScript("data:,(" + iframeScriptSecond.toString() + ")();",
-                           false);
-      }
-
-      function iframeLoadFirst() {
-        ok(true, "Got first iframe load event.");
-        iframe.removeEventListener("mozbrowserloadend", iframeLoadFirst);
-        iframe.addEventListener("mozbrowserloadend", iframeLoadSecond);
-
-        let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
-
-        let comp = SpecialPowers.wrap(SpecialPowers.Components);
-
-        let spObserver =
-          comp.classes["@mozilla.org/special-powers-observer;1"]
-              .getService(comp.interfaces.nsIMessageListener);
-
-        mm.addMessageListener("SPPrefService", spObserver);
-        mm.addMessageListener("SPProcessCrashService", spObserver);
-        mm.addMessageListener("SPPingService", spObserver);
-        mm.addMessageListener("SpecialPowers.Quit", spObserver);
-        mm.addMessageListener("SPPermissionManager", spObserver);
-
-        mm.addMessageListener("test:SpeechSynthesis:ipcTestMessage", onTestMessage);
-        mm.addMessageListener("test:SpeechSynthesis:ipcTestComplete", onTestComplete);
-
-        let specialPowersBase = "chrome://specialpowers/content/";
-        mm.loadFrameScript(specialPowersBase + "MozillaLogger.js", false);
-        mm.loadFrameScript(specialPowersBase + "specialpowersAPI.js", false);
-        mm.loadFrameScript(specialPowersBase + "specialpowers.js", false);
-
-        mm.loadFrameScript("data:,(" + iframeScriptFirst.toString() + ")();", false);
-
-        let ppmm = SpecialPowers.Cc["@mozilla.org/parentprocessmessagemanager;1"]
-          .getService(SpecialPowers.Ci.nsIMessageBroadcaster);
-        ppmm.addMessageListener("test:SpeechSynthesis:ipcSynthAddVoice", onSynthAddVoice);
-        ppmm.addMessageListener("test:SpeechSynthesis:ipcSynthSetDefault", onSynthSetDefault);
-        ppmm.addMessageListener("test:SpeechSynthesis:ipcSynthCleanup", onSynthCleanup);
-      }
-
-      iframe.addEventListener("mozbrowserloadend", iframeLoadFirst);
-
-      // Strip this filename and one directory level and then add "/test".
-      let href =  window.location.href;
-      href = href.substring(0, href.lastIndexOf('/'));
-      href = href.substring(0, href.lastIndexOf('/'));
-      href = href.substring(0, href.lastIndexOf('/'));
-      iframe.src = href + "/test?consoleLevel=INFO";
-
-      document.body.appendChild(iframe);
-    }
-
-    addEventListener("load", function() {
-
-      SpecialPowers.addPermission("browser", true, document);
-      SpecialPowers.pushPrefEnv({
-        "set": [
-          // TODO: remove this as part of bug 820712
-          ["network.disable.ipc.security", true],
-
-          ["dom.ipc.browser_frames.oop_by_default", true],
-          ["dom.mozBrowserFramesEnabled", true],
-          ["browser.pagethumbnails.capturing_disabled", true]
-        ]
-      }, runTests);
-    });
-
-  </script>
-</body>
-</html>
deleted file mode 100644
--- a/dom/media/webspeech/synth/ipc/test/mochitest.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[DEFAULT]
-skip-if = e10s
-support-files =
-  file_ipc.html
-
-[test_ipc.html]
-skip-if = buildapp == 'b2g' || toolkit == 'android' #bug 857673 # b2g(comp.classes['@mozilla.org/special-powers-observer;1'] is undefined) b2g-debug(comp.classes['@mozilla.org/special-powers-observer;1'] is undefined) b2g-desktop(comp.classes['@mozilla.org/special-powers-observer;1'] is undefined)
deleted file mode 100644
--- a/dom/media/webspeech/synth/ipc/test/test_ipc.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test for OOP TTS</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-  <body>
-  <iframe id="testFrame"></iframe>
-  <script type="application/javascript;version=1.7">
-    SimpleTest.waitForExplicitFinish();
-
-    SpecialPowers.pushPrefEnv({ set: [['media.webspeech.synth.enabled', true]] },
-                              function() { document.getElementById("testFrame").src = "file_ipc.html"; });
-  </script>
-</body>
-</html>
--- a/dom/media/webspeech/synth/moz.build
+++ b/dom/media/webspeech/synth/moz.build
@@ -1,16 +1,15 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 if CONFIG['MOZ_WEBSPEECH']:
     MOCHITEST_MANIFESTS += [
-        'ipc/test/mochitest.ini',
         'test/mochitest.ini',
     ]
 
     XPIDL_MODULE = 'dom_webspeechsynth'
 
     XPIDL_SOURCES += [
         'nsISpeechService.idl',
         'nsISynthVoiceRegistry.idl'
@@ -29,16 +28,18 @@ if CONFIG['MOZ_WEBSPEECH']:
     UNIFIED_SOURCES += [
         'ipc/SpeechSynthesisChild.cpp',
         'ipc/SpeechSynthesisParent.cpp',
         'nsSpeechTask.cpp',
         'nsSynthVoiceRegistry.cpp',
         'SpeechSynthesis.cpp',
         'SpeechSynthesisUtterance.cpp',
         'SpeechSynthesisVoice.cpp',
+        'test/FakeSynthModule.cpp',
+        'test/nsFakeSynthServices.cpp'
     ]
 
     if CONFIG['MOZ_SYNTH_PICO']:
         DIRS = ['pico']
 
 IPDL_SOURCES += [
     'ipc/PSpeechSynthesis.ipdl',
     'ipc/PSpeechSynthesisRequest.ipdl',
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -79,17 +79,17 @@ private:
   // and 'mSpeechTask' exclusively owns it and therefor exists as well.
   nsSpeechTask* mSpeechTask;
 
   bool mStarted;
 };
 
 // nsSpeechTask
 
-NS_IMPL_CYCLE_COLLECTION(nsSpeechTask, mSpeechSynthesis, mUtterance);
+NS_IMPL_CYCLE_COLLECTION(nsSpeechTask, mSpeechSynthesis, mUtterance, mCallback);
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSpeechTask)
   NS_INTERFACE_MAP_ENTRY(nsISpeechTask)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTask)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSpeechTask)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSpeechTask)
new file mode 100644
--- /dev/null
+++ b/dom/media/webspeech/synth/test/FakeSynthModule.cpp
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ModuleUtils.h"
+#include "nsIClassInfoImpl.h"
+
+#include "nsFakeSynthServices.h"
+
+using namespace mozilla::dom;
+
+#define FAKESYNTHSERVICE_CID \
+  {0xe7d52d9e, 0xc148, 0x47d8, {0xab, 0x2a, 0x95, 0xd7, 0xf4, 0x0e, 0xa5, 0x3d}}
+
+#define FAKESYNTHSERVICE_CONTRACTID "@mozilla.org/fakesynth;1"
+
+// Defines nsFakeSynthServicesConstructor
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsFakeSynthServices,
+                                         nsFakeSynthServices::GetInstanceForService)
+
+// Defines kFAKESYNTHSERVICE_CID
+NS_DEFINE_NAMED_CID(FAKESYNTHSERVICE_CID);
+
+static const mozilla::Module::CIDEntry kCIDs[] = {
+  { &kFAKESYNTHSERVICE_CID, true, nullptr, nsFakeSynthServicesConstructor },
+  { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kContracts[] = {
+  { FAKESYNTHSERVICE_CONTRACTID, &kFAKESYNTHSERVICE_CID },
+  { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kCategories[] = {
+  { "profile-after-change", "Fake Speech Synth", FAKESYNTHSERVICE_CONTRACTID },
+  { nullptr }
+};
+
+static void
+UnloadFakeSynthmodule()
+{
+  nsFakeSynthServices::Shutdown();
+}
+
+static const mozilla::Module kModule = {
+  mozilla::Module::kVersion,
+  kCIDs,
+  kContracts,
+  kCategories,
+  nullptr,
+  nullptr,
+  UnloadFakeSynthmodule
+};
+
+NSMODULE_DEFN(fakesynth) = &kModule;
--- a/dom/media/webspeech/synth/test/common.js
+++ b/dom/media/webspeech/synth/test/common.js
@@ -1,204 +1,8 @@
-var gSpeechRegistry = SpecialPowers.Cc["@mozilla.org/synth-voice-registry;1"]
-  .getService(SpecialPowers.Ci.nsISynthVoiceRegistry);
-
-var gAddedVoices = [];
-
-function SpeechTaskCallback(onpause, onresume, oncancel) {
-  this.onpause = onpause;
-  this.onresume = onresume;
-  this.oncancel = oncancel;
-}
-
-SpeechTaskCallback.prototype = {
-  QueryInterface: function(iid) {
-    return this;
-  },
-
-  getInterfaces: function(c) {},
-
-  getScriptableHelper: function() {},
-
-  onPause: function onPause() {
-    if (this.onpause)
-      this.onpause();
-  },
-
-  onResume: function onResume() {
-    if (this.onresume)
-      this.onresume();
-  },
-
-  onCancel: function onCancel() {
-    if (this.oncancel)
-      this.oncancel();
-  }
-};
-
-var TestSpeechServiceWithAudio = SpecialPowers.wrapCallbackObject({
-  CHANNELS: 1,
-  SAMPLE_RATE: 16000,
-
-  serviceType: SpecialPowers.Ci.nsISpeechService.SERVICETYPE_DIRECT_AUDIO,
-
-  speak: function speak(aText, aUri, aVolume, aRate, aPitch, aTask) {
-    var task = SpecialPowers.wrap(aTask);
-
-    window.setTimeout(
-      function () {
-        task.setup(SpecialPowers.wrapCallbackObject(new SpeechTaskCallback()), this.CHANNELS, this.SAMPLE_RATE);
-        // 0.025 seconds per character.
-        task.sendAudio(new Int16Array((this.SAMPLE_RATE/40)*aText.length), []);
-        task.sendAudio(new Int16Array(0), []);
-      }.bind(this), 0);
-  },
-
-  QueryInterface: function(iid) {
-    return this;
-  },
-
-  getInterfaces: function(c) {},
-
-  getScriptableHelper: function() {}
-});
-
-var TestSpeechServiceNoAudio = SpecialPowers.wrapCallbackObject({
-  serviceType: SpecialPowers.Ci.nsISpeechService.SERVICETYPE_INDIRECT_AUDIO,
-
-  speak: function speak(aText, aUri, aVolume, aRate, aPitch, aTask) {
-    var pair = this.expectedSpeaks.shift();
-    if (pair) {
-      // XXX: These tests do not happen in OOP
-      var utterance = pair[0];
-      var expected = pair[1];
-
-      is(aText, utterance.text, "Speak text matches utterance text");
-
-      var args = {uri: aUri, rate: aRate, pitch: aPitch};
-
-      for (var attr in args) {
-        if (expected[attr] != undefined)
-          is(args[attr], expected[attr], "expected service arg " + attr);
-      }
-    }
-
-    // If the utterance contains the phrase 'callback events', we will dispatch
-    // an appropriate event for each callback method.
-    var no_events = (aText.indexOf('callback events') < 0);
-    // If the utterance contains the phrase 'never end', we don't immediately
-    // end the 'synthesis' of the utterance.
-    var end_utterance = (aText.indexOf('never end') < 0);
-
-    var task = SpecialPowers.wrap(aTask);
-    task.setup(SpecialPowers.wrapCallbackObject(new SpeechTaskCallback(
-      function() {
-        if (!no_events) {
-          task.dispatchPause(1, 1.23);
-        }
-      },
-      function() {
-        if (!no_events) {
-          task.dispatchResume(1, 1.23);
-        }
-      },
-      function() {
-        if (!no_events) {
-          task.dispatchEnd(1, 1.23);
-        }
-      })));
-    setTimeout(function () {
-                 task.dispatchStart();
-                 if (end_utterance) {
-                   setTimeout(function () {
-                                task.dispatchEnd(
-                                  aText.length / 2.0, aText.length);
-                              }, 0);
-                 }
-               }, 0);
-  },
-
-  QueryInterface: function(iid) {
-    return this;
-  },
-
-  getInterfaces: function(c) {},
-
-  getScriptableHelper: function() {},
-
-  expectedSpeaks: []
-});
-
-function synthAddVoice(aServiceName, aName, aLang, aIsLocal) {
-  if (SpecialPowers.isMainProcess()) {
-    var voicesBefore = speechSynthesis.getVoices().length;
-    var uri = "urn:moz-tts:mylittleservice:" + encodeURI(aName + '?' + aLang);
-    gSpeechRegistry.addVoice(window[aServiceName], uri, aName, aLang, aIsLocal);
-
-    gAddedVoices.push([window[aServiceName], uri]);
-    var voicesAfter = speechSynthesis.getVoices().length;
-
-    is(voicesBefore + 1, voicesAfter, "Voice added");
-    var voice = speechSynthesis.getVoices()[voicesAfter - 1];
-    is(voice.voiceURI, uri, "voice URI matches");
-    is(voice.name, aName, "voice name matches");
-    is(voice.lang, aLang, "voice lang matches");
-    is(voice.localService, aIsLocal, "voice localService matches");
-
-    return uri;
-  } else {
-    // XXX: It would be nice to check here that the child gets the voice
-    // added update, but alas, it is aynchronous.
-    var mm = SpecialPowers.Cc["@mozilla.org/childprocessmessagemanager;1"]
-      .getService(SpecialPowers.Ci.nsISyncMessageSender);
-
-    return mm.sendSyncMessage(
-      'test:SpeechSynthesis:ipcSynthAddVoice',
-      [aServiceName, aName, aLang, aIsLocal])[0];
-  }
-}
-
-function synthSetDefault(aUri, aIsDefault) {
-  if (SpecialPowers.isMainProcess()) {
-    gSpeechRegistry.setDefaultVoice(aUri, aIsDefault);
-    var voices = speechSynthesis.getVoices();
-    for (var i in voices) {
-      if (voices[i].voiceURI == aUri)
-        ok(voices[i]['default'], "Voice set to default");
-    }
-  } else {
-    // XXX: It would be nice to check here that the child gets the voice
-    // added update, but alas, it is aynchronous.
-    var mm = SpecialPowers.Cc["@mozilla.org/childprocessmessagemanager;1"]
-      .getService(SpecialPowers.Ci.nsISyncMessageSender);
-
-    return mm.sendSyncMessage(
-      'test:SpeechSynthesis:ipcSynthSetDefault', [aUri, aIsDefault])[0];
-  }
-}
-
-function synthCleanup() {
-  if (SpecialPowers.isMainProcess()) {
-    var voicesBefore = speechSynthesis.getVoices().length;
-    var toRemove = gAddedVoices.length;
-    var removeArgs;
-    while ((removeArgs = gAddedVoices.shift()))
-      gSpeechRegistry.removeVoice.apply(gSpeechRegistry.removeVoice, removeArgs);
-
-    var voicesAfter = speechSynthesis.getVoices().length;
-    is(voicesAfter, voicesBefore - toRemove, "Successfully removed test voices");
-  } else {
-    // XXX: It would be nice to check here that the child gets the voice
-    // removed update, but alas, it is aynchronous.
-    var mm = SpecialPowers.Cc["@mozilla.org/childprocessmessagemanager;1"]
-      .getService(SpecialPowers.Ci.nsISyncMessageSender);
-    mm.sendSyncMessage('test:SpeechSynthesis:ipcSynthCleanup');
-  }
-}
-
 function synthTestQueue(aTestArgs, aEndFunc) {
   var utterances = [];
   for (var i in aTestArgs) {
     var uargs = aTestArgs[i][0];
     var u = new SpeechSynthesisUtterance(uargs.text);
 
     delete uargs.text;
 
@@ -215,24 +19,33 @@ function synthTestQueue(aTestArgs, aEndF
         ok(speechSynthesis.pending, "other utterances queued");
       } else {
         ok(!speechSynthesis.pending, "queue is empty, nothing pending.");
         if (aEndFunc)
           aEndFunc();
       }
     }
 
+    u.addEventListener('start',
+      (function (expectedUri) {
+        return function (e) {
+          if (expectedUri) {
+            var chosenVoice = SpecialPowers.wrap(e).target.chosenVoiceURI;
+            is(chosenVoice, expectedUri, "Incorrect URI is used");
+          }
+        };
+      })(aTestArgs[i][1] ? aTestArgs[i][1].uri : null));
+
     u.addEventListener('end', onend_handler);
     u.addEventListener('error', onend_handler);
 
     u.addEventListener(
       'error', function onerror_handler(e) {
         ok(false, "Error in speech utterance '" + e.target.text + "'");
       });
 
     utterances.push(u);
-    TestSpeechServiceNoAudio.expectedSpeaks.push([u, aTestArgs[i][1]]);
     speechSynthesis.speak(u);
   }
 
   ok(!speechSynthesis.speaking, "speechSynthesis is not speaking yet.");
   ok(speechSynthesis.pending, "speechSynthesis has an utterance queued.");
 }
--- a/dom/media/webspeech/synth/test/file_indirect_service_events.html
+++ b/dom/media/webspeech/synth/test/file_indirect_service_events.html
@@ -21,56 +21,54 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 1155034 **/
 
-synthAddVoice('TestSpeechServiceNoAudio', 'Female 1', 'en-GB', true);
-
 function test_with_events() {
   info('test_with_events');
   var utterance = new SpeechSynthesisUtterance("never end, callback events");
+  utterance.lang = 'it-IT-noend';
 
   utterance.addEventListener('start', function(e) {
     speechSynthesis.pause();
   // Wait to see if we get some bad events we didn't expect.
   });
 
   utterance.addEventListener('pause', function(e) {
-    ok(e.charIndex, 1, 'pause event charIndex matches service arguments');
-    ok(e.elapsedTime, 1.23, 'pause event elapsedTime matches service arguments');
+    is(e.charIndex, 1, 'pause event charIndex matches service arguments');
+    is(e.elapsedTime, 1.5, 'pause event elapsedTime matches service arguments');
     speechSynthesis.resume();
   });
 
   utterance.addEventListener('resume', function(e) {
-    ok(e.charIndex, 1, 'resume event charIndex matches service arguments');
-    ok(e.elapsedTime, 1.23, 'resume event elapsedTime matches service arguments');
+    is(e.charIndex, 1, 'resume event charIndex matches service arguments');
+    is(e.elapsedTime, 1.5, 'resume event elapsedTime matches service arguments');
     speechSynthesis.cancel();
   });
 
   utterance.addEventListener('end', function(e) {
     ok(e.charIndex, 1, 'resume event charIndex matches service arguments');
-    ok(e.elapsedTime, 1.23, 'end event elapsedTime matches service arguments');
+    ok(e.elapsedTime, 1.5, 'end event elapsedTime matches service arguments');
     test_no_events();
   });
 
   speechSynthesis.speak(utterance);
 }
 
 function test_no_events() {
   var utterance = new SpeechSynthesisUtterance("never end");
-
+  utterance.lang = "it-IT-noevents-noend";
   utterance.addEventListener('start', function(e) {
     speechSynthesis.pause();
     // Wait to see if we get some bad events we didn't expect.
     setTimeout(function() {
-      synthCleanup();
       SimpleTest.finish();
     }, 1000);
   });
 
   utterance.addEventListener('pause', function(e) {
     ok(false, 'no pause event was explicitly dispatched from the service')
     speechSynthesis.resume();
   });
--- a/dom/media/webspeech/synth/test/file_setup.html
+++ b/dom/media/webspeech/synth/test/file_setup.html
@@ -20,22 +20,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 525444 **/
 
-synthAddVoice('TestSpeechServiceNoAudio', 'Bob Marley', 'en-JM', true);
-synthAddVoice('TestSpeechServiceNoAudio', 'Amy Winehouse', 'en-GB', true);
-synthAddVoice('TestSpeechServiceNoAudio', 'Leonard Cohen', 'en-CA', true);
-synthAddVoice('TestSpeechServiceNoAudio', 'Celine Dion', 'fr-CA', true);
-synthAddVoice('TestSpeechServiceNoAudio', 'Julieta Venegas', 'es-MX', true);
-
 ok(SpeechSynthesis, "SpeechSynthesis exists in global scope");
 ok(SpeechSynthesisVoice, "SpeechSynthesisVoice exists in global scope");
 ok(SpeechSynthesisEvent, "SpeechSynthesisEvent exists in global scope");
 
 // SpeechSynthesisUtterance is the only type that has a constructor
 //  and writable properties
 ok(SpeechSynthesisUtterance, "SpeechSynthesisUtterance exists in global scope");
 var ssu = new SpeechSynthesisUtterance("hello world");
@@ -70,15 +64,13 @@ var voices1 = speechSynthesis.getVoices(
 var voices2 = speechSynthesis.getVoices();
 
 ok(voices1.length == voices2.length, "Voice count matches");
 
 for (var i in voices1) {
   ok(voices1[i] == voices2[i], "Voice instance matches");
 }
 
-synthCleanup();
-
 SimpleTest.finish();
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/webspeech/synth/test/file_speech_cancel.html
+++ b/dom/media/webspeech/synth/test/file_speech_cancel.html
@@ -21,40 +21,58 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 1150315 **/
 
-synthAddVoice('TestSpeechServiceWithAudio', 'Male 1', 'en-GB', true);
-
 var gotEndEvent = false;
-var utterance = new SpeechSynthesisUtterance("Hello, world!");
+// A long utterance that we will interrupt.
+var utterance = new SpeechSynthesisUtterance("Donec ac nunc feugiat, posuere " +
+  "mauris id, pharetra velit. Donec fermentum orci nunc, sit amet maximus" +
+  "dui tincidunt ut. Sed ultricies ac nisi a laoreet. Proin interdum," +
+  "libero maximus hendrerit posuere, lorem risus egestas nisl, a" +
+  "ultricies massa justo eu nisi. Duis mattis nibh a ligula tincidunt" +
+  "tincidunt non eu erat. Sed bibendum varius vulputate. Cras leo magna," +
+  "ornare ac posuere vel, luctus id metus. Mauris nec quam ac augue" +
+  "consectetur bibendum. Integer a commodo tortor. Duis semper dolor eu" +
+  "facilisis facilisis. Etiam venenatis turpis est, quis tincidunt velit" +
+  "suscipit a. Cras semper orci in sapien rhoncus bibendum. Suspendisse" +
+  "eu ex lobortis, finibus enim in, condimentum quam. Maecenas eget dui" +
+  "ipsum. Aliquam tortor leo, interdum eget congue ut, tempor id elit.");
 utterance.addEventListener('start', function(e) {
+  ok(true, 'start utterance 1');
   speechSynthesis.cancel();
   speechSynthesis.speak(utterance2);
 });
 
-var utterance2 = new SpeechSynthesisUtterance("Hello, world 2!");
+var utterance2 = new SpeechSynthesisUtterance("Proin ornare neque vitae " +
+  "risus mattis rutrum. Suspendisse a velit ut est convallis aliquet." +
+  "Nullam ante elit, malesuada vel luctus rutrum, ultricies nec libero." +
+  "Praesent eu iaculis orci. Sed nisl diam, sodales ac purus et," +
+  "volutpat interdum tortor. Nullam aliquam porta elit et maximus. Cras" +
+  "risus lectus, elementum vel sodales vel, ultricies eget lectus." +
+  "Curabitur velit lacus, mollis vel finibus et, molestie sit amet" +
+  "sapien. Proin vitae dolor ac augue posuere efficitur ac scelerisque" +
+  "diam. Nulla sed odio elit.");
 utterance2.addEventListener('start', function() {
   speechSynthesis.cancel();
   speechSynthesis.speak(utterance3);
 });
 utterance2.addEventListener('end', function(e) {
   gotEndEvent = true;
 });
 
 var utterance3 = new SpeechSynthesisUtterance("Hello, world 3!");
 utterance3.addEventListener('start', function() {
-  ok(gotEndEvent, "didn't get start event for this utterance")
+  ok(gotEndEvent, "didn't get start event for this utterance");
 });
 utterance3.addEventListener('end', function(e) {
-  synthCleanup();
   SimpleTest.finish();
 });
 
 speechSynthesis.speak(utterance);
 ok(!speechSynthesis.speaking, "speechSynthesis is not speaking yet.");
 ok(speechSynthesis.pending, "speechSynthesis has an utterance queued.");
 
 </script>
--- a/dom/media/webspeech/synth/test/file_speech_queue.html
+++ b/dom/media/webspeech/synth/test/file_speech_queue.html
@@ -13,58 +13,64 @@ https://bugzilla.mozilla.org/show_bug.cg
     window.ok = parent.ok;
   </script>
   <script type="application/javascript" src="common.js"></script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=525444">Mozilla Bug 525444</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 525444 **/
 
-var englishJamaican = synthAddVoice('TestSpeechServiceNoAudio',
-                                    'Bob Marley', 'en-JM', true);
-var englishBritish = synthAddVoice('TestSpeechServiceNoAudio',
-                                   'Amy Winehouse', 'en-GB', true);
-var englishCanadian = synthAddVoice('TestSpeechServiceNoAudio',
-                                    'Leonard Cohen', 'en-CA', true);
-var frenchCanadian = synthAddVoice('TestSpeechServiceNoAudio',
-                                   'Celine Dion', 'fr-CA', true);
-var spanishMexican = synthAddVoice('TestSpeechServiceNoAudio',
-                                   'Julieta Venegas', 'es-MX', true);
+// XXX: Rate and pitch are not tested.
+
+var langUriMap = {};
 
-synthSetDefault(englishBritish, true);
+for (var voice of speechSynthesis.getVoices()) {
+  if (voice.voiceURI.indexOf('urn:moz-tts:fake-direct') < 0) {
+    continue;
+  }
+  langUriMap[voice.lang] = voice.voiceURI;
+  ok(true, voice.lang + ' ' + voice.voiceURI + ' ' + voice.default);
+  is(voice.default, voice.lang == 'en-JM', 'Only Jamaican voice should be default');
+}
+
+ok(langUriMap['en-JM'], 'No English-Jamaican voice');
+ok(langUriMap['en-GB'], 'No English-British voice');
+ok(langUriMap['en-CA'], 'No English-Canadian voice');
+ok(langUriMap['fr-CA'], 'No French-Canadian voice');
+ok(langUriMap['es-MX'], 'No Spanish-Mexican voice');
 
 synthTestQueue(
   [[{text: "Hello, world."},
-    { uri: englishBritish }],
+    { uri: langUriMap['en-JM'] }],
    [{text: "Bonjour tout le monde .", lang: "fr", rate: 0.5, pitch: 0.75},
-    { uri: frenchCanadian, rate: 0.5, pitch: 0.75}],
+    { uri: langUriMap['fr-CA'], rate: 0.5, pitch: 0.75}],
    [{text: "How are you doing?", lang: "en-GB"},
-    { rate: 1, pitch: 1, uri: englishBritish}],
-   [{text: "¡hasta mañana", lang: "es-ES"},
-    { uri: spanishMexican }]],
+    { rate: 1, pitch: 1, uri: langUriMap['en-GB']}],
+   [{text: "¡hasta mañana!", lang: "es-MX"},
+    { uri: langUriMap['es-MX'] }]],
   function () {
-    synthSetDefault(englishJamaican, true);
-    var test_data = [[{text: "I shot the  sheriff."},
-                      { uri: englishJamaican }]];
+    var test_data = [];
     var voices = speechSynthesis.getVoices();
-    for (var i in voices) {
-      test_data.push([{text: "Hello world", voice: voices[i]},
-                      {uri: voices[i].voiceURI}]);
+    for (var voice of voices) {
+      if (voice.voiceURI.indexOf('urn:moz-tts:fake-direct') < 0) {
+        continue;
+      }
+      test_data.push([{text: "Hello world", voice: voice},
+                      {uri: voice.voiceURI}]);
     }
 
     synthTestQueue(test_data,
                    function () {
-                     synthCleanup();
                      SimpleTest.finish();
                    });
   });
 
 
 
 </script>
 </pre>
--- a/dom/media/webspeech/synth/test/file_speech_simple.html
+++ b/dom/media/webspeech/synth/test/file_speech_simple.html
@@ -21,33 +21,30 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 525444 **/
 
-synthAddVoice('TestSpeechServiceWithAudio', 'Male 1', 'en-GB', true);
-
 var gotStartEvent = false;
 var gotBoundaryEvent = false;
 var utterance = new SpeechSynthesisUtterance("Hello, world!");
 utterance.addEventListener('start', function(e) {
   ok(speechSynthesis.speaking, "speechSynthesis is speaking.");
   ok(!speechSynthesis.pending, "speechSynthesis has no other utterances queued.");
   gotStartEvent = true;
 });
 
 utterance.addEventListener('end', function(e) {
   ok(!speechSynthesis.speaking, "speechSynthesis is not speaking.");
   ok(!speechSynthesis.pending, "speechSynthesis has no other utterances queued.");
   ok(gotStartEvent, "Got 'start' event.");
   info('end ' + e.elapsedTime);
-  synthCleanup();
   SimpleTest.finish();
 });
 
 speechSynthesis.speak(utterance);
 ok(!speechSynthesis.speaking, "speechSynthesis is not speaking yet.");
 ok(speechSynthesis.pending, "speechSynthesis has an utterance queued.");
 
 </script>
--- a/dom/media/webspeech/synth/test/mochitest.ini
+++ b/dom/media/webspeech/synth/test/mochitest.ini
@@ -1,19 +1,14 @@
 [DEFAULT]
-skip-if = e10s
 support-files =
   common.js
   file_setup.html
   file_speech_queue.html
   file_speech_simple.html
   file_speech_cancel.html
   file_indirect_service_events.html
 
 [test_setup.html]
 [test_speech_queue.html]
-skip-if = buildapp == 'b2g' # b2g(Test timed out)
 [test_speech_simple.html]
-skip-if = buildapp == 'b2g' # b2g(Test timed out)
 [test_speech_cancel.html]
-skip-if = toolkit == 'gonk' # b2g(Test timed out)
 [test_indirect_service_events.html]
-skip-if = toolkit == 'gonk' # b2g(Test timed out)
new file mode 100644
--- /dev/null
+++ b/dom/media/webspeech/synth/test/nsFakeSynthServices.cpp
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "nsISupports.h"
+#include "nsFakeSynthServices.h"
+#include "nsPrintfCString.h"
+#include "nsIWeakReferenceUtils.h"
+#include "SharedBuffer.h"
+#include "nsISimpleEnumerator.h"
+
+#include "mozilla/dom/nsSynthVoiceRegistry.h"
+#include "mozilla/dom/nsSpeechTask.h"
+
+#include "nsThreadUtils.h"
+#include "prenv.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/DebugOnly.h"
+
+#define CHANNELS 1
+#define SAMPLERATE 1600
+
+namespace mozilla {
+namespace dom {
+
+StaticRefPtr<nsFakeSynthServices> nsFakeSynthServices::sSingleton;
+
+enum VoiceFlags
+{
+  eSuppressEvents = 1,
+  eSuppressEnd = 2
+};
+
+struct VoiceDetails
+{
+  const char* uri;
+  const char* name;
+  const char* lang;
+  bool defaultVoice;
+  uint32_t flags;
+};
+
+static const VoiceDetails sDirectVoices[] = {
+  {"urn:moz-tts:fake-direct:bob", "Bob Marley", "en-JM", true, 0},
+  {"urn:moz-tts:fake-direct:amy", "Amy Winehouse", "en-GB", false, 0},
+  {"urn:moz-tts:fake-direct:lenny", "Leonard Cohen", "en-CA", false, 0},
+  {"urn:moz-tts:fake-direct:celine", "Celine Dion", "fr-CA", false, 0},
+  {"urn:moz-tts:fake-direct:julie", "Julieta Venegas", "es-MX", false, },
+};
+
+static const VoiceDetails sIndirectVoices[] = {
+  {"urn:moz-tts:fake-indirect:zanetta", "Zanetta Farussi", "it-IT", false, 0},
+  {"urn:moz-tts:fake-indirect:margherita", "Margherita Durastanti", "it-IT-noevents-noend", false, eSuppressEvents | eSuppressEnd},
+  {"urn:moz-tts:fake-indirect:teresa", "Teresa Cornelys", "it-IT-noend", false, eSuppressEnd},
+};
+
+// FakeSynthCallback
+class FakeSynthCallback : public nsISpeechTaskCallback
+{
+public:
+  explicit FakeSynthCallback(nsISpeechTask* aTask) : mTask(aTask) { }
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(FakeSynthCallback, nsISpeechTaskCallback)
+
+  NS_IMETHOD OnPause()
+  {
+    if (mTask) {
+      mTask->DispatchPause(1.5, 1);
+    }
+
+    return NS_OK;
+  }
+
+  NS_IMETHOD OnResume()
+  {
+    if (mTask) {
+      mTask->DispatchResume(1.5, 1);
+    }
+
+    return NS_OK;
+  }
+
+  NS_IMETHOD OnCancel()
+  {
+    if (mTask) {
+      mTask->DispatchEnd(1.5, 1);
+    }
+
+    return NS_OK;
+  }
+
+private:
+  virtual ~FakeSynthCallback() { }
+
+  nsCOMPtr<nsISpeechTask> mTask;
+};
+
+NS_IMPL_CYCLE_COLLECTION(FakeSynthCallback, mTask);
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FakeSynthCallback)
+  NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FakeSynthCallback)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FakeSynthCallback)
+
+// FakeDirectAudioSynth
+
+class FakeDirectAudioSynth : public nsISpeechService
+{
+
+public:
+  FakeDirectAudioSynth() { }
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISPEECHSERVICE
+
+private:
+  virtual ~FakeDirectAudioSynth() { }
+};
+
+NS_IMPL_ISUPPORTS(FakeDirectAudioSynth, nsISpeechService)
+
+NS_IMETHODIMP
+FakeDirectAudioSynth::Speak(const nsAString& aText, const nsAString& aUri,
+                            float aVolume, float aRate, float aPitch,
+                            nsISpeechTask* aTask)
+{
+  class Runnable final : public nsRunnable
+  {
+  public:
+    Runnable(nsISpeechTask* aTask, const nsAString& aText) :
+      mTask(aTask), mText(aText)
+    {
+    }
+
+    NS_IMETHOD Run() override
+    {
+      nsRefPtr<FakeSynthCallback> cb = new FakeSynthCallback(nullptr);
+      mTask->Setup(cb, CHANNELS, SAMPLERATE, 2);
+
+      // Just an arbitrary multiplier. Pretend that each character is
+      // synthesized to 40 frames.
+      uint32_t frames_length = 40 * mText.Length();
+      nsAutoArrayPtr<int16_t> frames(new int16_t[frames_length]());
+      mTask->SendAudioNative(frames, frames_length);
+
+      mTask->SendAudioNative(nullptr, 0);
+
+      return NS_OK;
+    }
+
+  private:
+    nsCOMPtr<nsISpeechTask> mTask;
+    nsString mText;
+  };
+
+  nsCOMPtr<nsIRunnable> runnable = new Runnable(aTask, aText);
+  NS_DispatchToMainThread(runnable);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+FakeDirectAudioSynth::GetServiceType(SpeechServiceType* aServiceType)
+{
+  *aServiceType = nsISpeechService::SERVICETYPE_DIRECT_AUDIO;
+  return NS_OK;
+}
+
+// FakeDirectAudioSynth
+
+class FakeIndirectAudioSynth : public nsISpeechService
+{
+
+public:
+  FakeIndirectAudioSynth() {}
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISPEECHSERVICE
+
+private:
+  virtual ~FakeIndirectAudioSynth() { }
+};
+
+NS_IMPL_ISUPPORTS(FakeIndirectAudioSynth, nsISpeechService)
+
+NS_IMETHODIMP
+FakeIndirectAudioSynth::Speak(const nsAString& aText, const nsAString& aUri,
+                              float aVolume, float aRate, float aPitch,
+                              nsISpeechTask* aTask)
+{
+  class DispatchStart final : public nsRunnable
+  {
+  public:
+    explicit DispatchStart(nsISpeechTask* aTask) :
+      mTask(aTask)
+    {
+    }
+
+    NS_IMETHOD Run() override
+    {
+      mTask->DispatchStart();
+
+      return NS_OK;
+    }
+
+  private:
+    nsCOMPtr<nsISpeechTask> mTask;
+  };
+
+  class DispatchEnd final : public nsRunnable
+  {
+  public:
+    DispatchEnd(nsISpeechTask* aTask, const nsAString& aText) :
+      mTask(aTask), mText(aText)
+    {
+    }
+
+    NS_IMETHOD Run() override
+    {
+      mTask->DispatchEnd(mText.Length()/2, mText.Length());
+
+      return NS_OK;
+    }
+
+  private:
+    nsCOMPtr<nsISpeechTask> mTask;
+    nsString mText;
+  };
+
+  uint32_t flags = 0;
+  for (uint32_t i = 0; i < ArrayLength(sIndirectVoices); i++) {
+    if (aUri.EqualsASCII(sIndirectVoices[i].uri)) {
+      flags = sIndirectVoices[i].flags;
+    }
+  }
+
+  nsRefPtr<FakeSynthCallback> cb = new FakeSynthCallback(
+    (flags & eSuppressEvents) ? nullptr : aTask);
+
+  aTask->Setup(cb, 0, 0, 0);
+
+  nsCOMPtr<nsIRunnable> runnable = new DispatchStart(aTask);
+  NS_DispatchToMainThread(runnable);
+
+  if ((flags & eSuppressEnd) == 0) {
+    runnable = new DispatchEnd(aTask, aText);
+    NS_DispatchToMainThread(runnable);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+FakeIndirectAudioSynth::GetServiceType(SpeechServiceType* aServiceType)
+{
+  *aServiceType = nsISpeechService::SERVICETYPE_INDIRECT_AUDIO;
+  return NS_OK;
+}
+
+// nsFakeSynthService
+
+NS_INTERFACE_MAP_BEGIN(nsFakeSynthServices)
+  NS_INTERFACE_MAP_ENTRY(nsIObserver)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsFakeSynthServices)
+NS_IMPL_RELEASE(nsFakeSynthServices)
+
+nsFakeSynthServices::nsFakeSynthServices()
+{
+}
+
+nsFakeSynthServices::~nsFakeSynthServices()
+{
+}
+
+static void
+AddVoices(nsISpeechService* aService, const VoiceDetails* aVoices, uint32_t aLength)
+{
+  nsSynthVoiceRegistry* registry = nsSynthVoiceRegistry::GetInstance();
+  for (uint32_t i = 0; i < aLength; i++) {
+    NS_ConvertUTF8toUTF16 name(aVoices[i].name);
+    NS_ConvertUTF8toUTF16 uri(aVoices[i].uri);
+    NS_ConvertUTF8toUTF16 lang(aVoices[i].lang);
+    registry->AddVoice(aService, uri, name, lang, true);
+    if (aVoices[i].defaultVoice) {
+      registry->SetDefaultVoice(uri, true);
+    }
+  }
+}
+
+void
+nsFakeSynthServices::Init()
+{
+  mDirectService = new FakeDirectAudioSynth();
+  AddVoices(mDirectService, sDirectVoices, ArrayLength(sDirectVoices));
+
+  mIndirectService = new FakeIndirectAudioSynth();
+  AddVoices(mIndirectService, sIndirectVoices, ArrayLength(sIndirectVoices));
+}
+
+// nsIObserver
+
+NS_IMETHODIMP
+nsFakeSynthServices::Observe(nsISupports* aSubject, const char* aTopic,
+                             const char16_t* aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE(!strcmp(aTopic, "profile-after-change"), NS_ERROR_UNEXPECTED);
+
+  if (Preferences::GetBool("media.webspeech.synth.test")) {
+    Init();
+  }
+
+  return NS_OK;
+}
+
+// static methods
+
+nsFakeSynthServices*
+nsFakeSynthServices::GetInstance()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    MOZ_ASSERT(false, "nsFakeSynthServices can only be started on main gecko process");
+    return nullptr;
+  }
+
+  if (!sSingleton) {
+    sSingleton = new nsFakeSynthServices();
+  }
+
+  return sSingleton;
+}
+
+already_AddRefed<nsFakeSynthServices>
+nsFakeSynthServices::GetInstanceForService()
+{
+  nsRefPtr<nsFakeSynthServices> picoService = GetInstance();
+  return picoService.forget();
+}
+
+void
+nsFakeSynthServices::Shutdown()
+{
+  if (!sSingleton) {
+    return;
+  }
+
+  sSingleton = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/webspeech/synth/test/nsFakeSynthServices.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 nsFakeSynthServices_h
+#define nsFakeSynthServices_h
+
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+#include "nsIObserver.h"
+#include "nsIThread.h"
+#include "nsISpeechService.h"
+#include "nsRefPtrHashtable.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Monitor.h"
+
+namespace mozilla {
+namespace dom {
+
+class nsFakeSynthServices : public nsIObserver
+{
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  nsFakeSynthServices();
+
+  static nsFakeSynthServices* GetInstance();
+
+  static already_AddRefed<nsFakeSynthServices> GetInstanceForService();
+
+  static void Shutdown();
+
+private:
+
+  virtual ~nsFakeSynthServices();
+
+  void Init();
+
+  nsCOMPtr<nsISpeechService> mDirectService;
+
+  nsCOMPtr<nsISpeechService> mIndirectService;
+
+  static StaticRefPtr<nsFakeSynthServices> sSingleton;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -819,20 +819,25 @@ nsGIFDecoder2::WriteInternal(const char*
     case gif_skip_block:
       GETN(1, gif_consume_block);
       break;
 
     case gif_control_extension:
       mGIFStruct.is_transparent = *q & 0x1;
       mGIFStruct.tpixel = q[3];
       mGIFStruct.disposal_method = ((*q) >> 2) & 0x7;
-      // Some specs say 3rd bit (value 4), other specs say value 3
-      // Let's choose 3 (the more popular)
+
       if (mGIFStruct.disposal_method == 4) {
+        // Some specs say 3rd bit (value 4), other specs say value 3.
+        // Let's choose 3 (the more popular).
         mGIFStruct.disposal_method = 3;
+      } else if (mGIFStruct.disposal_method > 4) {
+        // This GIF is using a disposal method which is undefined in the spec.
+        // Treat it as DisposalMethod::NOT_SPECIFIED.
+        mGIFStruct.disposal_method = 0;
       }
 
       {
         DisposalMethod method = DisposalMethod(mGIFStruct.disposal_method);
         if (method == DisposalMethod::CLEAR_ALL ||
             method == DisposalMethod::CLEAR) {
           // We may have to display the background under this image during
           // animation playback, so we regard it as transparent.
--- a/image/src/DecodePool.cpp
+++ b/image/src/DecodePool.cpp
@@ -3,20 +3,22 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "DecodePool.h"
 
 #include <algorithm>
 
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Monitor.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIObserverService.h"
 #include "nsIThreadPool.h"
+#include "nsThreadManager.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOMCIDInternal.h"
 #include "prsystem.h"
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
@@ -102,79 +104,199 @@ public:
 private:
   explicit NotifyDecodeCompleteWorker(Decoder* aDecoder)
     : mDecoder(aDecoder)
   { }
 
   nsRefPtr<Decoder> mDecoder;
 };
 
-class DecodeWorker : public nsRunnable
-{
-public:
-  explicit DecodeWorker(Decoder* aDecoder)
-    : mDecoder(aDecoder)
-  {
-    MOZ_ASSERT(mDecoder);
-  }
-
-  NS_IMETHOD Run() override
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
-    DecodePool::Singleton()->Decode(mDecoder);
-    return NS_OK;
-  }
-
-private:
-  nsRefPtr<Decoder> mDecoder;
-};
-
 #ifdef MOZ_NUWA_PROCESS
 
-class DecodePoolNuwaListener final : public nsIThreadPoolListener
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-  NS_IMETHODIMP OnThreadCreated()
-  {
-    if (IsNuwaProcess()) {
-      NuwaMarkCurrentThread(static_cast<void(*)(void*)>(nullptr), nullptr);
-    }
-    return NS_OK;
-  }
-
-  NS_IMETHODIMP OnThreadShuttingDown() { return NS_OK; }
-
-private:
-  ~DecodePoolNuwaListener() { }
-};
-
-NS_IMPL_ISUPPORTS(DecodePoolNuwaListener, nsIThreadPoolListener)
-
 class RegisterDecodeIOThreadWithNuwaRunnable : public nsRunnable
 {
 public:
   NS_IMETHOD Run()
   {
     NuwaMarkCurrentThread(static_cast<void(*)(void*)>(nullptr), nullptr);
     return NS_OK;
   }
 };
+
 #endif // MOZ_NUWA_PROCESS
 
 
 ///////////////////////////////////////////////////////////////////////////////
 // DecodePool implementation.
 ///////////////////////////////////////////////////////////////////////////////
 
 /* static */ StaticRefPtr<DecodePool> DecodePool::sSingleton;
 
 NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
 
+struct Work
+{
+  enum class Type {
+    DECODE,
+    SHUTDOWN
+  } mType;
+
+  nsRefPtr<Decoder> mDecoder;
+};
+
+class DecodePoolImpl
+{
+public:
+  MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl)
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)
+
+  DecodePoolImpl()
+    : mMonitor("DecodePoolImpl")
+    , mShuttingDown(false)
+  { }
+
+  /// Initialize the current thread for use by the decode pool.
+  void InitCurrentThread()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    mThreadNaming.SetThreadPoolName(NS_LITERAL_CSTRING("ImgDecoder"));
+
+#ifdef MOZ_NUWA_PROCESS
+    if (IsNuwaProcess()) {
+      NuwaMarkCurrentThread(static_cast<void(*)(void*)>(nullptr), nullptr);
+    }
+#endif // MOZ_NUWA_PROCESS
+  }
+
+  /// Shut down the provided decode pool thread.
+  static void ShutdownThread(nsIThread* aThisThread)
+  {
+    // Threads have to be shut down from another thread, so we'll ask the
+    // main thread to do it for us.
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewRunnableMethod(aThisThread, &nsIThread::Shutdown);
+    NS_DispatchToMainThread(runnable);
+  }
+
+  /**
+   * Requests shutdown. New work items will be dropped on the floor, and all
+   * decode pool threads will be shut down once existing work items have been
+   * processed.
+   */
+  void RequestShutdown()
+  {
+    MonitorAutoLock lock(mMonitor);
+    mShuttingDown = true;
+    mMonitor.NotifyAll();
+  }
+
+  /// Pushes a new decode work item.
+  void PushWork(Decoder* aDecoder)
+  {
+    nsRefPtr<Decoder> decoder(aDecoder);
+
+    MonitorAutoLock lock(mMonitor);
+
+    if (mShuttingDown) {
+      // Drop any new work on the floor if we're shutting down.
+      return;
+    }
+
+    mQueue.AppendElement(Move(decoder));
+    mMonitor.Notify();
+  }
+
+  /// Pops a new work item, blocking if necessary.
+  Work PopWork()
+  {
+    Work work;
+
+    MonitorAutoLock lock(mMonitor);
+
+    do {
+      if (!mQueue.IsEmpty()) {
+        // XXX(seth): This is NOT efficient, obviously, since we're removing an
+        // element from the front of the array. However, it's not worth
+        // implementing something better right now, because we are replacing
+        // this FIFO behavior with LIFO behavior very soon.
+        work.mType = Work::Type::DECODE;
+        work.mDecoder = mQueue.ElementAt(0);
+        mQueue.RemoveElementAt(0);
+
+#ifdef MOZ_NUWA_PROCESS
+        nsThreadManager::get()->SetThreadWorking();
+#endif // MOZ_NUWA_PROCESS
+
+        return work;
+      }
+
+      if (mShuttingDown) {
+        work.mType = Work::Type::SHUTDOWN;
+        return work;
+      }
+
+#ifdef MOZ_NUWA_PROCESS
+      nsThreadManager::get()->SetThreadIdle(nullptr);
+#endif // MOZ_NUWA_PROCESS
+
+      // Nothing to do; block until some work is available.
+      mMonitor.Wait();
+    } while (true);
+  }
+
+private:
+  ~DecodePoolImpl() { }
+
+  nsThreadPoolNaming mThreadNaming;
+
+  // mMonitor guards mQueue and mShuttingDown.
+  Monitor mMonitor;
+  nsTArray<nsRefPtr<Decoder>> mQueue;
+  bool mShuttingDown;
+};
+
+class DecodePoolWorker : public nsRunnable
+{
+public:
+  explicit DecodePoolWorker(DecodePoolImpl* aImpl) : mImpl(aImpl) { }
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    mImpl->InitCurrentThread();
+
+    nsCOMPtr<nsIThread> thisThread;
+    nsThreadManager::get()->GetCurrentThread(getter_AddRefs(thisThread));
+
+    do {
+      Work work = mImpl->PopWork();
+      switch (work.mType) {
+        case Work::Type::DECODE:
+          DecodePool::Singleton()->Decode(work.mDecoder);
+          break;
+
+        case Work::Type::SHUTDOWN:
+          DecodePoolImpl::ShutdownThread(thisThread);
+          return NS_OK;
+
+        default:
+          MOZ_ASSERT_UNREACHABLE("Unknown work type");
+      }
+    } while (true);
+
+    MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<DecodePoolImpl> mImpl;
+};
+
 /* static */ void
 DecodePool::Initialize()
 {
   MOZ_ASSERT(NS_IsMainThread());
   DecodePool::Singleton();
 }
 
 /* static */ DecodePool*
@@ -185,40 +307,37 @@ DecodePool::Singleton()
     sSingleton = new DecodePool();
     ClearOnShutdown(&sSingleton);
   }
 
   return sSingleton;
 }
 
 DecodePool::DecodePool()
-  : mMutex("image::DecodePool")
+  : mImpl(new DecodePoolImpl)
+  , mMutex("image::DecodePool")
 {
-  // Initialize the thread pool.
-  mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
-  MOZ_RELEASE_ASSERT(mThreadPool,
-                     "Should succeed in creating image decoding thread pool");
-
-  mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder"));
+  // Determine the number of threads we want.
   int32_t prefLimit = gfxPrefs::ImageMTDecodingLimit();
   uint32_t limit;
   if (prefLimit <= 0) {
     limit = max(PR_GetNumberOfProcessors(), 2) - 1;
   } else {
     limit = static_cast<uint32_t>(prefLimit);
   }
 
-  mThreadPool->SetThreadLimit(limit);
-  mThreadPool->SetIdleThreadLimit(limit);
-
-#ifdef MOZ_NUWA_PROCESS
-  if (IsNuwaProcess()) {
-    mThreadPool->SetListener(new DecodePoolNuwaListener());
+  // Initialize the thread pool.
+  for (uint32_t i = 0 ; i < limit ; ++i) {
+    nsCOMPtr<nsIRunnable> worker = new DecodePoolWorker(mImpl);
+    nsCOMPtr<nsIThread> thread;
+    nsresult rv = NS_NewThread(getter_AddRefs(thread), worker);
+    MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && thread,
+                       "Should successfully create image decoding threads");
+    mThreads.AppendElement(Move(thread));
   }
-#endif
 
   // Initialize the I/O thread.
   nsresult rv = NS_NewNamedThread("ImageIO", getter_AddRefs(mIOThread));
   MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mIOThread,
                      "Should successfully create image I/O thread");
 
 #ifdef MOZ_NUWA_PROCESS
   nsCOMPtr<nsIRunnable> worker = new RegisterDecodeIOThreadWithNuwaRunnable();
@@ -238,49 +357,44 @@ DecodePool::~DecodePool()
   MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
 }
 
 NS_IMETHODIMP
 DecodePool::Observe(nsISupports*, const char* aTopic, const char16_t*)
 {
   MOZ_ASSERT(strcmp(aTopic, "xpcom-shutdown-threads") == 0, "Unexpected topic");
 
-  nsCOMPtr<nsIThreadPool> threadPool;
+  nsCOMArray<nsIThread> threads;
   nsCOMPtr<nsIThread> ioThread;
 
   {
     MutexAutoLock lock(mMutex);
-    threadPool.swap(mThreadPool);
+    threads.AppendElements(mThreads);
+    mThreads.Clear();
     ioThread.swap(mIOThread);
   }
 
-  if (threadPool) {
-    threadPool->Shutdown();
+  mImpl->RequestShutdown();
+
+  for (int32_t i = 0 ; i < threads.Count() ; ++i) {
+    threads[i]->Shutdown();
   }
 
   if (ioThread) {
     ioThread->Shutdown();
   }
 
   return NS_OK;
 }
 
 void
 DecodePool::AsyncDecode(Decoder* aDecoder)
 {
   MOZ_ASSERT(aDecoder);
-
-  nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aDecoder);
-
-  // Dispatch to the thread pool if it exists. If it doesn't, we're currently
-  // shutting down, so it's OK to just drop the job on the floor.
-  MutexAutoLock threadPoolLock(mMutex);
-  if (mThreadPool) {
-    mThreadPool->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL);
-  }
+  mImpl->PushWork(aDecoder);
 }
 
 void
 DecodePool::SyncDecodeIfSmall(Decoder* aDecoder)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aDecoder);
 
@@ -295,39 +409,23 @@ DecodePool::SyncDecodeIfSmall(Decoder* a
 void
 DecodePool::SyncDecodeIfPossible(Decoder* aDecoder)
 {
   MOZ_ASSERT(NS_IsMainThread());
   Decode(aDecoder);
 }
 
 already_AddRefed<nsIEventTarget>
-DecodePool::GetEventTarget()
-{
-  MutexAutoLock threadPoolLock(mMutex);
-  nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool);
-  return target.forget();
-}
-
-already_AddRefed<nsIEventTarget>
 DecodePool::GetIOEventTarget()
 {
   MutexAutoLock threadPoolLock(mMutex);
   nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mIOThread);
   return target.forget();
 }
 
-already_AddRefed<nsIRunnable>
-DecodePool::CreateDecodeWorker(Decoder* aDecoder)
-{
-  MOZ_ASSERT(aDecoder);
-  nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aDecoder);
-  return worker.forget();
-}
-
 void
 DecodePool::Decode(Decoder* aDecoder)
 {
   MOZ_ASSERT(aDecoder);
 
   nsresult rv = aDecoder->Decode();
 
   if (NS_SUCCEEDED(rv) && !aDecoder->GetDecodeDone()) {
--- a/image/src/DecodePool.h
+++ b/image/src/DecodePool.h
@@ -7,27 +7,30 @@
  * DecodePool manages the threads used for decoding raster images.
  */
 
 #ifndef mozilla_image_src_DecodePool_h
 #define mozilla_image_src_DecodePool_h
 
 #include "mozilla/Mutex.h"
 #include "mozilla/StaticPtr.h"
+#include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "nsIEventTarget.h"
 #include "nsIObserver.h"
+#include "nsRefPtr.h"
 
 class nsIThread;
 class nsIThreadPool;
 
 namespace mozilla {
 namespace image {
 
 class Decoder;
+class DecodePoolImpl;
 
 /**
  * DecodePool is a singleton class that manages decoding of raster images. It
  * owns a pool of image decoding threads that are used for asynchronous
  * decoding.
  *
  * DecodePool allows callers to run a decoder, handling management of the
  * decoder's lifecycle and whether it executes on the main thread,
@@ -59,56 +62,40 @@ public:
   /**
    * Run aDecoder synchronously if at all possible. If it can't complete
    * synchronously because the source data isn't complete, asynchronously decode
    * the rest.
    */
   void SyncDecodeIfPossible(Decoder* aDecoder);
 
   /**
-   * Returns an event target interface to the DecodePool's underlying thread
-   * pool. Callers can use this event target to submit work to the image
-   * decoding thread pool.
-   *
-   * @return An nsIEventTarget interface to the thread pool.
-   */
-  already_AddRefed<nsIEventTarget> GetEventTarget();
-
-  /**
    * Returns an event target interface to the DecodePool's I/O thread. Callers
    * who want to deliver data to workers on the DecodePool can use this event
    * target.
    *
    * @return An nsIEventTarget interface to the thread pool's I/O thread.
    */
   already_AddRefed<nsIEventTarget> GetIOEventTarget();
 
-  /**
-   * Creates a worker which can be used to attempt further decoding using the
-   * provided decoder.
-   *
-   * @return The new worker, which should be posted to the event target returned
-   *         by GetEventTarget.
-   */
-  already_AddRefed<nsIRunnable> CreateDecodeWorker(Decoder* aDecoder);
-
 private:
-  friend class DecodeWorker;
+  friend class DecodePoolWorker;
 
   DecodePool();
   virtual ~DecodePool();
 
   void Decode(Decoder* aDecoder);
   void NotifyDecodeComplete(Decoder* aDecoder);
   void NotifyProgress(Decoder* aDecoder);
 
   static StaticRefPtr<DecodePool> sSingleton;
 
-  // mMutex protects mThreadPool and mIOThread.
+  nsRefPtr<DecodePoolImpl>    mImpl;
+
+  // mMutex protects mThreads and mIOThread.
   Mutex                     mMutex;
-  nsCOMPtr<nsIThreadPool>   mThreadPool;
+  nsCOMArray<nsIThread>     mThreads;
   nsCOMPtr<nsIThread>       mIOThread;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_src_DecodePool_h
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -161,25 +161,17 @@ Decoder::Decode()
   return HasError() ? NS_ERROR_FAILURE : NS_OK;
 }
 
 void
 Decoder::Resume()
 {
   DecodePool* decodePool = DecodePool::Singleton();
   MOZ_ASSERT(decodePool);
-
-  nsCOMPtr<nsIEventTarget> target = decodePool->GetEventTarget();
-  if (MOZ_UNLIKELY(!target)) {
-    // We're shutting down and the DecodePool's thread pool has been destroyed.
-    return;
-  }
-
-  nsCOMPtr<nsIRunnable> worker = decodePool->CreateDecodeWorker(this);
-  target->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL);
+  decodePool->AsyncDecode(this);
 }
 
 bool
 Decoder::ShouldSyncDecode(size_t aByteLimit)
 {
   MOZ_ASSERT(aByteLimit > 0);
   MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
 
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -942,16 +942,20 @@ RasterImage::OnAddedFrame(uint32_t aNewF
     NS_DispatchToMainThread(runnable);
     return;
   }
 
   MOZ_ASSERT((mFrameCount == 1 && aNewFrameCount == 1) ||
              mFrameCount < aNewFrameCount,
              "Frame count running backwards");
 
+  if (mError) {
+    return;  // We're in an error state, possibly due to OOM. Bail.
+  }
+
   if (aNewFrameCount > mFrameCount) {
     mFrameCount = aNewFrameCount;
 
     if (aNewFrameCount == 2) {
       // We're becoming animated, so initialize animation stuff.
       MOZ_ASSERT(!mAnim, "Already have animation state?");
       mAnim = MakeUnique<FrameAnimator>(this, mSize, mAnimationMode);
 
--- a/image/src/moz.build
+++ b/image/src/moz.build
@@ -63,12 +63,14 @@ LOCAL_INCLUDES += [
     # Access to Skia headers for Downscaler
     '/gfx/2d',
     # We need to instantiate the decoders
     '/image/decoders',
     # Because VectorImage.cpp includes nsSVGUtils.h and nsSVGEffects.h
     '/layout/svg',
     # For URI-related functionality
     '/netwerk/base',
+    # DecodePool uses thread-related facilities.
+    '/xpcom/threads',
 ]
 
 # Because imgFrame.cpp includes "cairo.h"
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
--- a/image/test/crashtests/crashtests.list
+++ b/image/test/crashtests/crashtests.list
@@ -42,8 +42,13 @@ load truncated-second-frame.png # bug 86
 # after we've gotten our size.
 load multiple-png-hassize.ico
 
 # Bug 856615
 # Asserts in the debug build
 load 856616.gif
 
 skip-if(AddressSanitizer) skip-if(B2G) load 944353.jpg
+
+# Bug 1160801: Ensure that we handle invalid disposal types.
+load invalid-disposal-method-1.gif
+load invalid-disposal-method-2.gif
+load invalid-disposal-method-3.gif
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..30c61de188b9c63ea179c008c5171efad638f853
GIT binary patch
literal 167
zc${<hbhEHb)L_tH_{hW{sr6rbBl~|aQ2fav!o|SAp!iSFxhOTUBsE2$JhLQ2!QIn0
zfI$Z+!2r_4z@*;Ozw-23{>5{)-0I$ZZ_jW3wnrY*o^>vJb!yu??&F_)u7B%&{_C7F
SD+AC#29SZsdRvIpYYhO5fk;^Z
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..66158d81a90b670d28b258a22f87f037542122f0
GIT binary patch
literal 167
zc${<hbhEHb)L_tH_{hW{sr6rbBl~|aQ2fav!NtJ9p!iSFxhOTUBsE2$JhLQ2!QIn0
zfI$Z+!2r_4z@*;Ozw-23{>5{)-0I$ZZ_jW3wnrY*o^>vJb!yu??&F_)u7B%&{_C7F
SD+AC#29SZsdRvIpYYhO7tw>z}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0da0723773e1b6c4f3d4e9df6d47ebe753f6c087
GIT binary patch
literal 167
zc${<hbhEHb)L_tH_{hW{sr6rbBl~|aQ2fav!^ObBp!iSFxhOTUBsE2$JhLQ2!QIn0
zfI$Z+!2r_4z@*;Ozw-23{>5{)-0I$ZZ_jW3wnrY*o^>vJb!yu??&F_)u7B%&{_C7F
SD+AC#29SZsdRvIpYYhO9*+^jk
--- a/js/src/builtin/Map.js
+++ b/js/src/builtin/Map.js
@@ -27,8 +27,14 @@ function MapForEach(callbackfn, thisArg 
     while (true) {
         var result = callFunction(std_Map_iterator_next, entries);
         if (result.done)
             break;
         var entry = result.value;
         callFunction(callbackfn, thisArg, entry[1], entry[0], M);
     }
 }
+
+// ES6 final draft 23.1.2.2.
+function MapSpecies() {
+    // Step 1.
+    return this;
+}
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -1045,42 +1045,50 @@ const JSFunctionSpec MapObject::methods[
     JS_FN("delete", delete_, 1, 0),
     JS_FN("keys", keys, 0, 0),
     JS_FN("values", values, 0, 0),
     JS_FN("clear", clear, 0, 0),
     JS_SELF_HOSTED_FN("forEach", "MapForEach", 2, 0),
     JS_FS_END
 };
 
+const JSPropertySpec MapObject::staticProperties[] = {
+    JS_SELF_HOSTED_SYM_GET(species, "MapSpecies", 0),
+    JS_PS_END
+};
+
 static JSObject*
 InitClass(JSContext* cx, Handle<GlobalObject*> global, const Class* clasp, JSProtoKey key, Native construct,
-          const JSPropertySpec* properties, const JSFunctionSpec* methods)
+          const JSPropertySpec* properties, const JSFunctionSpec* methods,
+          const JSPropertySpec* staticProperties)
 {
     RootedNativeObject proto(cx, global->createBlankPrototype(cx, clasp));
     if (!proto)
         return nullptr;
     proto->setPrivate(nullptr);
 
     Rooted<JSFunction*> ctor(cx, global->createConstructor(cx, construct, ClassName(key, cx), 0));
     if (!ctor ||
+        !JS_DefineProperties(cx, ctor, staticProperties) ||
         !LinkConstructorAndPrototype(cx, ctor, proto) ||
         !DefinePropertiesAndFunctions(cx, proto, properties, methods) ||
         !GlobalObject::initBuiltinConstructor(cx, global, key, ctor, proto))
     {
         return nullptr;
     }
     return proto;
 }
 
 JSObject*
 MapObject::initClass(JSContext* cx, JSObject* obj)
 {
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
     RootedObject proto(cx,
-        InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods));
+        InitClass(cx, global, &class_, JSProto_Map, construct, properties, methods,
+                  staticProperties));
     if (proto) {
         // Define the "entries" method.
         JSFunction* fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0);
         if (!fun)
             return nullptr;
 
         // Define its alias.
         RootedValue funval(cx, ObjectValue(*fun));
@@ -1777,22 +1785,28 @@ const JSFunctionSpec SetObject::methods[
     JS_FN("add", add, 1, 0),
     JS_FN("delete", delete_, 1, 0),
     JS_FN("entries", entries, 0, 0),
     JS_FN("clear", clear, 0, 0),
     JS_SELF_HOSTED_FN("forEach", "SetForEach", 2, 0),
     JS_FS_END
 };
 
+const JSPropertySpec SetObject::staticProperties[] = {
+    JS_SELF_HOSTED_SYM_GET(species, "SetSpecies", 0),
+    JS_PS_END
+};
+
 JSObject*
 SetObject::initClass(JSContext* cx, JSObject* obj)
 {
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
     RootedObject proto(cx,
-        InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods));
+        InitClass(cx, global, &class_, JSProto_Set, construct, properties, methods,
+                  staticProperties));
     if (proto) {
         // Define the "values" method.
         JSFunction* fun = JS_DefineFunction(cx, proto, "values", values, 0, 0);
         if (!fun)
             return nullptr;
 
         // Define its aliases.
         RootedValue funval(cx, ObjectValue(*fun));
--- a/js/src/builtin/MapObject.h
+++ b/js/src/builtin/MapObject.h
@@ -103,16 +103,17 @@ class MapObject : public NativeObject {
     static bool has(JSContext* cx, HandleObject obj, HandleValue key, bool* rval);
     static bool set(JSContext* cx, HandleObject obj, HandleValue key, HandleValue val);
     static bool clear(JSContext* cx, HandleObject obj);
     static bool iterator(JSContext* cx, IteratorKind kind, HandleObject obj, MutableHandleValue iter);
 
   private:
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
+    static const JSPropertySpec staticProperties[];
     ValueMap* getData() { return static_cast<ValueMap*>(getPrivate()); }
     static ValueMap & extract(HandleObject o);
     static ValueMap & extract(CallReceiver call);
     static void mark(JSTracer* trc, JSObject* obj);
     static void finalize(FreeOp* fop, JSObject* obj);
     static bool construct(JSContext* cx, unsigned argc, Value* vp);
 
     static bool is(HandleValue v);
@@ -148,16 +149,17 @@ class SetObject : public NativeObject {
     static bool values(JSContext* cx, unsigned argc, Value* vp);
     static bool add(JSContext* cx, HandleObject obj, HandleValue key);
     static bool has(JSContext* cx, unsigned argc, Value* vp);
     static SetObject* create(JSContext* cx);
 
   private:
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
+    static const JSPropertySpec staticProperties[];
     ValueSet* getData() { return static_cast<ValueSet*>(getPrivate()); }
     static ValueSet & extract(CallReceiver call);
     static void mark(JSTracer* trc, JSObject* obj);
     static void finalize(FreeOp* fop, JSObject* obj);
     static bool construct(JSContext* cx, unsigned argc, Value* vp);
 
     static bool is(HandleValue v);
 
--- a/js/src/builtin/Set.js
+++ b/js/src/builtin/Set.js
@@ -27,8 +27,14 @@ function SetForEach(callbackfn, thisArg 
     while (true) {
         var result = callFunction(std_Set_iterator_next, values);
         if (result.done)
             break;
         var value = result.value;
         callFunction(callbackfn, thisArg, value, value, S);
     }
 }
+
+// ES6 final draft 23.2.2.2.
+function SetSpecies() {
+    // Step 1.
+    return this;
+}
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2098,16 +2098,21 @@ inline int CheckIsSetterOp(JSSetterOp op
      { { nullptr, JS_CAST_STRING_TO(getterName, const JSJitInfo*) } }, \
      JSNATIVE_WRAPPER(nullptr) }
 #define JS_SELF_HOSTED_GETSET(name, getterName, setterName, flags) \
     {name, \
      uint8_t(JS_CHECK_ACCESSOR_FLAGS(flags) | JSPROP_SHARED | JSPROP_GETTER | JSPROP_SETTER), \
      { nullptr, JS_CAST_STRING_TO(getterName, const JSJitInfo*) },  \
      { nullptr, JS_CAST_STRING_TO(setterName, const JSJitInfo*) } }
 #define JS_PS_END { nullptr, 0, JSNATIVE_WRAPPER(nullptr), JSNATIVE_WRAPPER(nullptr) }
+#define JS_SELF_HOSTED_SYM_GET(symbol, getterName, flags) \
+    {reinterpret_cast<const char*>(uint32_t(::JS::SymbolCode::symbol) + 1), \
+     uint8_t(JS_CHECK_ACCESSOR_FLAGS(flags) | JSPROP_SHARED | JSPROP_GETTER), \
+     { { nullptr, JS_CAST_STRING_TO(getterName, const JSJitInfo*) } }, \
+     JSNATIVE_WRAPPER(nullptr) }
 
 /*
  * To define a native function, set call to a JSNativeWrapper. To define a
  * self-hosted function, set selfHostedName to the name of a function
  * compiled during JSRuntime::initSelfHosting.
  */
 struct JSFunctionSpec {
     const char*     name;
@@ -4350,22 +4355,23 @@ GetSymbolFor(JSContext* cx, HandleString
  */
 JS_PUBLIC_API(JSString*)
 GetSymbolDescription(HandleSymbol symbol);
 
 /* Well-known symbols. */
 enum class SymbolCode : uint32_t {
     iterator,                       // Symbol.iterator
     match,                          // Symbol.match
+    species,                        // Symbol.species
     InSymbolRegistry = 0xfffffffe,  // created by Symbol.for() or JS::GetSymbolFor()
     UniqueSymbol = 0xffffffff       // created by Symbol() or JS::NewSymbol()
 };
 
 /* For use in loops that iterate over the well-known symbols. */
-const size_t WellKnownSymbolLimit = 2;
+const size_t WellKnownSymbolLimit = 3;
 
 /*
  * Return the SymbolCode telling what sort of symbol `symbol` is.
  *
  * A symbol's SymbolCode never changes once it is created.
  */
 JS_PUBLIC_API(SymbolCode)
 GetSymbolCode(Handle<Symbol*> symbol);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Symbol/species.js
@@ -0,0 +1,22 @@
+var BUGNUMBER = 1131043;
+var summary = "Implement @@species getter for builtin types";
+
+print(BUGNUMBER + ": " + summary);
+
+for (var C of [Map, Set]) {
+  assertEq(C[Symbol.species], C);
+}
+
+for (C of [Map, Set]) {
+  var desc = Object.getOwnPropertyDescriptor(C, Symbol.species);
+  assertDeepEq(Object.keys(desc).sort(), ["configurable", "enumerable", "get", "set"]);
+  assertEq(desc.set, undefined);
+  assertEq(desc.enumerable, false);
+  assertEq(desc.configurable, true);
+  assertEq(desc.get.apply(null), null);
+  assertEq(desc.get.apply(undefined), undefined);
+  assertEq(desc.get.apply(42), 42);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
--- a/js/src/tests/ecma_6/Symbol/well-known.js
+++ b/js/src/tests/ecma_6/Symbol/well-known.js
@@ -1,14 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/licenses/publicdomain/ */
 
 var names = [
     "iterator",
     "match",
+    "species",
 ];
 
 for (var name of names) {
     // Well-known symbols exist.
     assertEq(typeof Symbol[name], "symbol");
 
     // They are never in the registry.
     assertEq(Symbol[name] !== Symbol.for("Symbol." + name), true);
--- a/js/src/tests/ecma_6/TypedArray/slice.js
+++ b/js/src/tests/ecma_6/TypedArray/slice.js
@@ -63,15 +63,15 @@ for (var constructor of constructors) {
     }, TypeError, "Assert that we have an invalid constructor");
 
     // If obj.constructor[@@species] is undefined or null -- which it has to be
     // if we don't implement @@species -- then the default constructor is used.
     var mathConstructor = new constructor(8);
     mathConstructor.constructor = Math.sin;
     assertDeepEq(mathConstructor.slice(4), new constructor(4));
 
-    assertEq("species" in Symbol, false,
-             "you've implemented @@species -- add real tests here!");
+    assertEq(Symbol.species in Int8Array, false,
+             "you've implemented %TypedArray%[@@species] -- add real tests here!");
 }
 
 if (typeof reportCompare === "function")
     reportCompare(true, true);
 
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -243,20 +243,22 @@
     macro(number, number, "number") \
     macro(boolean, boolean, "boolean") \
     macro(null, null, "null") \
     macro(symbol, symbol, "symbol") \
     /* Well-known atom names must be continuous and ordered, matching \
      * enum JS::SymbolCode in jsapi.h. */ \
     macro(iterator, iterator, "iterator") \
     macro(match, match, "match") \
+    macro(species, species, "species") \
     /* Same goes for the descriptions of the well-known symbols. */ \
     macro(Symbol_create, Symbol_create, "Symbol.create") \
     macro(Symbol_hasInstance, Symbol_hasInstance, "Symbol.hasInstance") \
     macro(Symbol_isConcatSpreadable, Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") \
     macro(Symbol_isRegExp, Symbol_isRegExp, "Symbol.isRegExp") \
     macro(Symbol_iterator, Symbol_iterator, "Symbol.iterator") \
     macro(Symbol_match,    Symbol_match,    "Symbol.match") \
+    macro(Symbol_species,  Symbol_species,  "Symbol.species") \
     macro(Symbol_toPrimitive, Symbol_toPrimitive, "Symbol.toPrimitive") \
     macro(Symbol_toStringTag, Symbol_toStringTag, "Symbol.toStringTag") \
     macro(Symbol_unscopables, Symbol_unscopables, "Symbol.unscopables") \
 
 #endif /* vm_CommonPropertyNames_h */
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -386,16 +386,24 @@ GlobalObject::initSelfHostingBuiltins(JS
     RootedValue std_iterator(cx);
     std_iterator.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::iterator));
     if (!JS_DefineProperty(cx, global, "std_iterator", std_iterator,
                            JSPROP_PERMANENT | JSPROP_READONLY))
     {
         return false;
     }
 
+    RootedValue std_species(cx);
+    std_species.setSymbol(cx->wellKnownSymbols().get(JS::SymbolCode::species));
+    if (!JS_DefineProperty(cx, global, "std_species", std_species,
+                           JSPROP_PERMANENT | JSPROP_READONLY))
+    {
+        return false;
+    }
+
     return InitBareBuiltinCtor(cx, global, JSProto_Array) &&
            InitBareBuiltinCtor(cx, global, JSProto_TypedArray) &&
            InitBareBuiltinCtor(cx, global, JSProto_Uint8Array) &&
            InitBareBuiltinCtor(cx, global, JSProto_Uint32Array) &&
            InitBareWeakMapCtor(cx, global) &&
            initStopIterationClass(cx, global) &&
            InitSelfHostingCollectionIteratorFunctions(cx, global) &&
            JS_DefineFunctions(cx, global, builtins);
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -425,16 +425,17 @@ namespace js {
  *
  * Well-known symbols are never GC'd. The description() of each well-known
  * symbol is a permanent atom.
  */
 struct WellKnownSymbols
 {
     js::ImmutableSymbolPtr iterator;
     js::ImmutableSymbolPtr match;
+    js::ImmutableSymbolPtr species;
 
     const ImmutableSymbolPtr& get(size_t u) const {
         MOZ_ASSERT(u < JS::WellKnownSymbolLimit);
         const ImmutableSymbolPtr* symbols = reinterpret_cast<const ImmutableSymbolPtr*>(this);
         return symbols[u];
     }
 
     const ImmutableSymbolPtr& get(JS::SymbolCode code) const {
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -8051,42 +8051,35 @@ PresShell::HandleEventInternal(WidgetEve
     // XXX How about IME events and input events for plugins?
     if (aEvent->mFlags.mIsTrusted) {
       switch (aEvent->message) {
       case NS_KEY_PRESS:
       case NS_KEY_DOWN:
       case NS_KEY_UP: {
         nsIDocument* doc = GetCurrentEventContent() ?
                            mCurrentEventContent->OwnerDoc() : nullptr;
-        nsIDocument* fullscreenAncestor = nullptr;
         auto keyCode = aEvent->AsKeyboardEvent()->keyCode;
         if (keyCode == NS_VK_ESCAPE) {
-          if ((fullscreenAncestor = nsContentUtils::GetFullscreenAncestor(doc))) {
+          nsIDocument* root = nsContentUtils::GetRootDocument(doc);
+          if (root && root->IsFullScreenDoc()) {
             // Prevent default action on ESC key press when exiting
             // DOM fullscreen mode. This prevents the browser ESC key
             // handler from stopping all loads in the document, which
             // would cause <video> loads to stop.
             aEvent->mFlags.mDefaultPrevented = true;
             aEvent->mFlags.mOnlyChromeDispatch = true;
 
             // The event listeners in chrome can prevent this ESC behavior by
             // calling prevent default on the preceding keydown/press events.
             if (!mIsLastChromeOnlyEscapeKeyConsumed &&
                 aEvent->message == NS_KEY_UP) {
               // ESC key released while in DOM fullscreen mode.
-              // If fullscreen is running in content-only mode, exit the target
-              // doctree branch from fullscreen, otherwise fully exit all
-              // browser windows and documents from fullscreen mode.
-              // Note: in the content-only fullscreen case, we pass the
-              // fullscreenAncestor since |doc| may not actually be fullscreen
-              // here, and ExitFullscreen() has no affect when passed a
-              // non-fullscreen document.
-              nsIDocument::ExitFullscreen(
-                nsContentUtils::IsFullscreenApiContentOnly() ? fullscreenAncestor : nullptr,
-                /* async */ true);
+              // Fully exit all browser windows and documents from
+              // fullscreen mode.
+              nsIDocument::ExitFullscreen(nullptr, /* async */ true);
             }
           }
           nsCOMPtr<nsIDocument> pointerLockedDoc =
             do_QueryReferent(EventStateManager::sPointerLockedDoc);
           if (!mIsLastChromeOnlyEscapeKeyConsumed && pointerLockedDoc) {
             aEvent->mFlags.mDefaultPrevented = true;
             aEvent->mFlags.mOnlyChromeDispatch = true;
             if (aEvent->message == NS_KEY_UP) {
--- a/layout/generic/WritingModes.h
+++ b/layout/generic/WritingModes.h
@@ -25,16 +25,20 @@
 // such methods should only be used by other methods that have already checked
 // the writing modes.)
 
 #define CHECK_WRITING_MODE(param) \
    NS_ASSERTION(param == GetWritingMode(), "writing-mode mismatch")
 
 namespace mozilla {
 
+namespace widget {
+struct IMENotification;
+} // namespace widget
+
 // Physical axis constants.
 enum PhysicalAxis {
   eAxisVertical      = 0x0,
   eAxisHorizontal    = 0x1
 };
 
 inline bool IsInline(LogicalSide aSide) { return aSide & 0x2; }
 inline bool IsBlock(LogicalSide aSide) { return !IsInline(aSide); }
@@ -470,16 +474,20 @@ public:
 
 private:
   friend class LogicalPoint;
   friend class LogicalSize;
   friend class LogicalMargin;
   friend class LogicalRect;
 
   friend struct IPC::ParamTraits<WritingMode>;
+  // IMENotification cannot store this class directly since this has some
+  // constructors.  Therefore, it stores mWritingMode and recreate the
+  // instance from it.
+  friend struct widget::IMENotification;
 
   /**
    * Return a WritingMode representing an unknown value.
    */
   static inline WritingMode Unknown()
   {
     return WritingMode(eUnknownWritingMode);
   }
--- a/layout/generic/test/chrome.ini
+++ b/layout/generic/test/chrome.ini
@@ -2,16 +2,17 @@
 skip-if = buildapp == 'b2g'
 support-files =
   file_bug514732_window.xul
   frame_selection_underline-ref.xhtml
   frame_selection_underline.css
   frame_selection_underline.xhtml
 
 [test_backspace_delete.xul]
+skip-if = true # Bug 1163311
 [test_bug348681.html]
 [test_bug469613.xul]
 [test_bug469774.xul]
 [test_bug508115.xul]
 [test_bug514732-2.xul]
 [test_bug632379.xul]
 skip-if = buildapp == 'mulet'
 [test_selection_preventDefault.html]
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3064,16 +3064,44 @@ pref("intl.tsf.hack.easy_changjei.do_not
 // of selected clause of composition string.
 pref("intl.tsf.hack.google_ja_input.do_not_return_no_layout_error_at_first_char", true);
 // Whether use previous character rect for the result of
 // ITfContextView::GetTextExt() if the specified range is the caret of
 // composition string.
 pref("intl.tsf.hack.google_ja_input.do_not_return_no_layout_error_at_caret", true);
 #endif
 
+// If composition_font is set, Gecko sets the font to IME.  IME may use
+// the fonts on their window like candidate window.  If they are empty,
+// Gecko uses the system default font which is set to the IM context.
+// The font name must not start with '@'.  When the writing mode is vertical,
+// Gecko inserts '@' to the start of the font name automatically.
+// FYI: Changing these prefs requires to restart.
+pref("intl.imm.composition_font", "");
+
+// Japanist 2003's candidate window is broken if the font is "@System" which
+// is default composition font for vertical writing mode.
+// You can specify font to use on candidate window of Japanist 2003.
+// This value must not start with '@'.
+// FYI: Changing this pref requires to restart.
+pref("intl.imm.composition_font.japanist_2003", "MS PGothic");
+
+// Even if IME claims that they support vertical writing mode but it may not
+// support vertical writing mode for its candidate window.  This pref allows
+// to ignore the claim.
+// FYI: Changing this pref requires to restart.
+pref("intl.imm.vertical_writing.always_assume_not_supported", false);
+
+// We cannot retrieve active IME name with IMM32 API if a TIP of TSF is active.
+// This pref can specify active IME name when Japanese TIP is active.
+// For example:
+//   Google Japanese Input: "Google 日本語入力 IMM32 モジュール"
+//   ATOK 2011: "ATOK 2011" (similarly, e.g., ATOK 2013 is "ATOK 2013")
+pref("intl.imm.japanese.assume_active_tip_name_as", "");
+
 // See bug 448927, on topmost panel, some IMEs are not usable on Windows.
 pref("ui.panel.default_level_parent", false);
 
 pref("mousewheel.system_scroll_override_on_root_content.enabled", true);
 
 // High resolution scrolling with supported mouse drivers on Vista or later.
 pref("mousewheel.enable_pixel_scrolling", true);
 
@@ -4163,17 +4191,16 @@ pref("notification.feature.enabled", fal
 pref("dom.webnotifications.enabled", true);
 
 // Alert animation effect, name is disableSlidingEffect for backwards-compat.
 pref("alerts.disableSlidingEffect", false);
 
 // DOM full-screen API.
 pref("full-screen-api.enabled", false);
 pref("full-screen-api.allow-trusted-requests-only", true);
-pref("full-screen-api.content-only", false);
 pref("full-screen-api.pointer-lock.enabled", true);
 
 // DOM idle observers API
 pref("dom.idle-observers-api.enabled", true);
 
 // Time limit, in milliseconds, for EventStateManager::IsHandlingUserInput().
 // Used to detect long running handlers of user-generated events.
 pref("dom.event.handling-user-input-time-limit", 1000);
--- a/python/eme/gen-eme-voucher.py
+++ b/python/eme/gen-eme-voucher.py
@@ -2,39 +2,46 @@
 #
 # Copyright 2014 Adobe Systems Incorporated. All Rights Reserved.
 #
 # Adobe permits you to use, modify, and distribute this file in accordance
 # with the terms of the Mozilla Public License, v 2.0 accompanying it.  If
 # a copy of the MPL was not distributed with this file, You can obtain one
 # at http://mozilla.org/MPL/2.0/.
 #
-# Creates an Adobe Access signed voucher for any executable
+# Creates an Adobe Access signed voucher for x32/x64 windows executables
 #   Notes: This is currently python2.7 due to mozilla build system requirements
 
+from __future__ import print_function
+
 import argparse, bitstring, pprint, hashlib, os, subprocess, sys, tempfile
 from pyasn1.codec.der import encoder as der_encoder
 from pyasn1.type import univ, namedtype, namedval, constraint
 
+# Defined in WinNT.h from the Windows SDK
+IMAGE_SCN_MEM_EXECUTE = 0x20000000
+IMAGE_REL_BASED_HIGHLOW = 3
+IMAGE_REL_BASED_DIR64 = 10
+
 
 # CodeSectionDigest ::= SEQUENCE {
 #   offset				INTEGER --  section's file offset in the signed binary
 #   digestAlgorithm		OBJECT IDENTIFIER -- algorithm identifier for the hash value below. For now only supports SHA256.
 #   digestValue			OCTET STRING -- hash value of the TEXT segment.
 # }
 class CodeSectionDigest(univ.Sequence):
 	componentType = namedtype.NamedTypes(
 		namedtype.NamedType('offset', univ.Integer()),
 		namedtype.NamedType('digestAlgorithm', univ.ObjectIdentifier()),
 		namedtype.NamedType('digest', univ.OctetString()))
 
 
 # CodeSegmentDigest ::= SEQUENCE {
-#	 offset				INTEGER -- TEXT segment's file offset in the signed binary
-#	 codeSectionDigests			SET OF CodeSectionDigests
+#    offset				INTEGER -- TEXT segment's file offset in the signed binary
+#    codeSectionDigests			SET OF CodeSectionDigests
 # }
 
 class SetOfCodeSectionDigest(univ.SetOf):
 	componentType = CodeSectionDigest()
 
 
 class CodeSegmentDigest(univ.Sequence):
 	componentType = namedtype.NamedTypes(
@@ -94,16 +101,17 @@ class ApplicationDigest(univ.Sequence):
 def meets_requirements(items, requirements):
 	for r in requirements:
 		for n, v in r.items():
 			if n not in items or items[n] != v: return False
 	return True
 
 
 # return total number of bytes read from items_in excluding leaves
+# TODO: research replacing this with the python built-in struct module
 def parse_items(stream, items_in, items_out):
 	bits_read = 0
 	total_bits_read = 0
 
 	for item in items_in:
 		name = item[0]
 		t = item[1]
 		bits = 1 if ":" not in t else int(t[t.index(":") + 1:])
@@ -310,27 +318,28 @@ class COFFFileHeader:
 
 			for i in range(0, int((block_size - 8) / 2)):
 				data = stream.read('uintle:16')
 				typ = data >> 12
 				offset = data & 0xFFF
 
 				if offset == 0 and i > 0: continue
 
-				assert(typ == 3)
+				assert(typ == IMAGE_REL_BASED_HIGHLOW or typ == IMAGE_REL_BASED_DIR64)
 
 				cur_pos = stream.bitpos
 				sh, value_bytepos = self.get_rva_section(page_rva + offset)
 				stream.bytepos = value_bytepos
-				value = stream.read('uintle:32')
+				value = stream.read('uintle:32' if typ == IMAGE_REL_BASED_HIGHLOW else 'uintle:64')
 
 				# remove BaseAddress
 				value -= self.OptionalHeader.items['ImageBase']
 
-				stream.overwrite(bitstring.BitArray(uint=value, length=4 * 8), pos=value_bytepos * 8)
+				bit_size = (4 if typ == IMAGE_REL_BASED_HIGHLOW else 8) * 8
+				stream.overwrite(bitstring.BitArray(uint=value, length=bit_size), pos=value_bytepos * 8)
 				stream.pos = cur_pos
 
 		stream.bitpos = orig_pos
 
 	def get_rva_section(self, rva):
 		for sh in self.section_headers:
 			if rva < sh.items['VirtualAddress'] or rva >= sh.items['VirtualAddress'] + sh.items['VirtualSize']:
 				continue
@@ -340,25 +349,66 @@ class COFFFileHeader:
 
 		raise Exception('Could not match RVA to section')
 
 
 def create_temp_file(suffix=""):
 	fd, path = tempfile.mkstemp(suffix=suffix)
 	os.close(fd)
 	return path
-	
-# TIPS:
-#  How to convert PFX to PEM: openssl pkcs12 -in build/certificates/testPKI/IV.pfx -out build/certificates/testPKI/IV.cert.pem
+
+
+class ExpandPath(argparse.Action):
+	def __call__(self, parser, namespace, values, option_string=None):
+		setattr(namespace, self.dest, os.path.abspath(os.path.expanduser(values)))
+
+
+# this does a naming trick since windows doesn't allow multiple usernames for the same server
+def get_password(service_name, user_name):
+	try:
+		import keyring
+
+		# windows doesn't allow multiple usernames for the same server, argh
+		if sys.platform == "win32":
+			password = keyring.get_password(service_name + "-" + user_name, user_name)
+		else:
+			password = keyring.get_password(service_name, user_name)
+
+		return password
+	except:
+	    # This allows for manual testing where you do not wish to cache the password on the system 
+		print("Missing keyring module...getting password manually")
+
+	return None
+
+
+def openssl_cmd(app_args, args, password_in, password_out):
+	password = get_password(app_args.password_service, app_args.password_user) if (password_in or password_out) else None
+	env = None
+	args = [app_args.openssl_path] + args
+
+	if password is not None:
+		env = os.environ.copy()
+		env["COFF_PW"] = password
+
+		if password_in: args += ["-passin", "env:COFF_PW"]
+		if password_out: args += ["-passout", "env:COFF_PW", "-password", "env:COFF_PW"]
+
+	p = subprocess.Popen(args, env=env)
+	assert p.wait() == 0
+
+
 def main():
 	parser = argparse.ArgumentParser(description='PE/COFF Signer')
-	parser.add_argument('-input', required=True, help="File to parse.")
-	parser.add_argument('-output', required=True, help="File to write to.")
-	parser.add_argument('-openssl_path',help="Path to OpenSSL to create signed voucher")
-	parser.add_argument('-signer_cert',help="Path to certificate to use to sign voucher.  Must be PEM encoded.")
+	parser.add_argument('-input', action=ExpandPath, required=True, help="File to parse.")
+	parser.add_argument('-output', action=ExpandPath, required=True, help="File to write to.")
+	parser.add_argument('-openssl_path', action=ExpandPath, help="Path to OpenSSL to create signed voucher")
+	parser.add_argument('-signer_pfx', action=ExpandPath, help="Path to certificate to use to sign voucher.  Must contain full certificate chain.")
+	parser.add_argument('-password_service', help="Name of Keyring/Wallet service/host")
+	parser.add_argument('-password_user', help="Name of Keyring/Wallet user name")
 	parser.add_argument('-verbose', action='store_true', help="Verbose output.")
 	app_args = parser.parse_args()
 
 	# to simplify relocation handling we use a mutable BitStream so we can remove
 	# the BaseAddress from each relocation
 	stream = bitstring.BitStream(filename=app_args.input)
 
 	# find the COFF header.
@@ -380,17 +430,17 @@ def main():
 	arch_digest = ArchitectureDigest()
 	if coff_header.items['Machine'] == 0x14c:
 		arch_digest.setComponentByName('cpuType', CPUType('IMAGE_FILE_MACHINE_I386'))
 	elif coff_header.items['Machine'] == 0x8664:
 		arch_digest.setComponentByName('cpuType', CPUType('IMAGE_FILE_MACHINE_AMD64'))
 
 	arch_digest.setComponentByName('cpuSubType', CPUSubType('IMAGE_UNUSED'))
 
-	text_section_headers = list(filter(lambda x: (x.items['Characteristics'] & 0x20000000) == 0x20000000, coff_header.section_headers))
+	text_section_headers = list(filter(lambda x: (x.items['Characteristics'] & IMAGE_SCN_MEM_EXECUTE) == IMAGE_SCN_MEM_EXECUTE, coff_header.section_headers))
 
 	code_segment_digests = SetOfCodeSegmentDigest()
 	code_segment_idx = 0
 	for code_sect_header in text_section_headers:
 		stream.bytepos = code_sect_header.offset
 		code_sect_bytes = stream.read('bytes:' + str(code_sect_header.items['VirtualSize']))
 
 		digester = hashlib.sha256()
@@ -427,28 +477,43 @@ def main():
 
 	binaryDigest = der_encoder.encode(appDigest)
 
 	with open(app_args.output, 'wb') as f:
 		f.write(binaryDigest)
 
 	# sign with openssl if specified
 	if app_args.openssl_path is not None:
-		assert app_args.signer_cert is not None
-		
+		assert app_args.signer_pfx is not None
+
 		out_base, out_ext = os.path.splitext(app_args.output)
 		signed_path = out_base + ".signed" + out_ext
-		
+
 		# http://stackoverflow.com/questions/12507277/how-to-fix-unable-to-write-random-state-in-openssl
-		temp_file = None
+		temp_files = []
 		if sys.platform == "win32" and "RANDFILE" not in os.environ:
 			temp_file = create_temp_file()
+			temp_files += [temp_file]
 			os.environ["RANDFILE"] = temp_file
-			
+
 		try:
-			subprocess.check_call([app_args.openssl_path, "cms", "-sign", "-nodetach", "-md", "sha256", "-binary", "-in", app_args.output, "-outform", "der", "-out", signed_path, "-signer", app_args.signer_cert], )
+			# create PEM from PFX
+			pfx_pem_path = create_temp_file(".pem")
+			temp_files += [pfx_pem_path]
+			print("Extracting PEM from PFX to:" + pfx_pem_path)
+			openssl_cmd(app_args, ["pkcs12", "-in", app_args.signer_pfx, "-out", pfx_pem_path], True, True)
+
+			# extract CA certs
+			pfx_cert_path = create_temp_file(".cert")
+			temp_files += [pfx_cert_path]
+			print("Extracting cert from PFX to:" + pfx_cert_path)
+			openssl_cmd(app_args, ["pkcs12", "-in", app_args.signer_pfx, "-cacerts", "-nokeys", "-out", pfx_cert_path], True, False)
+
+			# we embed the public keychain for client validation
+			openssl_cmd(app_args, ["cms", "-sign", "-nodetach", "-md", "sha256", "-binary", "-in", app_args.output, "-outform", "der", "-out", signed_path, "-signer", pfx_pem_path, "-certfile", pfx_cert_path], True, False)
 		finally:
-			if temp_file is not None: 
-				del os.environ["RANDFILE"]
-				os.unlink(temp_file)
+			for t in temp_files:
+				if "RANDFILE" in os.environ and t == os.environ["RANDFILE"]:
+					del os.environ["RANDFILE"]
+				os.unlink(t)
 
 if __name__ == '__main__':
 	main()
--- a/security/apps/AppTrustDomain.cpp
+++ b/security/apps/AppTrustDomain.cpp
@@ -229,17 +229,17 @@ AppTrustDomain::DigestBuf(Input item,
                           DigestAlgorithm digestAlg,
                           /*out*/ uint8_t* digestBuf,
                           size_t digestBufLen)
 {
   return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen);
 }
 
 Result
-AppTrustDomain::CheckRevocation(EndEntityOrCA, const CertID&, Time,
+AppTrustDomain::CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
                                 /*optional*/ const Input*,
                                 /*optional*/ const Input*)
 {
   // We don't currently do revocation checking. If we need to distrust an Apps
   // certificate, we will use the active distrust mechanism.
   return Success;
 }
 
--- a/security/apps/AppTrustDomain.h
+++ b/security/apps/AppTrustDomain.h
@@ -29,16 +29,17 @@ public:
                               /*out*/ mozilla::pkix::TrustLevel& trustLevel)
                               override;
   virtual Result FindIssuer(mozilla::pkix::Input encodedIssuerName,
                             IssuerChecker& checker,
                             mozilla::pkix::Time time) override;
   virtual Result CheckRevocation(mozilla::pkix::EndEntityOrCA endEntityOrCA,
                                  const mozilla::pkix::CertID& certID,
                                  mozilla::pkix::Time time,
+                                 mozilla::pkix::Duration validityDuration,
                     /*optional*/ const mozilla::pkix::Input* stapledOCSPresponse,
                     /*optional*/ const mozilla::pkix::Input* aiaExtension) override;
   virtual Result IsChainValid(const mozilla::pkix::DERArray& certChain,
                               mozilla::pkix::Time time) override;
   virtual Result CheckSignatureDigestAlgorithm(
                    mozilla::pkix::DigestAlgorithm digestAlg) override;
   virtual Result CheckRSAPublicKeyModulusSizeInBits(
                    mozilla::pkix::EndEntityOrCA endEntityOrCA,
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -27,20 +27,22 @@ PRLogModuleInfo* gCertVerifierLog = null
 namespace mozilla { namespace psm {
 
 const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1;
 const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2;
 
 CertVerifier::CertVerifier(OcspDownloadConfig odc,
                            OcspStrictConfig osc,
                            OcspGetConfig ogc,
+                           uint32_t certShortLifetimeInDays,
                            PinningMode pinningMode)
   : mOCSPDownloadEnabled(odc == ocspOn)
   , mOCSPStrict(osc == ocspStrict)
   , mOCSPGETEnabled(ogc == ocspGetEnabled)
+  , mCertShortLifetimeInDays(certShortLifetimeInDays)
   , mPinningMode(pinningMode)
 {
 }
 
 CertVerifier::~CertVerifier()
 {
 }
 
@@ -188,17 +190,19 @@ CertVerifier::VerifyCert(CERTCertificate
     stapledOCSPResponse = &stapledOCSPResponseInput;
   }
 
   switch (usage) {
     case certificateUsageSSLClient: {
       // XXX: We don't really have a trust bit for SSL client authentication so
       // just use trustEmail as it is the closest alternative.
       NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
-                                       pinArg, ocspGETConfig, pinningDisabled,
+                                       pinArg, ocspGETConfig,
+                                       mCertShortLifetimeInDays,
+                                       pinningDisabled,
                                        MIN_RSA_BITS_WEAK, nullptr, builtChain);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_clientAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
@@ -214,18 +218,19 @@ CertVerifier::VerifyCert(CERTCertificate
       SECOidTag evPolicyOidTag;
       SECStatus srv = GetFirstEVPolicy(cert, evPolicy, evPolicyOidTag);
       if (srv == SECSuccess) {
         NSSCertDBTrustDomain
           trustDomain(trustSSL,
                       ocspFetching == NSSCertDBTrustDomain::NeverFetchOCSP
                         ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
                         : NSSCertDBTrustDomain::FetchOCSPForEV,
-                      mOCSPCache, pinArg, ocspGETConfig, mPinningMode,
-                      MIN_RSA_BITS, hostname, builtChain);
+                      mOCSPCache, pinArg, ocspGETConfig,
+                      mCertShortLifetimeInDays, mPinningMode, MIN_RSA_BITS,
+                      hostname, builtChain);
         rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                           KeyUsage::digitalSignature,// (EC)DHE
                                           KeyUsage::keyEncipherment, // RSA
                                           KeyUsage::keyAgreement,    // (EC)DH
                                           KeyPurposeId::id_kp_serverAuth,
                                           evPolicy, stapledOCSPResponse,
                                           ocspStaplingStatus);
         if (rv == Success) {
@@ -239,17 +244,18 @@ CertVerifier::VerifyCert(CERTCertificate
 
       if (flags & FLAG_MUST_BE_EV) {
         rv = Result::ERROR_POLICY_VALIDATION_FAILED;
         break;
       }
 
       // Now try non-EV.
       NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
-                                       pinArg, ocspGETConfig, mPinningMode,
+                                       pinArg, ocspGETConfig,
+                                       mCertShortLifetimeInDays, mPinningMode,
                                        MIN_RSA_BITS, hostname, builtChain);
       rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                         KeyUsage::digitalSignature, // (EC)DHE
                                         KeyUsage::keyEncipherment, // RSA
                                         KeyUsage::keyAgreement, // (EC)DH
                                         KeyPurposeId::id_kp_serverAuth,
                                         CertPolicyId::anyPolicy,
                                         stapledOCSPResponse,
@@ -258,19 +264,20 @@ CertVerifier::VerifyCert(CERTCertificate
         if (keySizeStatus) {
           *keySizeStatus = KeySizeStatus::LargeMinimumSucceeded;
         }
         break;
       }
 
       // If that failed, try again with a smaller minimum key size.
       NSSCertDBTrustDomain trustDomainWeak(trustSSL, ocspFetching, mOCSPCache,
-                                           pinArg, ocspGETConfig, mPinningMode,
-                                           MIN_RSA_BITS_WEAK, hostname,
-                                           builtChain);
+                                           pinArg, ocspGETConfig,
+                                           mCertShortLifetimeInDays,
+                                           mPinningMode, MIN_RSA_BITS_WEAK,
+                                           hostname, builtChain);
       rv = BuildCertChainForOneKeyUsage(trustDomainWeak, certDER, time,
                                         KeyUsage::digitalSignature, // (EC)DHE
                                         KeyUsage::keyEncipherment, // RSA
                                         KeyUsage::keyAgreement, // (EC)DH
                                         KeyPurposeId::id_kp_serverAuth,
                                         CertPolicyId::anyPolicy,
                                         stapledOCSPResponse,
                                         ocspStaplingStatus);
@@ -282,29 +289,33 @@ CertVerifier::VerifyCert(CERTCertificate
         }
       }
 
       break;
     }
 
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
-                                       pinArg, ocspGETConfig, pinningDisabled,
-                                       MIN_RSA_BITS_WEAK, nullptr, builtChain);
+                                       pinArg, ocspGETConfig,
+                                       mCertShortLifetimeInDays,
+                                       pinningDisabled, MIN_RSA_BITS_WEAK,
+                                       nullptr, builtChain);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeCA, KeyUsage::keyCertSign,
                           KeyPurposeId::id_kp_serverAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
     case certificateUsageEmailSigner: {
       NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
-                                       pinArg, ocspGETConfig, pinningDisabled,
-                                       MIN_RSA_BITS_WEAK, nullptr, builtChain);
+                                       pinArg, ocspGETConfig,
+                                       mCertShortLifetimeInDays,
+                                       pinningDisabled, MIN_RSA_BITS_WEAK,
+                                       nullptr, builtChain);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_emailProtection,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
         rv = BuildCertChain(trustDomain, certDER, time,
                             EndEntityOrCA::MustBeEndEntity,
@@ -315,18 +326,20 @@ CertVerifier::VerifyCert(CERTCertificate
       break;
     }
 
     case certificateUsageEmailRecipient: {
       // TODO: The higher level S/MIME processing should pass in which key
       // usage it is trying to verify for, and base its algorithm choices
       // based on the result of the verification(s).
       NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
-                                       pinArg, ocspGETConfig, pinningDisabled,
-                                       MIN_RSA_BITS_WEAK, nullptr, builtChain);
+                                       pinArg, ocspGETConfig,
+                                       mCertShortLifetimeInDays,
+                                       pinningDisabled, MIN_RSA_BITS_WEAK,
+                                       nullptr, builtChain);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::keyEncipherment, // RSA
                           KeyPurposeId::id_kp_emailProtection,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
         rv = BuildCertChain(trustDomain, certDER, time,
                             EndEntityOrCA::MustBeEndEntity,
@@ -335,18 +348,19 @@ CertVerifier::VerifyCert(CERTCertificate
                             CertPolicyId::anyPolicy, stapledOCSPResponse);
       }
       break;
     }
 
     case certificateUsageObjectSigner: {
       NSSCertDBTrustDomain trustDomain(trustObjectSigning, ocspFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
-                                       pinningDisabled,
-                                       MIN_RSA_BITS_WEAK, nullptr, builtChain);
+                                       mCertShortLifetimeInDays,
+                                       pinningDisabled, MIN_RSA_BITS_WEAK,
+                                       nullptr, builtChain);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_codeSigning,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
@@ -364,32 +378,36 @@ CertVerifier::VerifyCert(CERTCertificate
         eku = KeyPurposeId::anyExtendedKeyUsage;
       } else {
         endEntityOrCA = EndEntityOrCA::MustBeEndEntity;
         keyUsage = KeyUsage::digitalSignature;
         eku = KeyPurposeId::id_kp_OCSPSigning;
       }
 
       NSSCertDBTrustDomain sslTrust(trustSSL, ocspFetching, mOCSPCache, pinArg,
-                                    ocspGETConfig, pinningDisabled,
-                                    MIN_RSA_BITS_WEAK, nullptr, builtChain);
+                                    ocspGETConfig, mCertShortLifetimeInDays,
+                                    pinningDisabled, MIN_RSA_BITS_WEAK,
+                                    nullptr, builtChain);
       rv = BuildCertChain(sslTrust, certDER, time, endEntityOrCA,
                           keyUsage, eku, CertPolicyId::anyPolicy,
                           stapledOCSPResponse);
       if (rv == Result::ERROR_UNKNOWN_ISSUER) {
         NSSCertDBTrustDomain emailTrust(trustEmail, ocspFetching, mOCSPCache,
-                                        pinArg, ocspGETConfig, pinningDisabled,
-                                        MIN_RSA_BITS_WEAK, nullptr, builtChain);
+                                        pinArg, ocspGETConfig,
+                                        mCertShortLifetimeInDays,
+                                        pinningDisabled, MIN_RSA_BITS_WEAK,
+                                        nullptr, builtChain);
         rv = BuildCertChain(emailTrust, certDER, time, endEntityOrCA,
                             keyUsage, eku, CertPolicyId::anyPolicy,
                             stapledOCSPResponse);
         if (rv == Result::ERROR_UNKNOWN_ISSUER) {
           NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
                                                   ocspFetching, mOCSPCache,
                                                   pinArg, ocspGETConfig,
+                                                  mCertShortLifetimeInDays,
                                                   pinningDisabled,
                                                   MIN_RSA_BITS_WEAK,
                                                   nullptr, builtChain);
           rv = BuildCertChain(objectSigningTrust, certDER, time,
                               endEntityOrCA, keyUsage, eku,
                               CertPolicyId::anyPolicy, stapledOCSPResponse);
         }
       }
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -77,24 +77,26 @@ public:
 
   enum OcspDownloadConfig { ocspOff = 0, ocspOn };
   enum OcspStrictConfig { ocspRelaxed = 0, ocspStrict };
   enum OcspGetConfig { ocspGetDisabled = 0, ocspGetEnabled = 1 };
 
   bool IsOCSPDownloadEnabled() const { return mOCSPDownloadEnabled; }
 
   CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
-               OcspGetConfig ogc, PinningMode pinningMode);
+               OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
+               PinningMode pinningMode);
   ~CertVerifier();
 
   void ClearOCSPCache() { mOCSPCache.Clear(); }
 
   const bool mOCSPDownloadEnabled;
   const bool mOCSPStrict;
   const bool mOCSPGETEnabled;
+  const uint32_t mCertShortLifetimeInDays;
   const PinningMode mPinningMode;
 
 private:
   OCSPCache mOCSPCache;
 };
 
 void InitCertVerifierLog();
 SECStatus IsCertBuiltInRoot(CERTCertificate* cert, bool& result);
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -40,25 +40,27 @@ namespace mozilla { namespace psm {
 
 const char BUILTIN_ROOTS_MODULE_DEFAULT_NAME[] = "Builtin Roots Module";
 
 NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
                                            OCSPFetching ocspFetching,
                                            OCSPCache& ocspCache,
              /*optional but shouldn't be*/ void* pinArg,
                                            CertVerifier::OcspGetConfig ocspGETConfig,
+                                           uint32_t certShortLifetimeInDays,
                                            CertVerifier::PinningMode pinningMode,
                                            unsigned int minRSABits,
                               /*optional*/ const char* hostname,
                               /*optional*/ ScopedCERTCertList* builtChain)
   : mCertDBTrustType(certDBTrustType)
   , mOCSPFetching(ocspFetching)
   , mOCSPCache(ocspCache)
   , mPinArg(pinArg)
   , mOCSPGetConfig(ocspGETConfig)
+  , mCertShortLifetimeInDays(certShortLifetimeInDays)
   , mPinningMode(pinningMode)
   , mMinRSABits(minRSABits)
   , mHostname(hostname)
   , mBuiltChain(builtChain)
   , mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID))
   , mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED)
 {
 }
@@ -324,16 +326,17 @@ GetOCSPAuthorityInfoAccessLocation(PLAre
   }
 
   return Success;
 }
 
 Result
 NSSCertDBTrustDomain::CheckRevocation(EndEntityOrCA endEntityOrCA,
                                       const CertID& certID, Time time,
+                                      Duration validityDuration,
                          /*optional*/ const Input* stapledOCSPResponse,
                          /*optional*/ const Input* aiaExtension)
 {
   // Actively distrusted certificates will have already been blocked by
   // GetCertTrust.
 
   // TODO: need to verify that IsRevoked isn't called for trust anchors AND
   // that that fact is documented in mozillapkix.
@@ -450,17 +453,20 @@ NSSCertDBTrustDomain::CheckRevocation(En
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
   // TODO: We still need to handle the fallback for expired responses. But,
   // if/when we disable OCSP fetching by default, it would be ambiguous whether
   // security.OCSP.enable==0 means "I want the default" or "I really never want
   // you to ever fetch OCSP."
 
+  Duration shortLifetime(mCertShortLifetimeInDays * Time::ONE_DAY_IN_SECONDS);
+
   if ((mOCSPFetching == NeverFetchOCSP) ||
+      (validityDuration < shortLifetime) ||
       (endEntityOrCA == EndEntityOrCA::MustBeCA &&
        (mOCSPFetching == FetchOCSPForDVHardFail ||
         mOCSPFetching == FetchOCSPForDVSoftFail ||
         blocklistIsFresh))) {
     // We're not going to be doing any fetching, so if there was a cached
     // "unknown" response, say so.
     if (cachedResponseResult == Result::ERROR_OCSP_UNKNOWN_CERT) {
       return Result::ERROR_OCSP_UNKNOWN_CERT;
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -48,16 +48,17 @@ public:
     FetchOCSPForDVHardFail = 2,
     FetchOCSPForEV = 3,
     LocalOnlyOCSPForEV = 4,
   };
 
   NSSCertDBTrustDomain(SECTrustType certDBTrustType, OCSPFetching ocspFetching,
                        OCSPCache& ocspCache, void* pinArg,
                        CertVerifier::OcspGetConfig ocspGETConfig,
+                       uint32_t certShortLifetimeInDays,
                        CertVerifier::PinningMode pinningMode,
                        unsigned int minRSABits,
           /*optional*/ const char* hostname = nullptr,
       /*optional out*/ ScopedCERTCertList* builtChain = nullptr);
 
   virtual Result FindIssuer(mozilla::pkix::Input encodedIssuerName,
                             IssuerChecker& checker,
                             mozilla::pkix::Time time) override;
@@ -91,16 +92,17 @@ public:
                            mozilla::pkix::DigestAlgorithm digestAlg,
                            /*out*/ uint8_t* digestBuf,
                            size_t digestBufLen) override;
 
   virtual Result CheckRevocation(
                    mozilla::pkix::EndEntityOrCA endEntityOrCA,
                    const mozilla::pkix::CertID& certID,
                    mozilla::pkix::Time time,
+                   mozilla::pkix::Duration validityDuration,
       /*optional*/ const mozilla::pkix::Input* stapledOCSPResponse,
       /*optional*/ const mozilla::pkix::Input* aiaExtension)
                    override;
 
   virtual Result IsChainValid(const mozilla::pkix::DERArray& certChain,
                               mozilla::pkix::Time time) override;
 
   CertVerifier::OCSPStaplingStatus GetOCSPStaplingStatus() const
@@ -122,16 +124,17 @@ private:
     uint16_t maxLifetimeInDays, mozilla::pkix::Input encodedResponse,
     EncodedResponseSource responseSource, /*out*/ bool& expired);
 
   const SECTrustType mCertDBTrustType;
   const OCSPFetching mOCSPFetching;
   OCSPCache& mOCSPCache; // non-owning!
   void* mPinArg; // non-owning!
   const CertVerifier::OcspGetConfig mOCSPGetConfig;
+  const uint32_t mCertShortLifetimeInDays;
   CertVerifier::PinningMode mPinningMode;
   const unsigned int mMinRSABits;
   const char* mHostname; // non-owning - only used for pinning checks
   ScopedCERTCertList* mBuiltChain; // non-owning
   nsCOMPtr<nsICertBlocklist> mCertBlocklist;
   CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
 };
 
--- a/security/manager/ssl/src/SharedCertVerifier.h
+++ b/security/manager/ssl/src/SharedCertVerifier.h
@@ -15,17 +15,19 @@ class SharedCertVerifier : public mozill
 {
 protected:
   ~SharedCertVerifier();
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedCertVerifier)
 
   SharedCertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
-                     OcspGetConfig ogc, PinningMode pinningMode)
-    : mozilla::psm::CertVerifier(odc, osc, ogc, pinningMode)
+                     OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
+                     PinningMode pinningMode)
+    : mozilla::psm::CertVerifier(odc, osc, ogc, certShortLifetimeInDays,
+                                 pinningMode)
   {
   }
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__SharedCertVerifier_h
--- a/security/manager/ssl/src/nsNSSComponent.cpp
+++ b/security/manager/ssl/src/nsNSSComponent.cpp
@@ -176,40 +176,49 @@ bool EnsureNSSInitialized(EnsureNSSOpera
 
   default:
     NS_ASSERTION(false, "Bad operator to EnsureNSSInitialized");
     return false;
   }
 }
 
 static void
-GetOCSPBehaviorFromPrefs(/*out*/ CertVerifier::OcspDownloadConfig* odc,
-                         /*out*/ CertVerifier::OcspStrictConfig* osc,
-                         /*out*/ CertVerifier::OcspGetConfig* ogc,
-                         const MutexAutoLock& /*proofOfLock*/)
+GetRevocationBehaviorFromPrefs(/*out*/ CertVerifier::OcspDownloadConfig* odc,
+                               /*out*/ CertVerifier::OcspStrictConfig* osc,
+                               /*out*/ CertVerifier::OcspGetConfig* ogc,
+                               /*out*/ uint32_t* certShortLifetimeInDays,
+                               const MutexAutoLock& /*proofOfLock*/)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(odc);
   MOZ_ASSERT(osc);
   MOZ_ASSERT(ogc);
+  MOZ_ASSERT(certShortLifetimeInDays);
 
   // 0 = disabled, otherwise enabled
   *odc = Preferences::GetInt("security.OCSP.enabled", 1)
        ? CertVerifier::ocspOn
        : CertVerifier::ocspOff;
 
   *osc = Preferences::GetBool("security.OCSP.require", false)
        ? CertVerifier::ocspStrict
        : CertVerifier::ocspRelaxed;
 
   // XXX: Always use POST for OCSP; see bug 871954 for undoing this.
   *ogc = Preferences::GetBool("security.OCSP.GET.enabled", false)
        ? CertVerifier::ocspGetEnabled
        : CertVerifier::ocspGetDisabled;
 
+  // If we pass in just 0 as the second argument to Preferences::GetUint, there
+  // are two function signatures that match (given that 0 can be intepreted as
+  // a null pointer). Thus the compiler will complain without the cast.
+  *certShortLifetimeInDays =
+    Preferences::GetUint("security.pki.cert_short_lifetime_in_days",
+                         static_cast<uint32_t>(0));
+
   SSL_ClearSessionCache();
 }
 
 nsNSSComponent::nsNSSComponent()
   :mutex("nsNSSComponent.mutex"),
    mNSSInitialized(false),
 #ifndef MOZ_NO_SMART_CARDS
    mThreadList(nullptr),
@@ -870,19 +879,23 @@ void nsNSSComponent::setValidationOption
                            CertVerifier::pinningDisabled));
   if (pinningMode > CertVerifier::pinningEnforceTestMode) {
     pinningMode = CertVerifier::pinningDisabled;
   }
 
   CertVerifier::OcspDownloadConfig odc;
   CertVerifier::OcspStrictConfig osc;
   CertVerifier::OcspGetConfig ogc;
+  uint32_t certShortLifetimeInDays;
 
-  GetOCSPBehaviorFromPrefs(&odc, &osc, &ogc, lock);
-  mDefaultCertVerifier = new SharedCertVerifier(odc, osc, ogc, pinningMode);
+  GetRevocationBehaviorFromPrefs(&odc, &osc, &ogc, &certShortLifetimeInDays,
+                                 lock);
+  mDefaultCertVerifier = new SharedCertVerifier(odc, osc, ogc,
+                                                certShortLifetimeInDays,
+                                                pinningMode);
 }
 
 // Enable the TLS versions given in the prefs, defaulting to TLS 1.0 (min) and
 // TLS 1.2 (max) when the prefs aren't set or set to invalid values.
 nsresult
 nsNSSComponent::setEnabledTLSVersions()
 {
   // keep these values in sync with security-prefs.js
@@ -1339,16 +1352,17 @@ nsNSSComponent::Observe(nsISupports* aSu
       SSL_OptionSetDefault(SSL_ENABLE_ALPN,
                            Preferences::GetBool("security.ssl.enable_alpn",
                                                 ALPN_ENABLED_DEFAULT));
     } else if (prefName.Equals("security.ssl.disable_session_identifiers")) {
       ConfigureTLSSessionIdentifiers();
     } else if (prefName.EqualsLiteral("security.OCSP.enabled") ||
                prefName.EqualsLiteral("security.OCSP.require") ||
                prefName.EqualsLiteral("security.OCSP.GET.enabled") ||
+               prefName.EqualsLiteral("security.pki.cert_short_lifetime_in_days") ||
                prefName.EqualsLiteral("security.ssl.enable_ocsp_stapling") ||
                prefName.EqualsLiteral("security.cert_pinning.enforcement_level")) {
       MutexAutoLock lock(mutex);
       setValidationOptions(false, lock);
     } else {
       clearSessionCache = false;
     }
     if (clearSessionCache)
--- a/security/manager/ssl/tests/unit/test_ocsp_caching.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_caching.js
@@ -42,16 +42,49 @@ function run_test() {
 
   add_tests();
 
   add_test(function() { ocspResponder.stop(run_next_test); });
   run_next_test();
 }
 
 function add_tests() {
+  // Test that verifying a certificate with a "short lifetime" doesn't result
+  // in OCSP fetching. Due to longevity requirements in our testing
+  // infrastructure, the certificate we encounter is valid for a very long
+  // time, so we have to define a "short lifetime" as something very long.
+  add_test(function() {
+    Services.prefs.setIntPref("security.pki.cert_short_lifetime_in_days",
+                              12000);
+    run_next_test();
+  });
+  add_connection_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess,
+                      clearSessionCache);
+  add_test(function() {
+    Assert.equal(0, gFetchCount,
+                 "expected zero OCSP requests for a short-lived certificate");
+    Services.prefs.setIntPref("security.pki.cert_short_lifetime_in_days", 100);
+    run_next_test();
+  });
+  // If a "short lifetime" is something more reasonable, ensure that we do OCSP
+  // fetching for this long-lived certificate.
+  add_connection_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess,
+                      clearSessionCache);
+  add_test(function() {
+    Assert.equal(1, gFetchCount,
+                 "expected one OCSP request for a long-lived certificate");
+    Services.prefs.clearUserPref("security.pki.cert_short_lifetime_in_days");
+    run_next_test();
+  });
+
+  //---------------------------------------------------------------------------
+
+  // Reset state
+  add_test(function() { clearOCSPCache(); gFetchCount = 0; run_next_test(); });
+
   // This test assumes that OCSPStaplingServer uses the same cert for
   // ocsp-stapling-unknown.example.com and ocsp-stapling-none.example.com.
 
   // Get an Unknown response for the *.exmaple.com cert and put it in the
   // OCSP cache.
   add_connection_test("ocsp-stapling-unknown.example.com",
                       SEC_ERROR_OCSP_UNKNOWN_CERT,
                       clearSessionCache);
--- a/security/pkix/include/pkix/Time.h
+++ b/security/pkix/include/pkix/Time.h
@@ -102,25 +102,50 @@ private:
   //      // WRONG! 1970-01-01-00:00:00 == time_t(0), but not Time(0)!
   //      return Time(t);
   //    }
   explicit Time(uint64_t elapsedSecondsAD)
     : elapsedSecondsAD(elapsedSecondsAD)
   {
   }
   friend Time TimeFromElapsedSecondsAD(uint64_t);
+  friend class Duration;
 
   uint64_t elapsedSecondsAD;
 };
 
 inline Time TimeFromElapsedSecondsAD(uint64_t elapsedSecondsAD)
 {
   return Time(elapsedSecondsAD);
 }
 
 Time Now();
 
 // Note the epoch is the unix epoch (ie 00:00:00 UTC, 1 January 1970)
 Time TimeFromEpochInSeconds(uint64_t secondsSinceEpoch);
 
+class Duration final
+{
+public:
+  Duration(Time timeA, Time timeB)
+    : durationInSeconds(timeA < timeB
+                        ? timeB.elapsedSecondsAD - timeA.elapsedSecondsAD
+                        : timeA.elapsedSecondsAD - timeB.elapsedSecondsAD)
+  {
+  }
+
+  explicit Duration(uint64_t durationInSeconds)
+    : durationInSeconds(durationInSeconds)
+  {
+  }
+
+  bool operator<(const Duration& other) const
+  {
+    return durationInSeconds < other.durationInSeconds;
+  }
+
+private:
+  uint64_t durationInSeconds;
+};
+
 } } // namespace mozilla::pkix
 
 #endif // mozilla_pkix_Time_h
--- a/security/pkix/include/pkix/pkixtypes.h
+++ b/security/pkix/include/pkix/pkixtypes.h
@@ -263,16 +263,17 @@ public:
   // certificate chain passed to IsChainValid; especially, it would be very
   // wrong to assume that the certificate chain is valid.
   //
   // certChain.GetDER(0) is the trust anchor.
   virtual Result IsChainValid(const DERArray& certChain, Time time) = 0;
 
   virtual Result CheckRevocation(EndEntityOrCA endEntityOrCA,
                                  const CertID& certID, Time time,
+                                 Duration validityDuration,
                     /*optional*/ const Input* stapledOCSPresponse,
                     /*optional*/ const Input* aiaExtension) = 0;
 
   // Check that the given digest algorithm is acceptable for use in signatures.
   //
   // Return Success if the algorithm is acceptable,
   // Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED if the algorithm is not
   // acceptable, or another error code if another error occurred.
--- a/security/pkix/lib/pkixbuild.cpp
+++ b/security/pkix/lib/pkixbuild.cpp
@@ -219,18 +219,27 @@ PathBuildingStep::Check(Input potentialI
 
   // We avoid doing revocation checking for expired certificates because OCSP
   // responders are allowed to forget about expired certificates, and many OCSP
   // responders return an error when asked for the status of an expired
   // certificate.
   if (deferredSubjectError != Result::ERROR_EXPIRED_CERTIFICATE) {
     CertID certID(subject.GetIssuer(), potentialIssuer.GetSubjectPublicKeyInfo(),
                   subject.GetSerialNumber());
+    Time notBefore(Time::uninitialized);
+    Time notAfter(Time::uninitialized);
+    // This should never fail. If we're here, we've already checked that the
+    // given time is in the certificate's validity period.
+    rv = CheckValidity(subject.GetValidity(), time, &notBefore, &notAfter);
+    if (rv != Success) {
+      return rv;
+    }
+    Duration validityDuration(notAfter, notBefore);
     rv = trustDomain.CheckRevocation(subject.endEntityOrCA, certID, time,
-                                     stapledOCSPResponse,
+                                     validityDuration, stapledOCSPResponse,
                                      subject.GetAuthorityInfoAccess());
     if (rv != Success) {
       return RecordResult(rv, keepGoing);
     }
   }
 
   return RecordResult(Success, keepGoing);
 }
--- a/security/pkix/lib/pkixcheck.cpp
+++ b/security/pkix/lib/pkixcheck.cpp
@@ -120,17 +120,19 @@ CheckSignatureAlgorithm(TrustDomain& tru
   }
 
   return Success;
 }
 
 // 4.1.2.5 Validity
 
 Result
-CheckValidity(Input encodedValidity, Time time)
+CheckValidity(Input encodedValidity, Time time,
+              /*optional out*/ Time* notBeforeOut,
+              /*optional out*/ Time* notAfterOut)
 {
   Reader validity(encodedValidity);
   Time notBefore(Time::uninitialized);
   if (der::TimeChoice(validity, notBefore) != Success) {
     return Result::ERROR_INVALID_DER_TIME;
   }
 
   Time notAfter(Time::uninitialized);
@@ -149,16 +151,22 @@ CheckValidity(Input encodedValidity, Tim
   if (time < notBefore) {
     return Result::ERROR_NOT_YET_VALID_CERTIFICATE;
   }
 
   if (time > notAfter) {
     return Result::ERROR_EXPIRED_CERTIFICATE;
   }
 
+  if (notBeforeOut) {
+    *notBeforeOut = notBefore;
+  }
+  if (notAfterOut) {
+    *notAfterOut = notAfter;
+  }
   return Success;
 }
 
 // 4.1.2.7 Subject Public Key Info
 
 Result
 CheckSubjectPublicKeyInfo(Reader& input, TrustDomain& trustDomain,
                           EndEntityOrCA endEntityOrCA)
--- a/security/pkix/lib/pkixcheck.h
+++ b/security/pkix/lib/pkixcheck.h
@@ -40,11 +40,15 @@ Result CheckIssuerIndependentProperties(
           const CertPolicyId& requiredPolicy,
           unsigned int subCACount,
           /*out*/ TrustLevel& trustLevel);
 
 Result CheckNameConstraints(Input encodedNameConstraints,
                             const BackCert& firstChild,
                             KeyPurposeId requiredEKUIfPresent);
 
+Result CheckValidity(Input encodedValidity, Time time,
+                     /*optional out*/ Time* notBeforeOut = nullptr,
+                     /*optional out*/ Time* notAfterOut = nullptr);
+
 } } // namespace mozilla::pkix
 
 #endif // mozilla_pkix_pkixcheck_h
--- a/security/pkix/test/gtest/pkixbuild_tests.cpp
+++ b/security/pkix/test/gtest/pkixbuild_tests.cpp
@@ -134,17 +134,17 @@ private:
     rv = checker.Check(derCert, nullptr/*additionalNameConstraints*/,
                        keepGoing);
     if (rv != Success) {
       return rv;
     }
     return Success;
   }
 
-  Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
+  Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
                          /*optional*/ const Input*, /*optional*/ const Input*)
                          override
   {
     return Success;
   }
 
   Result IsChainValid(const DERArray&, Time) override
   {
@@ -398,17 +398,17 @@ public:
     bool keepGoing;
     EXPECT_EQ(Success,
               checker.Check(issuerInput, nullptr /*additionalNameConstraints*/,
                             keepGoing));
     EXPECT_EQ(expectedKeepGoing, keepGoing);
     return Success;
   }
 
-  Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
+  Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
                          /*optional*/ const Input*, /*optional*/ const Input*)
                          override
   {
     return Success;
   }
 
   Result IsChainValid(const DERArray&, Time) override
   {
--- a/security/pkix/test/gtest/pkixcert_extension_tests.cpp
+++ b/security/pkix/test/gtest/pkixcert_extension_tests.cpp
@@ -63,17 +63,17 @@ class TrustEverythingTrustDomain final :
 private:
   Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input,
                       /*out*/ TrustLevel& trustLevel) override
   {
     trustLevel = TrustLevel::TrustAnchor;
     return Success;
   }
 
-  Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
+  Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
                          /*optional*/ const Input*, /*optional*/ const Input*)
                          override
   {
     return Success;
   }
 
   Result IsChainValid(const DERArray&, Time) override
   {
--- a/security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp
+++ b/security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp
@@ -86,18 +86,18 @@ private:
     Result rv = issuerCert.Init(issuerDER->data(), issuerDER->length());
     if (rv != Success) {
       return rv;
     }
     bool keepGoing;
     return checker.Check(issuerCert, nullptr, keepGoing);
   }
 
-  Result CheckRevocation(EndEntityOrCA, const CertID&, Time, const Input*,
-                         const Input*) override
+  Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+                         const Input*, const Input*) override
   {
     return Success;
   }
 
   Result IsChainValid(const DERArray&, Time) override
   {
     return Success;
   }
--- a/security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp
+++ b/security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp
@@ -287,17 +287,17 @@ public:
 
     bool keepGoing;
     EXPECT_EQ(Success, checker.Check(issuerInput, nullptr, keepGoing));
     EXPECT_FALSE(keepGoing);
 
     return Success;
   }
 
-  Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
+  Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
                          /*optional*/ const Input*,
                          /*optional*/ const Input*) override
   {
     return Success;
   }
 
   Result IsChainValid(const DERArray&, Time) override
   {
--- a/security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp
+++ b/security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp
@@ -17,27 +17,22 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
+#include "pkixcheck.h"
 #include "pkixgtest.h"
 
 using namespace mozilla::pkix;
 using namespace mozilla::pkix::test;
 
-namespace mozilla { namespace pkix {
-
-Result CheckValidity(const Input encodedValidity, Time time);
-
-} } // namespace mozilla::pkix
-
 static const Time PAST_TIME(YMDHMS(1998, 12, 31, 12, 23, 56));
 
 #define OLDER_GENERALIZEDTIME \
   0x18, 15,                               /* tag, length */ \
   '1', '9', '9', '9', '0', '1', '0', '1', /* 1999-01-01 */ \
   '0', '0', '0', '0', '0', '0', 'Z'       /* 00:00:00Z */
 
 #define OLDER_UTCTIME \
--- a/security/pkix/test/gtest/pkixgtest.h
+++ b/security/pkix/test/gtest/pkixgtest.h
@@ -97,17 +97,17 @@ public:
 
   Result FindIssuer(Input, IssuerChecker&, Time) override
   {
     ADD_FAILURE();
     return NotReached("FindIssuer should not be called",
                       Result::FATAL_ERROR_LIBRARY_FAILURE);
   }
 
-  Result CheckRevocation(EndEntityOrCA, const CertID&, Time,
+  Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
                           /*optional*/ const Input*,
                           /*optional*/ const Input*) override
   {
     ADD_FAILURE();
     return NotReached("CheckRevocation should not be called",
                       Result::FATAL_ERROR_LIBRARY_FAILURE);
   }
 
--- a/security/sandbox/linux/SandboxChroot.cpp
+++ b/security/sandbox/linux/SandboxChroot.cpp
@@ -73,18 +73,29 @@ AlwaysClose(int fd)
 
 static int
 OpenDeletedDirectory()
 {
   // We don't need this directory to persist between invocations of
   // the program (nor need it to be cleaned up if something goes wrong
   // here, because mkdtemp will choose a fresh name), so /tmp as
   // specified by FHS is adequate.
-  char path[] = "/tmp/mozsandbox.XXXXXX";
-  if (!mkdtemp(path)) {
+  //
+  // However, this needs a filesystem where a deleted directory can
+  // still be used, and /tmp is sometimes not that; e.g., aufs(5),
+  // often used for containers, will cause the chroot() to fail with
+  // ESTALE (bug 1162965).  So this uses /dev/shm if possible instead.
+  char tmpPath[] = "/tmp/mozsandbox.XXXXXX";
+  char shmPath[] = "/dev/shm/mozsandbox.XXXXXX";
+  char* path;
+  if (mkdtemp(shmPath)) {
+    path = shmPath;
+  } else if (mkdtemp(tmpPath)) {
+    path = tmpPath;
+  } else {
     SANDBOX_LOG_ERROR("mkdtemp: %s", strerror(errno));
     return -1;
   }
   int fd = HANDLE_EINTR(open(path, O_RDONLY | O_DIRECTORY));
   if (fd < 0) {
     SANDBOX_LOG_ERROR("open %s: %s", path, strerror(errno));
     // Try to clean up.  Shouldn't fail, but livable if it does.
     DebugOnly<bool> ok = HANDLE_EINTR(rmdir(path)) == 0;
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5556,16 +5556,22 @@
   },
   "DEVTOOLS_DEBUGGER_RDP_REMOTE_RECONFIGURETHREAD_MS": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "10000",
     "n_buckets": "1000",
     "description": "The time (in milliseconds) that it took a 'reconfigure thread' request to go round trip."
   },
+  "MEDIA_WMF_DECODE_ERROR": {
+    "expires_in_version": "50",
+    "kind": "enumerated",
+    "n_values": 256,
+    "description": "WMF media decoder error or success (0) codes."
+  },
   "VIDEO_CANPLAYTYPE_H264_CONSTRAINT_SET_FLAG": {
     "expires_in_version": "50",
     "kind": "enumerated",
     "n_values": 128,
     "description": "The H.264 constraint set flag as extracted from the codecs parameter passed to HTMLMediaElement.canPlayType, with the addition of 0 for unknown values."
   },
   "VIDEO_CANPLAYTYPE_H264_LEVEL": {
     "expires_in_version": "50",
@@ -7535,16 +7541,30 @@
     "kind": "count"
   },
   "VIDEO_MSE_UNLOAD_STATE": {
     "expires_in_version": "45",
     "kind": "enumerated",
     "n_values": 5,
     "description": "MSE video state when unloading. ended = 0, paused = 1, stalled = 2, seeking = 3, other = 4"
   },
+  "VIDEO_ADOBE_GMP_DISAPPEARED": {
+    "alert_emails": ["cpearce@mozilla.com"],
+    "expires_in_version": "42",
+    "kind": "flag",
+    "description": "Whether or not the Adobe EME GMP was expected to be resident on disk but mysteriously isn't.",
+    "releaseChannelCollection": "opt-out"
+  },
+  "VIDEO_OPENH264_GMP_DISAPPEARED": {
+    "alert_emails": ["cpearce@mozilla.com", "rjesup@mozilla.com"],
+    "expires_in_version": "42",
+    "kind": "flag",
+    "description": "Whether or not the OpenH264 GMP was expected to be resident on disk but mysteriously isn't.",
+    "releaseChannelCollection": "opt-out"
+  },
   "FX_SANITIZE_TOTAL": {
     "alert_emails": ["firefox-dev@mozilla.org", "gavin@mozilla.com"],
     "expires_in_version": "50",
     "kind": "exponential",
     "high": "30000",
     "n_buckets": 20,
     "extended_statistics_ok": true,
     "description": "Sanitize: Total time it takes to sanitize (ms)"
--- a/toolkit/crashreporter/test/dumputils.cpp
+++ b/toolkit/crashreporter/test/dumputils.cpp
@@ -68,18 +68,20 @@ DumpCheckMemory(const char* dump_file)
   if (!memory_list) {
     return false;
   }
 
   void *addr;
   FILE *fp = fopen("crash-addr", "r");
   if (!fp)
     return false;
-  if (fscanf(fp, "%p", &addr) != 1)
+  if (fscanf(fp, "%p", &addr) != 1) {
+    fclose(fp);
     return false;
+  }
   fclose(fp);
 
   remove("crash-addr");
 
   MinidumpMemoryRegion* region =
     memory_list->GetMemoryRegionForAddress(uint64_t(addr));
   if(!region)
     return false;
--- a/toolkit/modules/GMPUtils.jsm
+++ b/toolkit/modules/GMPUtils.jsm
@@ -84,18 +84,18 @@ this.GMPPrefs = {
   KEY_UPDATE_LAST_CHECK:        "media.gmp-manager.lastCheck",
   KEY_SECONDS_BETWEEN_CHECKS:   "media.gmp-manager.secondsBetweenChecks",
   KEY_APP_DISTRIBUTION:         "distribution.id",
   KEY_APP_DISTRIBUTION_VERSION: "distribution.version",
   KEY_BUILDID:                  "media.gmp-manager.buildID",
   KEY_CERTS_BRANCH:             "media.gmp-manager.certs.",
   KEY_PROVIDER_ENABLED:         "media.gmp-provider.enabled",
   KEY_LOG_BASE:                 "media.gmp.log.",
-  KEY_LOGGING_LEVEL:            this.KEY_LOG_BASE + "level",
-  KEY_LOGGING_DUMP:             this.KEY_LOG_BASE + "dump",
+  KEY_LOGGING_LEVEL:            "media.gmp.log.level",
+  KEY_LOGGING_DUMP:             "media.gmp.log.dump",
 
   /**
    * Obtains the specified preference in relation to the specified plugin.
    * @param aKey The preference key value to use.
    * @param aDefaultValue The default value if no preference exists.
    * @param aPlugin The plugin to scope the preference to.
    * @return The obtained preference value, or the defaultValue if none exists.
    */
--- a/toolkit/mozapps/extensions/internal/GMPProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/GMPProvider.jsm
@@ -43,38 +43,42 @@ const GMP_PLUGINS = [
     id:              OPEN_H264_ID,
     name:            "openH264_name",
     description:     "openH264_description2",
     // The following licenseURL is part of an awful hack to include the OpenH264
     // license without having bug 624602 fixed yet, and intentionally ignores
     // localisation.
     licenseURL:      "chrome://mozapps/content/extensions/OpenH264-license.txt",
     homepageURL:     "http://www.openh264.org/",
-    optionsURL:      "chrome://mozapps/content/extensions/gmpPrefs.xul"
+    optionsURL:      "chrome://mozapps/content/extensions/gmpPrefs.xul",
+    missingKey:      "VIDEO_OPENH264_GMP_DISAPPEARED",
   },
   {
     id:              EME_ADOBE_ID,
     name:            "eme-adobe_name",
     description:     "eme-adobe_description",
     // The following learnMoreURL is another hack to be able to support a SUMO page for this
     // feature.
     get learnMoreURL() {
       return Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
     },
     licenseURL:      "http://help.adobe.com/en_US/primetime/drm/HTML5_CDM_EULA/index.html",
     homepageURL:     "http://help.adobe.com/en_US/primetime/drm/HTML5_CDM",
     optionsURL:      "chrome://mozapps/content/extensions/gmpPrefs.xul",
-    isEME:           true
+    isEME:           true,
+    missingKey:      "VIDEO_ADOBE_GMP_DISAPPEARED",
   }];
 
 XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
   () => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
 XPCOMUtils.defineLazyGetter(this, "gmpService",
   () => Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(Ci.mozIGeckoMediaPluginChromeService));
 
+XPCOMUtils.defineLazyGetter(this, "telemetryService", () => Services.telemetry);
+
 let messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
                        .getService(Ci.nsIMessageListenerManager);
 
 let gLogger;
 let gLogAppenderDump = null;
 
 function configureLogging() {
   if (!gLogger) {
@@ -135,16 +139,20 @@ GMPWrapper.prototype = {
       this._gmpPath = OS.Path.join(OS.Constants.Path.profileDir,
                                    this._plugin.id,
                                    GMPPrefs.get(GMPPrefs.KEY_PLUGIN_VERSION,
                                                 null, this._plugin.id));
     }
     return this._gmpPath;
   },
 
+  get missingKey() {
+    return this._plugin.missingKey;
+  },
+
   get id() { return this._plugin.id; },
   get type() { return "plugin"; },
   get isGMPlugin() { return true; },
   get name() { return this._plugin.name; },
   get creator() { return null; },
   get homepageURL() { return this._plugin.homepageURL; },
 
   get description() { return this._plugin.description; },
@@ -442,16 +450,53 @@ GMPWrapper.prototype = {
                        this.onPrefVersionChanged, this);
     if (this._plugin.isEME) {
       Preferences.ignore(GMPPrefs.KEY_EME_ENABLED,
                          this.onPrefEMEGlobalEnabledChanged, this);
       messageManager.removeMessageListener("EMEVideo:ContentMediaKeysRequest", this);
     }
     return this._updateTask;
   },
+
+  _arePluginFilesOnDisk: function () {
+    let fileExists = function(aGmpPath, aFileName) {
+      let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+      let path = OS.Path.join(aGmpPath, aFileName);
+      f.initWithPath(path);
+      return f.exists();
+    };
+
+    // Determine the name of the GMP dynamic library; it differs on every
+    // platform. Note: we can't use Services.appInfo.OS here, as that's
+    // "XPCShell" in our tests.
+    let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
+    let isOSX = ("nsILocalFileMac" in Ci);
+    let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc);
+
+    let libName = "";
+    let id = this._plugin.id;
+    if (isWindows) {
+      libName = id.substring(4) + ".dll";
+    } else if (isOSX) {
+      libName = "lib" + id.substring(4) + ".dylib";
+    } else if (isLinux) {
+      libName = id.substring(4) + ".so";
+    } else {
+      this._info.error("_arePluginFilesOnDisk - unsupported platform.");
+      return false;
+    }
+
+    return fileExists(this.gmpPath, libName) &&
+           fileExists(this.gmpPath, id.substring(4) + ".info");
+  },
+
+  validate: function() {
+    return !this.isInstalled ||
+           this._arePluginFilesOnDisk();
+  },
 };
 
 let GMPProvider = {
   get name() { return "GMPProvider"; },
 
   _plugins: null,
 
   startup: function() {
@@ -467,16 +512,23 @@ let GMPProvider = {
     for (let [id, plugin] of this._plugins) {
       let wrapper = plugin.wrapper;
       let gmpPath = wrapper.gmpPath;
       let isEnabled = wrapper.isActive;
       this._log.trace("startup - enabled=" + isEnabled + ", gmpPath=" +
                       gmpPath);
 
       if (gmpPath && isEnabled) {
+        if (!wrapper.validate()) {
+          this._log.info("startup - gmp " + plugin.id +
+                         " missing lib and/or info files, uninstalling");
+          telemetryService.getHistogramById(wrapper.missingKey).add(true);
+          wrapper.uninstallPlugin();
+          continue;
+        }
         this._log.info("startup - adding gmp directory " + gmpPath);
         try {
           gmpService.addPluginDirectory(gmpPath);
         } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') {
           this._log.warn("startup - adding gmp directory failed with " +
                          e.name + " - sandboxing not available?", e);
         }
       }
@@ -582,16 +634,17 @@ let GMPProvider = {
       let plugin = {
         id: aPlugin.id,
         name: pluginsBundle.GetStringFromName(aPlugin.name),
         description: pluginsBundle.GetStringFromName(aPlugin.description),
         homepageURL: aPlugin.homepageURL,
         optionsURL: aPlugin.optionsURL,
         wrapper: null,
         isEME: aPlugin.isEME,
+        missingKey: aPlugin.missingKey,
       };
       plugin.fullDescription = this.generateFullDescription(aPlugin);
       plugin.wrapper = new GMPWrapper(plugin);
       this._plugins.set(plugin.id, plugin);
     }
   },
 
   ensureProperCDMInstallState: function() {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gmpProvider.js
@@ -4,26 +4,30 @@
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 let GMPScope = Cu.import("resource://gre/modules/addons/GMPProvider.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "pluginsBundle",
   () => Services.strings.createBundle("chrome://global/locale/plugins.properties"));
 
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+
 let gMockAddons = new Map();
 let gMockEmeAddons = new Map();
 
 for (let plugin of GMPScope.GMP_PLUGINS) {
   let mockAddon = Object.freeze({
       id: plugin.id,
       isValid: true,
       isInstalled: false,
       nameId: plugin.name,
       descriptionId: plugin.description,
+      missingKey: plugin.missingKey,
   });
   gMockAddons.set(mockAddon.id, mockAddon);
   if (mockAddon.id.indexOf("gmp-eme-") == 0) {
     gMockEmeAddons.set(mockAddon.id, mockAddon);
   }
 }
 
 let gInstalledAddonId = "";
@@ -211,40 +215,111 @@ add_task(function* test_autoUpdatePrefPe
     Assert.equal(addon.applyBackgroundUpdates, AddonManager.AUTOUPDATE_ENABLE);
     Assert.ok(gPrefs.getBoolPref(autoupdateKey));
 
     addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
     Assert.ok(!gPrefs.prefHasUserValue(autoupdateKey));
   }
 });
 
+function createMockPluginFilesIfNeeded(aFile, aPluginId) {
+  function createFile(aFileName) {
+    let f = aFile.clone();
+    f.append(aFileName);
+    if (!f.exists()) {
+      f.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+    }
+  };
+
+  // Note: we can't use Services.appInfo.OS, as that's "XPCShell" in our tests.
+  let isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes);
+  let isOSX = ("nsILocalFileMac" in Components.interfaces);
+  let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Components.classes);
+
+  let libName = "";
+  if (isWindows) {
+    libName = aPluginId.substring(4) + ".dll";
+  } else if (isOSX) {
+    libName = "lib" + aPluginId.substring(4) + ".dylib";
+  } else if (isLinux) {
+    libName = aPluginId.substring(4) + ".so";
+  } else {
+    // FAIL!
+    return;
+  }
+  createFile(libName);
+  createFile(aPluginId.substring(4) + ".info");
+}
+
 add_task(function* test_pluginRegistration() {
   const TEST_VERSION = "1.2.3.4";
 
+  let profD = do_get_profile();
   for (let addon of gMockAddons.values()) {
-    let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+    let file = profD.clone();
     file.append(addon.id);
     file.append(TEST_VERSION);
 
     let addedPaths = [];
     let removedPaths = [];
     let clearPaths = () => { addedPaths = []; removedPaths = []; }
 
     let MockGMPService = {
-      addPluginDirectory: path => addedPaths.push(path),
-      removePluginDirectory: path => removedPaths.push(path),
-      removeAndDeletePluginDirectory: path => removedPaths.push(path),
+      addPluginDirectory: path => {
+        if (!addedPaths.includes(path)) {
+          addedPaths.push(path);
+        }
+      },
+      removePluginDirectory: path => {
+        if (!removedPaths.includes(path)) {
+          removedPaths.push(path);
+        }
+      },
+      removeAndDeletePluginDirectory: path => {
+        if (!removedPaths.includes(path)) {
+          removedPaths.push(path);
+        }
+      },
+    };
+
+    let reportedKeys = [];
+
+    let MockTelemetry = {
+      getHistogramById: key => {
+        return {
+          add: value => {
+            reportedKeys.push(key);
+          }
+        }
+      }
     };
 
     GMPScope.gmpService = MockGMPService;
+    GMPScope.telemetryService = MockTelemetry;
     gPrefs.setBoolPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), true);
 
-    // Check that the plugin gets registered after startup.
+    // Test that plugin registration fails if the plugin dynamic library and
+    // info files are not present.
     gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
-                      TEST_VERSION);
+                       TEST_VERSION);
+    clearPaths();
+    yield promiseRestartManager();
+    Assert.equal(addedPaths.indexOf(file.path), -1);
+    Assert.deepEqual(removedPaths, [file.path]);
+
+    // Test that the GMPProvider tried to report via telemetry that the
+    // addon's lib file is missing.
+    Assert.deepEqual(reportedKeys, [addon.missingKey]);
+
+    // Create dummy GMP library/info files, and test that plugin registration
+    // succeeds during startup, now that we've added GMP info/lib files.
+    createMockPluginFilesIfNeeded(file, addon.id);
+
+    gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
+                       TEST_VERSION);
     clearPaths();
     yield promiseRestartManager();
     Assert.notEqual(addedPaths.indexOf(file.path), -1);
     Assert.deepEqual(removedPaths, []);
 
     // Check that clearing the version doesn't trigger registration.
     clearPaths();
     gPrefs.clearUserPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id));
@@ -254,30 +329,30 @@ add_task(function* test_pluginRegistrati
     // Restarting with no version set should not trigger registration.
     clearPaths();
     yield promiseRestartManager();
     Assert.equal(addedPaths.indexOf(file.path), -1);
     Assert.equal(removedPaths.indexOf(file.path), -1);
 
     // Changing the pref mid-session should cause unregistration and registration.
     gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
-                      TEST_VERSION);
+                       TEST_VERSION);
     clearPaths();
     const TEST_VERSION_2 = "5.6.7.8";
     let file2 = Services.dirsvc.get("ProfD", Ci.nsIFile);
     file2.append(addon.id);
     file2.append(TEST_VERSION_2);
     gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
                       TEST_VERSION_2);
     Assert.deepEqual(addedPaths, [file2.path]);
     Assert.deepEqual(removedPaths, [file.path]);
 
     // Disabling the plugin should cause unregistration.
     gPrefs.setCharPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_VERSION, addon.id),
-                      TEST_VERSION);
+                       TEST_VERSION);
     clearPaths();
     gPrefs.setBoolPref(gGetKey(GMPScope.GMPPrefs.KEY_PLUGIN_ENABLED, addon.id), false);
     Assert.deepEqual(addedPaths, []);
     Assert.deepEqual(removedPaths, [file.path]);
 
     // Restarting with the plugin disabled should not cause registration.
     clearPaths();
     yield promiseRestartManager();
--- a/webapprt/locales/en-US/webapprt/overrides/dom.properties
+++ b/webapprt/locales/en-US/webapprt/overrides/dom.properties
@@ -74,17 +74,16 @@ FullScreenDeniedHidden=Request for full-
 FullScreenDeniedIframeNotAllowed=Request for full-screen was denied because at least one of the document's containing iframes does not have an "allowfullscreen" attribute.
 FullScreenDeniedNotInputDriven=Request for full-screen was denied because Element.mozRequestFullScreen() was not called from inside a short running user-generated event handler.
 FullScreenDeniedNotInDocument=Request for full-screen was denied because requesting element is no longer in its document.
 FullScreenDeniedMovedDocument=Request for full-screen was denied because requesting element has moved document.
 FullScreenDeniedLostWindow=Request for full-screen was denied because we no longer have a window.
 FullScreenDeniedSubDocFullScreen=Request for full-screen was denied because a subdocument of the document requesting full-screen is already full-screen.
 FullScreenDeniedNotDescendant=Request for full-screen was denied because requesting element is not a descendant of the current full-screen element.
 FullScreenDeniedNotFocusedTab=Request for full-screen was denied because requesting element is not in the currently focused tab.
-FullScreenDeniedContentOnly=Request for full-screen was denied because requesting element is in the chrome document and the fullscreen API is configured for content only.
 RemovedFullScreenElement=Exited full-screen because full-screen element was removed from document.
 FocusedWindowedPluginWhileFullScreen=Exited full-screen because windowed plugin was focused.
 HTMLSyncXHRWarning=HTML parsing in XMLHttpRequest is not supported in the synchronous mode.
 InvalidRedirectChannelWarning=Unable to redirect to %S because the channel doesn't implement nsIWritablePropertyBag2.
 ResponseTypeSyncXHRWarning=Use of XMLHttpRequest's responseType attribute is no longer supported in the synchronous mode in window context.
 WithCredentialsSyncXHRWarning=Use of XMLHttpRequest's withCredentials attribute is no longer supported in the synchronous mode in window context.
 TimeoutSyncXHRWarning=Use of XMLHttpRequest's timeout attribute is not supported in the synchronous mode in window context.
 JSONCharsetWarning=An attempt was made to declare a non-UTF-8 encoding for JSON retrieved using XMLHttpRequest. Only UTF-8 is supported for decoding JSON.
--- a/widget/PuppetWidget.cpp
+++ b/widget/PuppetWidget.cpp
@@ -881,29 +881,22 @@ PuppetWidget::NotifyIMEOfSelectionChange
 
 #ifndef MOZ_CROSS_PROCESS_IME
   return NS_OK;
 #endif
 
   if (!mTabChild)
     return NS_ERROR_FAILURE;
 
-  nsEventStatus status;
-  WidgetQueryContentEvent queryEvent(true, NS_QUERY_SELECTED_TEXT, this);
-  InitEvent(queryEvent, nullptr);
-  DispatchEvent(&queryEvent, status);
-
-  if (queryEvent.mSucceeded) {
-    mTabChild->SendNotifyIMESelection(
-      mIMELastReceivedSeqno,
-      queryEvent.GetSelectionStart(),
-      queryEvent.GetSelectionEnd(),
-      queryEvent.GetWritingMode(),
-      aIMENotification.mSelectionChangeData.mCausedByComposition);
-  }
+  mTabChild->SendNotifyIMESelection(
+    mIMELastReceivedSeqno,
+    aIMENotification.mSelectionChangeData.StartOffset(),
+    aIMENotification.mSelectionChangeData.EndOffset(),
+    aIMENotification.mSelectionChangeData.GetWritingMode(),
+    aIMENotification.mSelectionChangeData.mCausedByComposition);
   return NS_OK;
 }
 
 nsresult
 PuppetWidget::NotifyIMEOfMouseButtonEvent(
                 const IMENotification& aIMENotification)
 {
   if (!mTabChild) {
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -531,16 +531,17 @@ public:
     NS_ASSERTION(message == NS_QUERY_SELECTED_TEXT,
                  "not querying selection");
     return mReply.mOffset + (mReply.mReversed ? 0 : mReply.mString.Length());
   }
 
   mozilla::WritingMode GetWritingMode(void) const
   {
     NS_ASSERTION(message == NS_QUERY_SELECTED_TEXT ||
+                 message == NS_QUERY_CARET_RECT ||
                  message == NS_QUERY_TEXT_RECT,
                  "not querying selection or text rect");
     return mReply.mWritingMode;
   }
 
   bool mSucceeded;
   bool mWasAsync;
   bool mUseNativeLineBreak;
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -49,16 +49,17 @@
 #include "mozilla/layers/APZEventState.h"
 #include "mozilla/layers/APZThreadUtils.h"
 #include "mozilla/layers/ChromeProcessController.h"
 #include "mozilla/layers/InputAPZContext.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "mozilla/dom/TabParent.h"
 #include "nsRefPtrHashtable.h"
 #include "TouchEvents.h"
+#include "WritingModes.h"
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
 
 #ifdef DEBUG
 #include "nsIObserver.h"
 
 static void debug_RegisterPrefCallbacks();
@@ -93,16 +94,35 @@ bool            gDisableNativeTheme     
 #define TOUCH_INJECT_PUMP_TIMER_MSEC 50
 #define TOUCH_INJECT_LONG_TAP_DEFAULT_MSEC 1500
 int32_t nsIWidget::sPointerIdCounter = 0;
 
 // Some statics from nsIWidget.h
 /*static*/ uint64_t AutoObserverNotifier::sObserverId = 0;
 /*static*/ nsDataHashtable<nsUint64HashKey, nsCOMPtr<nsIObserver>> AutoObserverNotifier::sSavedObservers;
 
+namespace mozilla {
+namespace widget {
+
+void
+IMENotification::SelectionChangeData::SetWritingMode(
+                                        const WritingMode& aWritingMode)
+{
+  mWritingMode = aWritingMode.mWritingMode;
+}
+
+WritingMode
+IMENotification::SelectionChangeData::GetWritingMode() const
+{
+  return WritingMode(mWritingMode);
+}
+
+} // namespace widget
+} // namespace mozilla
+
 nsAutoRollup::nsAutoRollup()
 {
   // remember if mLastRollup was null, and only clear it upon destruction
   // if so. This prevents recursive usage of nsAutoRollup from clearing
   // mLastRollup when it shouldn't.
   wasClear = !nsBaseWidget::mLastRollup;
 }
 
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -670,16 +670,20 @@ struct ParamTraits<mozilla::widget::IMEN
   typedef mozilla::widget::IMENotification paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg,
       static_cast<mozilla::widget::IMEMessageType>(aParam.mMessage));
     switch (aParam.mMessage) {
       case mozilla::widget::NOTIFY_IME_OF_SELECTION_CHANGE:
+        WriteParam(aMsg, aParam.mSelectionChangeData.mOffset);
+        WriteParam(aMsg, aParam.mSelectionChangeData.mLength);
+        WriteParam(aMsg, aParam.mSelectionChangeData.mWritingMode);
+        WriteParam(aMsg, aParam.mSelectionChangeData.mReversed);
         WriteParam(aMsg, aParam.mSelectionChangeData.mCausedByComposition);
         return;
       case mozilla::widget::NOTIFY_IME_OF_TEXT_CHANGE:
         WriteParam(aMsg, aParam.mTextChangeData.mStartOffset);
         WriteParam(aMsg, aParam.mTextChangeData.mOldEndOffset);
         WriteParam(aMsg, aParam.mTextChangeData.mNewEndOffset);
         WriteParam(aMsg, aParam.mTextChangeData.mCausedByComposition);
         return;
@@ -706,16 +710,24 @@ struct ParamTraits<mozilla::widget::IMEN
     mozilla::widget::IMEMessageType IMEMessage = 0;
     if (!ReadParam(aMsg, aIter, &IMEMessage)) {
       return false;
     }
     aResult->mMessage = static_cast<mozilla::widget::IMEMessage>(IMEMessage);
     switch (aResult->mMessage) {
       case mozilla::widget::NOTIFY_IME_OF_SELECTION_CHANGE:
         return ReadParam(aMsg, aIter,
+                         &aResult->mSelectionChangeData.mOffset) &&
+               ReadParam(aMsg, aIter,
+                         &aResult->mSelectionChangeData.mLength) &&
+               ReadParam(aMsg, aIter,
+                         &aResult->mSelectionChangeData.mWritingMode) &&
+               ReadParam(aMsg, aIter,
+                         &aResult->mSelectionChangeData.mReversed) &&
+               ReadParam(aMsg, aIter,
                          &aResult->mSelectionChangeData.mCausedByComposition);
       case mozilla::widget::NOTIFY_IME_OF_TEXT_CHANGE:
         return ReadParam(aMsg, aIter,
                          &aResult->mTextChangeData.mStartOffset) &&
                ReadParam(aMsg, aIter,
                          &aResult->mTextChangeData.mOldEndOffset) &&
                ReadParam(aMsg, aIter,
                          &aResult->mTextChangeData.mNewEndOffset) &&
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -33,16 +33,17 @@ class   imgIContainer;
 class   nsIContent;
 class   ViewWrapper;
 class   nsIWidgetListener;
 class   nsIntRegion;
 class   nsIScreen;
 
 namespace mozilla {
 class CompositorVsyncDispatcher;
+class WritingMode;
 namespace dom {
 class TabChild;
 }
 namespace plugins {
 class PluginWidgetChild;
 }
 namespace layers {
 class Composer2D;
@@ -590,16 +591,20 @@ struct IMENotification
     : mMessage(static_cast<IMEMessage>(-1))
   {}
 
   MOZ_IMPLICIT IMENotification(IMEMessage aMessage)
     : mMessage(aMessage)
   {
     switch (aMessage) {
       case NOTIFY_IME_OF_SELECTION_CHANGE:
+        mSelectionChangeData.mOffset = UINT32_MAX;
+        mSelectionChangeData.mLength = 0;
+        mSelectionChangeData.mWritingMode = 0;
+        mSelectionChangeData.mReversed = false;
         mSelectionChangeData.mCausedByComposition = false;
         break;
       case NOTIFY_IME_OF_TEXT_CHANGE:
         mTextChangeData.mStartOffset = 0;
         mTextChangeData.mOldEndOffset = 0;
         mTextChangeData.mNewEndOffset = 0;
         mTextChangeData.mCausedByComposition = false;
         break;
@@ -613,23 +618,50 @@ struct IMENotification
         mMouseButtonEventData.mModifiers = 0;
       default:
         break;
     }
   }
 
   IMEMessage mMessage;
 
+  // NOTIFY_IME_OF_SELECTION_CHANGE specific data
+  struct SelectionChangeData
+  {
+    // Selection range.
+    uint32_t mOffset;
+    uint32_t mLength;
+
+    // Writing mode at the selection.
+    uint8_t mWritingMode;
+
+    bool mReversed;
+    bool mCausedByComposition;
+
+    void SetWritingMode(const WritingMode& aWritingMode);
+    WritingMode GetWritingMode() const;
+
+    uint32_t StartOffset() const
+    {
+      return mOffset + (mReversed ? mLength : 0);
+    }
+    uint32_t EndOffset() const
+    {
+      return mOffset + (mReversed ? 0 : mLength);
+    }
+    bool IsInInt32Range() const
+    {
+      return mOffset + mLength <= INT32_MAX;
+    }
+  };
+
   union
   {
     // NOTIFY_IME_OF_SELECTION_CHANGE specific data
-    struct
-    {
-      bool mCausedByComposition;
-    } mSelectionChangeData;
+    SelectionChangeData mSelectionChangeData;
 
     // NOTIFY_IME_OF_TEXT_CHANGE specific data
     struct
     {
       uint32_t mStartOffset;
       uint32_t mOldEndOffset;
       uint32_t mNewEndOffset;
 
--- a/widget/windows/WinIMEHandler.cpp
+++ b/widget/windows/WinIMEHandler.cpp
@@ -175,16 +175,17 @@ IMEHandler::NotifyIME(nsWindow* aWindow,
     switch (aIMENotification.mMessage) {
       case NOTIFY_IME_OF_SELECTION_CHANGE: {
         nsresult rv = nsTextStore::OnSelectionChange();
         // If IMM IME is active, we need to notify nsIMM32Handler of updating
         // composition change.  It will adjust candidate window position or
         // composition window position.
         if (IsIMMActive()) {
           nsIMM32Handler::OnUpdateComposition(aWindow);
+          nsIMM32Handler::OnSelectionChange(aWindow, aIMENotification);
         }
         return rv;
       }
       case NOTIFY_IME_OF_COMPOSITION_UPDATE:
         // If IMM IME is active, we need to notify nsIMM32Handler of updating
         // composition change.  It will adjust candidate window position or
         // composition window position.
         if (IsIMMActive()) {
@@ -233,16 +234,19 @@ IMEHandler::NotifyIME(nsWindow* aWindow,
       return NS_OK;
     case REQUEST_TO_CANCEL_COMPOSITION:
       nsIMM32Handler::CancelComposition(aWindow);
       return NS_OK;
     case NOTIFY_IME_OF_POSITION_CHANGE:
     case NOTIFY_IME_OF_COMPOSITION_UPDATE:
       nsIMM32Handler::OnUpdateComposition(aWindow);
       return NS_OK;
+    case NOTIFY_IME_OF_SELECTION_CHANGE:
+      nsIMM32Handler::OnSelectionChange(aWindow, aIMENotification);
+      return NS_OK;
     case NOTIFY_IME_OF_MOUSE_BUTTON_EVENT:
       return nsIMM32Handler::OnMouseButtonEvent(aWindow, aIMENotification);
 #ifdef NS_ENABLE_TSF
     case NOTIFY_IME_OF_BLUR:
       // If a plugin gets focus while TSF has focus, we need to notify TSF of
       // the blur.
       if (nsTextStore::ThinksHavingFocus()) {
         return nsTextStore::OnFocusChange(false, aWindow,
--- a/widget/windows/nsIMM32Handler.cpp
+++ b/widget/windows/nsIMM32Handler.cpp
@@ -11,23 +11,113 @@
 #include "nsWindowDefs.h"
 #include "WinUtils.h"
 #include "KeyboardLayout.h"
 #include <algorithm>
 
 #include "mozilla/MiscEvents.h"
 #include "mozilla/TextEvents.h"
 
+#ifndef IME_PROP_ACCEPT_WIDE_VKEY
+#define IME_PROP_ACCEPT_WIDE_VKEY 0x20
+#endif
+
 using namespace mozilla;
 using namespace mozilla::widget;
 
 static nsIMM32Handler* gIMM32Handler = nullptr;
 
 PRLogModuleInfo* gIMM32Log = nullptr;
 
+static void
+HandleSeparator(nsACString& aDesc)
+{
+  if (!aDesc.IsEmpty()) {
+    aDesc.AppendLiteral(" | ");
+  }
+}
+
+class GetIMEGeneralPropertyName : public nsAutoCString
+{
+public:
+  GetIMEGeneralPropertyName(DWORD aFlags)
+  {
+    if (!aFlags) {
+      AppendLiteral("no flags");
+      return;
+    }
+    if (aFlags & IME_PROP_AT_CARET) {
+      AppendLiteral("IME_PROP_AT_CARET");
+    }
+    if (aFlags & IME_PROP_SPECIAL_UI) {
+      HandleSeparator(*this);
+      AppendLiteral("IME_PROP_SPECIAL_UI");
+    }
+    if (aFlags & IME_PROP_CANDLIST_START_FROM_1) {
+      HandleSeparator(*this);
+      AppendLiteral("IME_PROP_CANDLIST_START_FROM_1");
+    }
+    if (aFlags & IME_PROP_UNICODE) {
+      HandleSeparator(*this);
+      AppendLiteral("IME_PROP_UNICODE");
+    }
+    if (aFlags & IME_PROP_COMPLETE_ON_UNSELECT) {
+      HandleSeparator(*this);
+      AppendLiteral("IME_PROP_COMPLETE_ON_UNSELECT");
+    }
+    if (aFlags & IME_PROP_ACCEPT_WIDE_VKEY) {
+      HandleSeparator(*this);
+      AppendLiteral("IME_PROP_ACCEPT_WIDE_VKEY");
+    }
+  }
+  virtual ~GetIMEGeneralPropertyName() {}
+};
+
+class GetIMEUIPropertyName : public nsAutoCString
+{
+public:
+  GetIMEUIPropertyName(DWORD aFlags)
+  {
+    if (!aFlags) {
+      AppendLiteral("no flags");
+      return;
+    }
+    if (aFlags & UI_CAP_2700) {
+      AppendLiteral("UI_CAP_2700");
+    }
+    if (aFlags & UI_CAP_ROT90) {
+      HandleSeparator(*this);
+      AppendLiteral("UI_CAP_ROT90");
+    }
+    if (aFlags & UI_CAP_ROTANY) {
+      HandleSeparator(*this);
+      AppendLiteral("UI_CAP_ROTANY");
+    }
+  }
+  virtual ~GetIMEUIPropertyName() {}
+};
+
+class GetWritingModeName : public nsAutoCString
+{
+public:
+  GetWritingModeName(const WritingMode& aWritingMode)
+  {
+    if (!aWritingMode.IsVertical()) {
+      Assign("Horizontal");
+      return;
+    }
+    if (aWritingMode.IsVerticalLR()) {
+      Assign("Vertical (LR)");
+      return;
+    }
+    Assign("Vertical (RL)");
+  }
+  virtual ~GetWritingModeName() {}
+};
+
 static UINT sWM_MSIME_MOUSE = 0; // mouse message for MSIME 98/2000
 
 //-------------------------------------------------------------------------
 //
 // from http://download.microsoft.com/download/6/0/9/60908e9e-d2c1-47db-98f6-216af76a235f/msime.h
 // The document for this has been removed from MSDN...
 //
 //-------------------------------------------------------------------------
@@ -36,18 +126,22 @@ static UINT sWM_MSIME_MOUSE = 0; // mous
 
 #define IMEMOUSE_NONE       0x00    // no mouse button was pushed
 #define IMEMOUSE_LDOWN      0x01
 #define IMEMOUSE_RDOWN      0x02
 #define IMEMOUSE_MDOWN      0x04
 #define IMEMOUSE_WUP        0x10    // wheel up
 #define IMEMOUSE_WDOWN      0x20    // wheel down
 
+WritingMode nsIMM32Handler::sWritingModeOfCompositionFont;
+nsString nsIMM32Handler::sIMEName;
 UINT nsIMM32Handler::sCodePage = 0;
 DWORD nsIMM32Handler::sIMEProperty = 0;
+DWORD nsIMM32Handler::sIMEUIProperty = 0;
+bool nsIMM32Handler::sAssumeVerticalWritingModeNotSupported = false;
 
 /* static */ void
 nsIMM32Handler::EnsureHandlerInstance()
 {
   if (!gIMM32Handler) {
     gIMM32Handler = new nsIMM32Handler();
   }
 }
@@ -56,17 +150,20 @@ nsIMM32Handler::EnsureHandlerInstance()
 nsIMM32Handler::Initialize()
 {
   if (!gIMM32Log)
     gIMM32Log = PR_NewLogModule("nsIMM32HandlerWidgets");
 
   if (!sWM_MSIME_MOUSE) {
     sWM_MSIME_MOUSE = ::RegisterWindowMessage(RWM_MOUSE);
   }
-  InitKeyboardLayout(::GetKeyboardLayout(0));
+  sAssumeVerticalWritingModeNotSupported =
+    Preferences::GetBool(
+      "intl.imm.vertical_writing.always_assume_not_supported", false);
+  InitKeyboardLayout(nullptr, ::GetKeyboardLayout(0));
 }
 
 /* static */ void
 nsIMM32Handler::Terminate()
 {
   if (!gIMM32Handler)
     return;
   delete gIMM32Handler;
@@ -96,52 +193,118 @@ nsIMM32Handler::IsTopLevelWindowOfCompos
 {
   if (!gIMM32Handler || !gIMM32Handler->mComposingWindow) {
     return false;
   }
   HWND wnd = gIMM32Handler->mComposingWindow->GetWindowHandle();
   return WinUtils::GetTopLevelHWND(wnd, true) == aWindow->GetWindowHandle();
 }
 
+/* static */
+bool
+nsIMM32Handler::IsJapanist2003Active()
+{
+  return sIMEName.EqualsLiteral("Japanist 2003");
+}
+
+/* static */ bool
+nsIMM32Handler::IsGoogleJapaneseInputActive()
+{
+  // NOTE: Even on Windows for en-US, the name of Google Japanese Input is
+  //       written in Japanese.
+  return sIMEName.Equals(L"Google \x65E5\x672C\x8A9E\x5165\x529B "
+                         L"IMM32 \x30E2\x30B8\x30E5\x30FC\x30EB");
+}
+
 /* static */ bool
 nsIMM32Handler::ShouldDrawCompositionStringOurselves()
 {
   // If current IME has special UI or its composition window should not
   // positioned to caret position, we should now draw composition string
   // ourselves.
   return !(sIMEProperty & IME_PROP_SPECIAL_UI) &&
           (sIMEProperty & IME_PROP_AT_CARET);
 }
 
+/* static */ bool
+nsIMM32Handler::IsVerticalWritingSupported()
+{
+  // Even if IME claims that they support vertical writing mode but it may not
+  // support vertical writing mode for its candidate window.
+  if (sAssumeVerticalWritingModeNotSupported) {
+    return false;
+  }
+  // Google Japanese Input doesn't support vertical writing mode.  We should
+  // return false if it's active IME.
+  if (IsGoogleJapaneseInputActive()) {
+    return false;
+  }
+  return !!(sIMEUIProperty & (UI_CAP_2700 | UI_CAP_ROT90 | UI_CAP_ROTANY));
+}
+
 /* static */ void
-nsIMM32Handler::InitKeyboardLayout(HKL aKeyboardLayout)
+nsIMM32Handler::InitKeyboardLayout(nsWindow* aWindow,
+                                   HKL aKeyboardLayout)
 {
+  UINT IMENameLength = ::ImmGetDescriptionW(aKeyboardLayout, nullptr, 0);
+  if (IMENameLength) {
+    // Add room for the terminating null character
+    sIMEName.SetLength(++IMENameLength);
+    IMENameLength =
+      ::ImmGetDescriptionW(aKeyboardLayout, sIMEName.BeginWriting(),
+                           IMENameLength);
+    // Adjust the length to ignore the terminating null character
+    sIMEName.SetLength(IMENameLength);
+  } else {
+    sIMEName.Truncate();
+  }
+
   WORD langID = LOWORD(aKeyboardLayout);
   ::GetLocaleInfoW(MAKELCID(langID, SORT_DEFAULT),
                    LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER,
                    (PWSTR)&sCodePage, sizeof(sCodePage) / sizeof(WCHAR));
   sIMEProperty = ::ImmGetProperty(aKeyboardLayout, IGP_PROPERTY);
+  sIMEUIProperty = ::ImmGetProperty(aKeyboardLayout, IGP_UI);
+
+  // If active IME is a TIP of TSF, we cannot retrieve the name with IMM32 API.
+  // For hacking some bugs of some TIP, we should set an IME name from the
+  // pref.
+  if (sCodePage == 932 && sIMEName.IsEmpty()) {
+    sIMEName =
+      Preferences::GetString("intl.imm.japanese.assume_active_tip_name_as");
+  }
+
+  // Whether the IME supports vertical writing mode might be changed or
+  // some IMEs may need specific font for their UI.  Therefore, we should
+  // update composition font forcibly here.
+  if (aWindow) {
+    MaybeAdjustCompositionFont(aWindow, sWritingModeOfCompositionFont, true);
+  }
+
   PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
-    ("IMM32: InitKeyboardLayout, aKeyboardLayout=%08x, sCodePage=%lu, "
-     "sIMEProperty=%08x",
-     aKeyboardLayout, sCodePage, sIMEProperty));
+    ("IMM32: InitKeyboardLayout, aKeyboardLayout=%08x (\"%s\"), sCodePage=%lu, "
+     "sIMEProperty=%s, sIMEUIProperty=%s",
+     aKeyboardLayout, NS_ConvertUTF16toUTF8(sIMEName).get(),
+     sCodePage, GetIMEGeneralPropertyName(sIMEProperty).get(),
+     GetIMEUIPropertyName(sIMEUIProperty).get()));
 }
 
 /* static */ UINT
 nsIMM32Handler::GetKeyboardCodePage()
 {
   return sCodePage;
 }
 
 /* static */
 nsIMEUpdatePreference
 nsIMM32Handler::GetIMEUpdatePreference()
 {
   return nsIMEUpdatePreference(
     nsIMEUpdatePreference::NOTIFY_POSITION_CHANGE |
+    nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE |
     nsIMEUpdatePreference::NOTIFY_MOUSE_BUTTON_EVENT_ON_CHAR);
 }
 
 // used for checking the lParam of WM_IME_COMPOSITION
 #define IS_COMPOSING_LPARAM(lParam) \
   ((lParam) & (GCS_COMPSTR | GCS_COMPATTR | GCS_COMPCLAUSE | GCS_CURSORPOS))
 #define IS_COMMITTING_LPARAM(lParam) ((lParam) & GCS_RESULTSTR)
 // Some IMEs (e.g., the standard IME for Korean) don't have caret position,
@@ -253,30 +416,67 @@ nsIMM32Handler::OnUpdateComposition(nsWi
   if (aWindow->PluginHasFocus()) {
     return;
   }
 
   nsIMEContext IMEContext(aWindow->GetWindowHandle());
   gIMM32Handler->SetIMERelatedWindowsPos(aWindow, IMEContext);
 }
 
+// static
+void
+nsIMM32Handler::OnSelectionChange(nsWindow* aWindow,
+                                  const IMENotification& aIMENotification)
+{
+  if (aIMENotification.mSelectionChangeData.mCausedByComposition) {
+    return;
+  }
+  MaybeAdjustCompositionFont(aWindow,
+    aIMENotification.mSelectionChangeData.GetWritingMode());
+}
+
+// static
+void
+nsIMM32Handler::MaybeAdjustCompositionFont(nsWindow* aWindow,
+                                           const WritingMode& aWritingMode,
+                                           bool aForceUpdate)
+{
+  switch (sCodePage) {
+    case 932: // Japanese Shift-JIS
+    case 936: // Simlified Chinese GBK
+    case 949: // Korean
+    case 950: // Traditional Chinese Big5
+      EnsureHandlerInstance();
+      break;
+    default:
+      // If there is no instance of nsIMM32Hander, we shouldn't waste footprint.
+      if (!gIMM32Handler) {
+        return;
+      }
+  }
+
+  // Like Navi-Bar of ATOK, some IMEs may require proper composition font even
+  // before sending WM_IME_STARTCOMPOSITION.
+  nsIMEContext IMEContext(aWindow->GetWindowHandle());
+  gIMM32Handler->AdjustCompositionFont(IMEContext, aWritingMode, aForceUpdate);
+}
 
 /* static */ bool
 nsIMM32Handler::ProcessInputLangChangeMessage(nsWindow* aWindow,
                                               WPARAM wParam,
                                               LPARAM lParam,
                                               MSGResult& aResult)
 {
   aResult.mResult = 0;
   aResult.mConsumed = false;
   // We don't need to create the instance of the handler here.
   if (gIMM32Handler) {
     gIMM32Handler->OnInputLangChange(aWindow, wParam, lParam, aResult);
   }
-  InitKeyboardLayout(reinterpret_cast<HKL>(lParam));
+  InitKeyboardLayout(aWindow, reinterpret_cast<HKL>(lParam));
   // We can release the instance here, because the instance may be never
   // used. E.g., the new keyboard layout may not use IME, or it may use TSF.
   Terminate();
   // Don't return as "processed", the messages should be processed on nsWindow
   // too.
   return false;
 }
 
@@ -760,16 +960,19 @@ nsIMM32Handler::OnIMEStartCompositionOnP
 {
   PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
     ("IMM32: OnIMEStartCompositionOnPlugin, hWnd=%08x, mIsComposingOnPlugin=%s\n",
      aWindow->GetWindowHandle(), mIsComposingOnPlugin ? "TRUE" : "FALSE"));
   mIsComposingOnPlugin = true;
   mComposingWindow = aWindow;
   nsIMEContext IMEContext(aWindow->GetWindowHandle());
   SetIMERelatedWindowsPosOnPlugin(aWindow, IMEContext);
+  // On widnowless plugin, we should assume that the focused editor is always
+  // in horizontal writing mode.
+  AdjustCompositionFont(IMEContext, WritingMode());
   aResult.mConsumed =
     aWindow->DispatchPluginEvent(WM_IME_STARTCOMPOSITION, wParam, lParam,
                                  false);
   return true;
 }
 
 bool
 nsIMM32Handler::OnIMECompositionOnPlugin(nsWindow* aWindow,
@@ -936,16 +1139,18 @@ nsIMM32Handler::HandleStartComposition(n
   aWindow->InitEvent(selection, &point);
   aWindow->DispatchWindowEvent(&selection);
   if (!selection.mSucceeded) {
     PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
       ("IMM32: HandleStartComposition, FAILED (NS_QUERY_SELECTED_TEXT)\n"));
     return;
   }
 
+  AdjustCompositionFont(aIMEContext, selection.GetWritingMode());
+
   mCompositionStart = selection.mReply.mOffset;
 
   WidgetCompositionEvent event(true, NS_COMPOSITION_START, aWindow);
   aWindow->InitEvent(event, &point);
   aWindow->DispatchWindowEvent(&event);
 
   mIsComposing = true;
   mComposingWindow = aWindow;
@@ -1346,28 +1551,56 @@ nsIMM32Handler::HandleQueryCharPosition(
     GetCharacterRectOfSelectedTextAt(aWindow, pCharPosition->dwCharPos, r);
   NS_ENSURE_TRUE(ret, false);
 
   nsIntRect screenRect;
   // We always need top level window that is owner window of the popup window
   // even if the content of the popup window has focus.
   ResolveIMECaretPos(aWindow->GetTopLevelWindow(false),
                      r, nullptr, screenRect);
+
+  // XXX This might need to check writing mode.  However, MSDN doesn't explain
+  //     how to set the values in vertical writing mode. Additionally, IME
+  //     doesn't work well with top-left of the character (this is explicitly
+  //     documented) and its horizontal width.  So, it might be better to set
+  //     top-right corner of the character and horizontal width, but we're not
+  //     sure if it doesn't cause any problems with a lot of IMEs...
   pCharPosition->pt.x = screenRect.x;
   pCharPosition->pt.y = screenRect.y;
 
   pCharPosition->cLineHeight = r.height;
 
-  // XXX we should use NS_QUERY_EDITOR_RECT event here.
-  ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument);
+  WidgetQueryContentEvent editorRect(true, NS_QUERY_EDITOR_RECT, aWindow);
+  aWindow->InitEvent(editorRect);
+  aWindow->DispatchWindowEvent(&editorRect);
+  if (NS_WARN_IF(!editorRect.mSucceeded)) {
+    PR_LOG(gIMM32Log, PR_LOG_ERROR,
+      ("IMM32: HandleQueryCharPosition, NS_QUERY_EDITOR_RECT failed"));
+    ::GetWindowRect(aWindow->GetWindowHandle(), &pCharPosition->rcDocument);
+  } else {
+    nsIntRect editorRectInWindow =
+      LayoutDevicePixel::ToUntyped(editorRect.mReply.mRect);
+    nsWindow* window = editorRect.mReply.mFocusedWidget ?
+      static_cast<nsWindow*>(editorRect.mReply.mFocusedWidget) : aWindow;
+    nsIntRect editorRectInScreen;
+    ResolveIMECaretPos(window, editorRectInWindow, nullptr, editorRectInScreen);
+    ::SetRect(&pCharPosition->rcDocument,
+              editorRectInScreen.x, editorRectInScreen.y,
+              editorRectInScreen.XMost(), editorRectInScreen.YMost());
+  }
 
   *oResult = TRUE;
 
   PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
-    ("IMM32: HandleQueryCharPosition, SUCCEEDED\n"));
+    ("IMM32: HandleQueryCharPosition, SUCCEEDED, pCharPosition={ pt={ x=%d, "
+     "y=%d }, cLineHeight=%d, rcDocument={ left=%d, top=%d, right=%d, "
+     "bottom=%d } }",
+     pCharPosition->pt.x, pCharPosition->pt.y, pCharPosition->cLineHeight,
+     pCharPosition->rcDocument.left, pCharPosition->rcDocument.top,
+     pCharPosition->rcDocument.right, pCharPosition->rcDocument.bottom));
   return true;
 }
 
 bool
 nsIMM32Handler::HandleDocumentFeed(nsWindow* aWindow,
                                    LPARAM lParam,
                                    LRESULT *oResult)
 {
@@ -1756,17 +1989,18 @@ nsIMM32Handler::ConvertToANSIString(cons
   ::WideCharToMultiByte(aCodePage, 0, (LPCWSTR)aStr.get(), aStr.Length(),
                         (LPSTR)aANSIStr.BeginWriting(), len, nullptr, nullptr);
   return true;
 }
 
 bool
 nsIMM32Handler::GetCharacterRectOfSelectedTextAt(nsWindow* aWindow,
                                                  uint32_t aOffset,
-                                                 nsIntRect &aCharRect)
+                                                 nsIntRect& aCharRect,
+                                                 WritingMode* aWritingMode)
 {
   nsIntPoint point(0, 0);
 
   WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow);
   aWindow->InitEvent(selection, &point);
   aWindow->DispatchWindowEvent(&selection);
   if (!selection.mSucceeded) {
     PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
@@ -1793,31 +2027,39 @@ nsIMM32Handler::GetCharacterRectOfSelect
   nsIntRect r;
   if (!useCaretRect) {
     WidgetQueryContentEvent charRect(true, NS_QUERY_TEXT_RECT, aWindow);
     charRect.InitForQueryTextRect(offset, 1);
     aWindow->InitEvent(charRect, &point);
     aWindow->DispatchWindowEvent(&charRect);
     if (charRect.mSucceeded) {
       aCharRect = LayoutDevicePixel::ToUntyped(charRect.mReply.mRect);
+      if (aWritingMode) {
+        *aWritingMode = charRect.GetWritingMode();
+      }
       PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
         ("IMM32: GetCharacterRectOfSelectedTextAt, aOffset=%lu, SUCCEEDED\n",
          aOffset));
       PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
-        ("IMM32: GetCharacterRectOfSelectedTextAt, aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }\n",
-         aCharRect.x, aCharRect.y, aCharRect.width, aCharRect.height));
+        ("IMM32: GetCharacterRectOfSelectedTextAt, "
+         "aCharRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
+         "charRect.GetWritingMode()=%s",
+         aCharRect.x, aCharRect.y, aCharRect.width, aCharRect.height,
+         GetWritingModeName(charRect.GetWritingMode()).get()));
       return true;
     }
   }
 
-  return GetCaretRect(aWindow, aCharRect);
+  return GetCaretRect(aWindow, aCharRect, aWritingMode);
 }
 
 bool
-nsIMM32Handler::GetCaretRect(nsWindow* aWindow, nsIntRect &aCaretRect)
+nsIMM32Handler::GetCaretRect(nsWindow* aWindow,
+                             nsIntRect& aCaretRect,
+                             WritingMode* aWritingMode)
 {
   nsIntPoint point(0, 0);
 
   WidgetQueryContentEvent selection(true, NS_QUERY_SELECTED_TEXT, aWindow);
   aWindow->InitEvent(selection, &point);
   aWindow->DispatchWindowEvent(&selection);
   if (!selection.mSucceeded) {
     PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
@@ -1832,30 +2074,37 @@ nsIMM32Handler::GetCaretRect(nsWindow* a
   aWindow->InitEvent(caretRect, &point);
   aWindow->DispatchWindowEvent(&caretRect);
   if (!caretRect.mSucceeded) {
     PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
       ("IMM32: GetCaretRect,  FAILED (NS_QUERY_CARET_RECT)\n"));
     return false;
   }
   aCaretRect = LayoutDevicePixel::ToUntyped(caretRect.mReply.mRect);
+  if (aWritingMode) {
+    *aWritingMode = caretRect.GetWritingMode();
+  }
   PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
-    ("IMM32: GetCaretRect, SUCCEEDED, aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }\n",
-     aCaretRect.x, aCaretRect.y, aCaretRect.width, aCaretRect.height));
+    ("IMM32: GetCaretRect, SUCCEEDED, "
+     "aCaretRect={ x: %ld, y: %ld, width: %ld, height: %ld }, "
+     "caretRect.GetWritingMode()=%s",
+     aCaretRect.x, aCaretRect.y, aCaretRect.width, aCaretRect.height,
+     GetWritingModeName(caretRect.GetWritingMode()).get()));
   return true;
 }
 
 bool
 nsIMM32Handler::SetIMERelatedWindowsPos(nsWindow* aWindow,
                                         const nsIMEContext &aIMEContext)
 {
   nsIntRect r;
   // Get first character rect of current a normal selected text or a composing
   // string.
-  bool ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r);
+  WritingMode writingMode;
+  bool ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r, &writingMode);
   NS_ENSURE_TRUE(ret, false);
   nsWindow* toplevelWindow = aWindow->GetTopLevelWindow(false);
   nsIntRect firstSelectedCharRect;
   ResolveIMECaretPos(toplevelWindow, r, aWindow, firstSelectedCharRect);
 
   // Set native caret size/position to our caret. Some IMEs honor it. E.g.,
   // "Intelligent ABC" (Simplified Chinese) and "MS PinYin 3.0" (Simplified
   // Chinese) on XP.
@@ -1876,59 +2125,117 @@ nsIMM32Handler::SetIMERelatedWindowsPos(
   }
   ::SetCaretPos(caretRect.x, caretRect.y);
 
   if (ShouldDrawCompositionStringOurselves()) {
     PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
       ("IMM32: SetIMERelatedWindowsPos, Set candidate window\n"));
 
     // Get a rect of first character in current target in composition string.
+    nsIntRect firstTargetCharRect, lastTargetCharRect;
     if (mIsComposing && !mCompositionString.IsEmpty()) {
       // If there are no targetted selection, we should use it's first character
       // rect instead.
-      uint32_t offset;
-      if (!GetTargetClauseRange(&offset)) {
+      uint32_t offset, length;
+      if (!GetTargetClauseRange(&offset, &length)) {
         PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
           ("IMM32: SetIMERelatedWindowsPos, FAILED, by GetTargetClauseRange\n"));
         return false;
       }
       ret = GetCharacterRectOfSelectedTextAt(aWindow,
-                                             offset - mCompositionStart, r);
+                                             offset - mCompositionStart,
+                                             firstTargetCharRect, &writingMode);
       NS_ENSURE_TRUE(ret, false);
+      if (length) {
+        ret = GetCharacterRectOfSelectedTextAt(aWindow,
+                offset + length - 1 - mCompositionStart, lastTargetCharRect);
+        NS_ENSURE_TRUE(ret, false);
+      } else {
+        lastTargetCharRect = firstTargetCharRect;
+      }
     } else {
       // If there are no composition string, we should use a first character
       // rect.
-      ret = GetCharacterRectOfSelectedTextAt(aWindow, 0, r);
+      ret = GetCharacterRectOfSelectedTextAt(aWindow, 0,
+                                             firstTargetCharRect, &writingMode);
       NS_ENSURE_TRUE(ret, false);
+      lastTargetCharRect = firstTargetCharRect;
     }
-    nsIntRect firstTargetCharRect;
-    ResolveIMECaretPos(toplevelWindow, r, aWindow, firstTargetCharRect);
+    ResolveIMECaretPos(toplevelWindow, firstTargetCharRect,
+                       aWindow, firstTargetCharRect);
+    ResolveIMECaretPos(toplevelWindow, lastTargetCharRect,
+                       aWindow, lastTargetCharRect);
+    nsIntRect targetClauseRect;
+    targetClauseRect.UnionRect(firstTargetCharRect, lastTargetCharRect);
 
-    // Move the candidate window to first character position of the target.
+    // Move the candidate window to proper position from the target clause as
+    // far as possible.
     CANDIDATEFORM candForm;
     candForm.dwIndex = 0;
-    candForm.dwStyle = CFS_EXCLUDE;
-    candForm.ptCurrentPos.x = firstTargetCharRect.x;
-    candForm.ptCurrentPos.y = firstTargetCharRect.y;
-    candForm.rcArea.right = candForm.rcArea.left = candForm.ptCurrentPos.x;
-    candForm.rcArea.top = candForm.ptCurrentPos.y;
-    candForm.rcArea.bottom = candForm.ptCurrentPos.y +
-                               firstTargetCharRect.height;
+    if (!writingMode.IsVertical() || IsVerticalWritingSupported()) {
+      candForm.dwStyle = CFS_EXCLUDE;
+      // Candidate window shouldn't overlap the target clause in any writing
+      // mode.
+      candForm.rcArea.left = targetClauseRect.x;
+      candForm.rcArea.right = targetClauseRect.XMost();
+      candForm.rcArea.top = targetClauseRect.y;
+      candForm.rcArea.bottom = targetClauseRect.YMost();
+      if (!writingMode.IsVertical()) {
+        // In horizontal layout, current point of interest should be top-left
+        // of the first character.
+        candForm.ptCurrentPos.x = firstTargetCharRect.x;
+        candForm.ptCurrentPos.y = firstTargetCharRect.y;
+      } else if (writingMode.IsVerticalRL()) {
+        // In vertical layout (RL), candidate window should be positioned right
+        // side of target clause.  However, we don't set vertical writing font
+        // to the IME.  Therefore, the candidate window may be positioned
+        // bottom-left of target clause rect with these information.
+        candForm.ptCurrentPos.x = targetClauseRect.x;
+        candForm.ptCurrentPos.y = targetClauseRect.y;
+      } else {
+        MOZ_ASSERT(writingMode.IsVerticalLR(), "Did we miss some causes?");
+        // In vertical layout (LR), candidate window should be poisitioned left
+        // side of target clause.  Although, we don't set vertical writing font
+        // to the IME, the candidate window may be positioned bottom-right of
+        // the target clause rect with these information.
+        candForm.ptCurrentPos.x = targetClauseRect.XMost();
+        candForm.ptCurrentPos.y = targetClauseRect.y;
+      }
+    } else {
+      // If vertical writing is not supported by IME, let's set candidate
+      // window position to the bottom-left of the target clause because
+      // the position must be the safest position to prevent the candidate
+      // window to overlap with the target clause.
+      candForm.dwStyle = CFS_CANDIDATEPOS;
+      candForm.ptCurrentPos.x = targetClauseRect.x;
+      candForm.ptCurrentPos.y = targetClauseRect.YMost();
+    }
+    PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
+      ("IMM32: SetIMERelatedWindowsPos, Calling ImmSetCandidateWindow()... "
+       "ptCurrentPos={ x=%d, y=%d }, "
+       "rcArea={ left=%d, top=%d, right=%d, bottom=%d }, "
+       "writingMode=%s",
+       candForm.ptCurrentPos.x, candForm.ptCurrentPos.y,
+       candForm.rcArea.left, candForm.rcArea.top,
+       candForm.rcArea.right, candForm.rcArea.bottom,
+       GetWritingModeName(writingMode).get()));
     ::ImmSetCandidateWindow(aIMEContext.get(), &candForm);
   } else {
     PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
       ("IMM32: SetIMERelatedWindowsPos, Set composition window\n"));
 
     // Move the composition window to caret position (if selected some
     // characters, we should use first character rect of them).
     // And in this mode, IME adjusts the candidate window position
     // automatically. So, we don't need to set it.
     COMPOSITIONFORM compForm;
     compForm.dwStyle = CFS_POINT;
-    compForm.ptCurrentPos.x = firstSelectedCharRect.x;
+    compForm.ptCurrentPos.x =
+      !writingMode.IsVerticalLR() ? firstSelectedCharRect.x :
+                                    firstSelectedCharRect.XMost();
     compForm.ptCurrentPos.y = firstSelectedCharRect.y;
     ::ImmSetCompositionWindow(aIMEContext.get(), &compForm);
   }
 
   return true;
 }
 
 void
@@ -2006,16 +2313,148 @@ nsIMM32Handler::ResolveIMECaretPos(nsIWi
 
   if (aReferenceWidget)
     aOutRect.MoveBy(aReferenceWidget->WidgetToScreenOffsetUntyped());
 
   if (aNewOriginWidget)
     aOutRect.MoveBy(-aNewOriginWidget->WidgetToScreenOffsetUntyped());
 }
 
+static void
+SetHorizontalFontToLogFont(const nsAString& aFontFace,
+                           LOGFONTW& aLogFont)
+{
+  aLogFont.lfEscapement = aLogFont.lfOrientation = 0;
+  if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 1)) {
+    memcpy(aLogFont.lfFaceName, L"System", sizeof(L"System"));
+    return;
+  }
+  memcpy(aLogFont.lfFaceName, aFontFace.BeginReading(),
+         aFontFace.Length() * sizeof(wchar_t));
+  aLogFont.lfFaceName[aFontFace.Length()] = 0;
+}
+
+static void
+SetVerticalFontToLogFont(const nsAString& aFontFace,
+                         LOGFONTW& aLogFont)
+{
+  aLogFont.lfEscapement = aLogFont.lfOrientation = 2700;
+  if (NS_WARN_IF(aFontFace.Length() > LF_FACESIZE - 2)) {
+    memcpy(aLogFont.lfFaceName, L"@System", sizeof(L"@System"));
+    return;
+  }
+  aLogFont.lfFaceName[0] = '@';
+  memcpy(&aLogFont.lfFaceName[1], aFontFace.BeginReading(),
+         aFontFace.Length() * sizeof(wchar_t));
+  aLogFont.lfFaceName[aFontFace.Length() + 1] = 0;
+}
+
+void
+nsIMM32Handler::AdjustCompositionFont(const nsIMEContext& aIMEContext,
+                                      const WritingMode& aWritingMode,
+                                      bool aForceUpdate)
+{
+  // An instance of nsIMM32Handler is destroyed when active IME is changed.
+  // Therefore, we need to store the information which are set to the IM
+  // context to static variables since IM context is never recreated.
+  static bool sCompositionFontsInitialized = false;
+  static nsString sCompositionFont =
+    Preferences::GetString("intl.imm.composition_font");
+
+  // If composition font is customized by pref, we need to modify the
+  // composition font of the IME context at first time even if the writing mode
+  // is horizontal.
+  bool setCompositionFontForcibly = aForceUpdate ||
+    (!sCompositionFontsInitialized && !sCompositionFont.IsEmpty());
+
+  static WritingMode sCurrentWritingMode;
+  static nsString sCurrentIMEName;
+  if (!setCompositionFontForcibly &&
+      sWritingModeOfCompositionFont == aWritingMode &&
+      sCurrentIMEName == sIMEName) {
+    // Nothing to do if writing mode isn't being changed.
+    return;
+  }
+
+  // Decide composition fonts for both horizontal writing mode and vertical
+  // writing mode.  If the font isn't specified by the pref, use default
+  // font which is already set to the IM context.  And also in vertical writing
+  // mode, insert '@' to the start of the font.
+  if (!sCompositionFontsInitialized) {
+    sCompositionFontsInitialized = true;
+    // sCompositionFontH must not start with '@' and its length is less than
+    // LF_FACESIZE since it needs to end with null terminating character.
+    if (sCompositionFont.IsEmpty() ||
+        sCompositionFont.Length() > LF_FACESIZE - 1 ||
+        sCompositionFont[0] == '@') {
+      LOGFONTW defaultLogFont;
+      if (NS_WARN_IF(!::ImmGetCompositionFont(aIMEContext.get(),
+                                              &defaultLogFont))) {
+        PR_LOG(gIMM32Log, PR_LOG_ERROR,
+          ("IMM32: AdjustCompositionFont, ::ImmGetCompositionFont() failed"));
+        sCompositionFont.AssignLiteral("System");
+      } else {
+        // The font face is typically, "System".
+        sCompositionFont.Assign(defaultLogFont.lfFaceName);
+      }
+    }
+
+    PR_LOG(gIMM32Log, PR_LOG_ALWAYS,
+      ("IMM32: AdjustCompositionFont, sCompositionFont=\"%s\" is initialized",
+       NS_ConvertUTF16toUTF8(sCompositionFont).get()));
+  }
+
+  static nsString sCompositionFontForJapanist2003;
+  if (IsJapanist2003Active() && sCompositionFontForJapanist2003.IsEmpty()) {
+    const char* kCompositionFontForJapanist2003 =
+      "intl.imm.composition_font.japanist_2003";
+    sCompositionFontForJapanist2003 =
+      Preferences::GetString(kCompositionFontForJapanist2003);
+    // If the font name is not specified properly, let's use
+    // "MS PGothic" instead.
+    if (sCompositionFontForJapanist2003.IsEmpty() ||
+        sCompositionFontForJapanist2003.Length() > LF_FACESIZE - 2 ||
+        sCompositionFontForJapanist2003[0] == '@') {
+      sCompositionFontForJapanist2003.AssignLiteral("MS PGothic");
+    }
+  }
+
+  sWritingModeOfCompositionFont = aWritingMode;
+  sCurrentIMEName = sIMEName;
+
+  LOGFONTW logFont;
+  memset(&logFont, 0, sizeof(logFont));
+  if (!::ImmGetCompositionFont(aIMEContext.get(), &logFont)) {
+    PR_LOG(gIMM32Log, PR_LOG_ERROR,
+      ("IMM32: AdjustCompositionFont, ::ImmGetCompositionFont() failed"));
+    logFont.lfFaceName[0] = 0;
+  }
+  // Need to reset some information which should be recomputed with new font.
+  logFont.lfWidth = 0;
+  logFont.lfWeight = FW_DONTCARE;
+  logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
+  logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
+  logFont.lfPitchAndFamily = DEFAULT_PITCH;
+
+  if (!mIsComposingOnPlugin &&
+      aWritingMode.IsVertical() && IsVerticalWritingSupported()) {
+    SetVerticalFontToLogFont(
+      IsJapanist2003Active() ? sCompositionFontForJapanist2003 :
+                               sCompositionFont, logFont);
+  } else {
+    SetHorizontalFontToLogFont(
+      IsJapanist2003Active() ? sCompositionFontForJapanist2003 :
+                               sCompositionFont, logFont);
+  }
+  PR_LOG(gIMM32Log, PR_LOG_WARNING,
+    ("IMM32: AdjustCompositionFont, calling ::ImmSetCompositionFont(\"%s\")",
+     NS_ConvertUTF16toUTF8(nsDependentString(logFont.lfFaceName)).get()));
+  ::ImmSetCompositionFontW(aIMEContext.get(), &logFont);
+}
+
 /* static */ nsresult
 nsIMM32Handler::OnMouseButtonEvent(nsWindow* aWindow,
                                    const IMENotification& aIMENotification)
 {
   // We don't need to create the instance of the handler here.
   if (!gIMM32Handler) {
     return NS_OK;
   }
--- a/widget/windows/nsIMM32Handler.h
+++ b/widget/windows/nsIMM32Handler.h
@@ -9,16 +9,17 @@
 #include "nscore.h"
 #include <windows.h>
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsIWidget.h"
 #include "mozilla/EventForwards.h"
 #include "nsRect.h"
+#include "WritingModes.h"
 
 class nsWindow;
 
 namespace mozilla {
 namespace widget {
 
 struct MSGResult;
 
@@ -138,33 +139,40 @@ public:
 #endif
 
   // If aForce is TRUE, these methods doesn't check whether we have composition
   // or not.  If you don't set it to TRUE, these method doesn't commit/cancel
   // the composition on uexpected window.
   static void CommitComposition(nsWindow* aWindow, bool aForce = false);
   static void CancelComposition(nsWindow* aWindow, bool aForce = false);
   static void OnUpdateComposition(nsWindow* aWindow);
+  static void OnSelectionChange(nsWindow* aWindow,
+                                const IMENotification& aIMENotification);
 
   static nsIMEUpdatePreference GetIMEUpdatePreference();
 
   // Returns NS_SUCCESS_EVENT_CONSUMED if the mouse button event is consumed by
   // IME.  Otherwise, NS_OK.
   static nsresult OnMouseButtonEvent(nsWindow* aWindow,
                                      const IMENotification& aIMENotification);
 
 protected:
   static void EnsureHandlerInstance();
 
   static bool IsComposingOnOurEditor();
   static bool IsComposingOnPlugin();
   static bool IsComposingWindow(nsWindow* aWindow);
 
+  static bool IsJapanist2003Active();
+  static bool IsGoogleJapaneseInputActive();
+
   static bool ShouldDrawCompositionStringOurselves();
-  static void InitKeyboardLayout(HKL aKeyboardLayout);
+  static bool IsVerticalWritingSupported();
+  // aWindow can be nullptr if it's called without receiving WM_INPUTLANGCHANGE.
+  static void InitKeyboardLayout(nsWindow* aWindow, HKL aKeyboardLayout);
   static UINT GetKeyboardCodePage();
 
   /**
    * Checks whether the window is top level window of the composing window.
    * In this method, the top level window means in all windows, not only in all
    * OUR windows.  I.e., if the aWindow is embedded, this always returns FALSE.
    */
   static bool IsTopLevelWindowOfComposition(nsWindow* aWindow);
@@ -270,23 +278,47 @@ protected:
   bool ConvertToANSIString(const nsAFlatString& aStr,
                              UINT aCodePage,
                              nsACString& aANSIStr);
 
   bool SetIMERelatedWindowsPos(nsWindow* aWindow,
                                const nsIMEContext& aIMEContext);
   void SetIMERelatedWindowsPosOnPlugin(nsWindow* aWindow,
                                        const nsIMEContext& aIMEContext);
-  bool GetCharacterRectOfSelectedTextAt(nsWindow* aWindow,
-                                          uint32_t aOffset,
-                                          nsIntRect &aCharRect);
-  bool GetCaretRect(nsWindow* aWindow, nsIntRect &aCaretRect);
+  bool GetCharacterRectOfSelectedTextAt(
+         nsWindow* aWindow,
+         uint32_t aOffset,
+         nsIntRect& aCharRect,
+         mozilla::WritingMode* aWritingMode = nullptr);
+  bool GetCaretRect(nsWindow* aWindow,
+                    nsIntRect& aCaretRect,
+                    mozilla::WritingMode* aWritingMode = nullptr);
   void GetCompositionString(const nsIMEContext &aIMEContext,
                             DWORD aIndex,
                             nsAString& aCompositionString) const;
+
+  /**
+   * AdjustCompositionFont() makes IME vertical writing mode if it's supported.
+   * If aForceUpdate is true, it will update composition font even if writing
+   * mode isn't being changed.
+   */
+  void AdjustCompositionFont(const nsIMEContext& aIMEContext,
+                             const mozilla::WritingMode& aWritingMode,
+                             bool aForceUpdate = false);
+
+  /**
+   * MaybeAdjustCompositionFont() calls AdjustCompositionFont() when the
+   * locale of active IME is CJK.  Note that this creates an instance even
+   * when there is no composition but the locale is CJK.
+   */
+  static void MaybeAdjustCompositionFont(
+                nsWindow* aWindow,
+                const mozilla::WritingMode& aWritingMode,
+                bool aForceUpdate = false);
+
   /**
    *  Get the current target clause of composition string.
    *  If there are one or more characters whose attribute is ATTR_TARGET_*,
    *  this returns the first character's offset and its length.
    *  Otherwise, e.g., the all characters are ATTR_INPUT, this returns
    *  the composition string range because the all is the current target.
    *
    *  aLength can be null (default), but aOffset must not be null.
@@ -354,13 +386,17 @@ protected:
 
   int32_t mCursorPosition;
   uint32_t mCompositionStart;
 
   bool mIsComposing;
   bool mIsComposingOnPlugin;
   bool mNativeCaretIsCreated;
 
+  static mozilla::WritingMode sWritingModeOfCompositionFont;
+  static nsString sIMEName;
   static UINT sCodePage;
   static DWORD sIMEProperty;
+  static DWORD sIMEUIProperty;
+  static bool sAssumeVerticalWritingModeNotSupported;
 };
 
 #endif // nsIMM32Handler_h__