Merge autoland to mozilla-central. a=merge
authorBogdan Tara <btara@mozilla.com>
Sat, 15 Sep 2018 00:47:21 +0300
changeset 492116 750e71a8f79b
parent 492090 28de549aaae2 (current diff)
parent 492115 368a1b20a52c (diff)
child 492146 87ae93caad9b
child 492235 18405f68c9e6
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
750e71a8f79b / 64.0a1 / 20180914220208 / files
nightly linux64
750e71a8f79b / 64.0a1 / 20180914220208 / files
nightly mac
750e71a8f79b / 64.0a1 / 20180914220208 / files
nightly win32
750e71a8f79b / 64.0a1 / 20180914220208 / files
nightly win64
750e71a8f79b / 64.0a1 / 20180914220208 / 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 autoland to mozilla-central. a=merge
testing/web-platform/meta/webdriver/tests/execute_async_script/collections.py.ini
toolkit/content/widgets/findbar.xml
toolkit/locales/en-US/chrome/global/findbar.dtd
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -1733,17 +1733,17 @@ var gMainPane = {
       possibleAppMenuItems.push(menuItem);
     }
     // Add gio handlers
     if (Cc["@mozilla.org/gio-service;1"]) {
       let gIOSvc = Cc["@mozilla.org/gio-service;1"].
                    getService(Ci.nsIGIOService);
       var gioApps = gIOSvc.getAppsForURIScheme(handlerInfo.type);
       let possibleHandlers = handlerInfo.possibleApplicationHandlers;
-      for (let handler of gioApps.enumerate()) {
+      for (let handler of gioApps.enumerate(Ci.nsIHandlerApp)) {
         // OS handler share the same name, it's most likely the same app, skipping...
         if (handler.name == handlerInfo.defaultDescription) {
           continue;
         }
         // Check if the handler is already in possibleHandlers
         let appAlreadyInHandlers = false;
         for (let i = possibleHandlers.length - 1; i >= 0; --i) {
           let app = possibleHandlers.queryElementAt(i, Ci.nsIHandlerApp);
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -1619,17 +1619,23 @@ var SessionStoreInternal = {
         () => {
           // Set up the list of promises that will signal a complete sessionstore
           // shutdown: either all data is saved, or we crashed or the message IPC
           // channel went away in the meantime.
           let promises = [this.flushAllWindowsAsync(progress)];
 
           const observeTopic = topic => {
             let deferred = PromiseUtils.defer();
-            const cleanup = () => Services.obs.removeObserver(deferred.resolve, topic);
+            const cleanup = () => {
+              try {
+                Services.obs.removeObserver(deferred.resolve, topic);
+              } catch (ex) {
+                Cu.reportError("SessionStore: exception whilst flushing all windows: " + ex);
+              }
+            };
             Services.obs.addObserver(subject => {
               // Skip abort on ipc:content-shutdown if not abnormal/crashed
               subject.QueryInterface(Ci.nsIPropertyBag2);
               if (!(topic == "ipc:content-shutdown" && !subject.get("abnormal"))) {
                 deferred.resolve();
               }
             }, topic);
             deferred.promise.then(cleanup, cleanup);
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -225,20 +225,19 @@
 }
 
 %include ../shared/toolbarbuttons.inc.css
 %include ../shared/toolbarbutton-icons.inc.css
 %include ../shared/menupanel.inc.css
 
 /* Override OSX-specific toolkit findbar button styles */
 .findbar-button {
-  background: none;
-  box-shadow: none;
+  background: none !important;
+  box-shadow: none !important;
   border: none;
-  color: inherit;
 }
 
 /* On Mac, native buttons keep their full opacity when they become disabled
  * and only the glyph or text on top of them becomes less opaque. */
 :root:not([customizing]) #back-button[disabled="true"] {
   opacity: 1 !important;
   /* Disabled toolbar buttons get an opacity of 0.4 which multiplies
    * their fill-opacity of 0.7. calc() doesn't work here - we'd need
--- a/dom/media/AutoplayPolicy.cpp
+++ b/dom/media/AutoplayPolicy.cpp
@@ -190,36 +190,36 @@ AutoplayPolicy::IsAllowedToPlay(const HT
 
   AUTOPLAY_LOG("IsAllowedToPlay, mediaElement=%p, isAllowToPlay=%s",
                 &aElement, AllowAutoplayToStr(result));
 
   return result;
 }
 
 /* static */ bool
-AutoplayPolicy::IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext)
+AutoplayPolicy::IsAllowedToPlay(const AudioContext& aContext)
 {
   if (!Preferences::GetBool("media.autoplay.block-webaudio", false)) {
     return true;
   }
 
   if (DefaultAutoplayBehaviour() == nsIAutoplay::ALLOWED) {
     return true;
   }
 
   if (!Preferences::GetBool("media.autoplay.enabled.user-gestures-needed", false)) {
     return true;
   }
 
   // Offline context won't directly output sound to audio devices.
-  if (aContext->IsOffline()) {
+  if (aContext.IsOffline()) {
     return true;
   }
 
-  if (IsWindowAllowedToPlay(aContext->GetOwner())) {
+  if (IsWindowAllowedToPlay(aContext.GetParentObject())) {
     return true;
   }
 
   return false;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/AutoplayPolicy.h
+++ b/dom/media/AutoplayPolicy.h
@@ -33,26 +33,26 @@ class AudioContext;
  * 3) Document's origin has the "autoplay-media" permission.
  */
 class AutoplayPolicy
 {
 public:
   // Returns whether a given media element is allowed to play.
   static bool IsAllowedToPlay(const HTMLMediaElement& aElement);
 
+  // Returns whether a given AudioContext is allowed to play.
+  static bool IsAllowedToPlay(const AudioContext& aContext);
+
   // Returns true if a given media element would be allowed to play
   // if block autoplay was enabled. If this returns false, it means we would
   // either block or ask for permission.
   // Note: this is for telemetry purposes, and doesn't check the prefs
   // which enable/disable block autoplay. Do not use for blocking logic!
   static bool WouldBeAllowedToPlayIfAutoplayDisabled(const HTMLMediaElement& aElement);
 
-  // Returns whether a given AudioContext is allowed to play.
-  static bool IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext);
-
   // Returns the AutoplayPermissionManager that a given document must request on
   // for autoplay permission.
   static already_AddRefed<AutoplayPermissionManager> RequestFor(
     const nsIDocument& aDocument);
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -3,21 +3,23 @@
 /* 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 "AudioContext.h"
 
 #include "blink/PeriodicWave.h"
 
+#include "mozilla/AutoplayPermissionManager.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/OwningNonNull.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs.h"
 
 #include "mozilla/dom/AnalyserNode.h"
 #include "mozilla/dom/AnalyserNodeBinding.h"
 #include "mozilla/dom/AudioBufferSourceNodeBinding.h"
 #include "mozilla/dom/AudioContextBinding.h"
 #include "mozilla/dom/BaseAudioContextBinding.h"
 #include "mozilla/dom/BiquadFilterNodeBinding.h"
 #include "mozilla/dom/ChannelMergerNodeBinding.h"
@@ -69,16 +71,21 @@
 #include "nsRFPService.h"
 #include "OscillatorNode.h"
 #include "PannerNode.h"
 #include "PeriodicWave.h"
 #include "ScriptProcessorNode.h"
 #include "StereoPannerNode.h"
 #include "WaveShaperNode.h"
 
+extern mozilla::LazyLogModule gAutoplayPermissionLog;
+
+#define AUTOPLAY_LOG(msg, ...)                                             \
+  MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
 namespace mozilla {
 namespace dom {
 
 // 0 is a special value that MediaStreams use to denote they are not part of a
 // AudioContext.
 static dom::AudioContext::AudioContextId gAudioContextId = 1;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(AudioContext)
@@ -148,42 +155,71 @@ AudioContext::AudioContext(nsPIDOMWindow
   , mCloseCalled(false)
   , mSuspendCalled(false)
   , mIsDisconnecting(false)
 {
   bool mute = aWindow->AddAudioContext(this);
 
   // Note: AudioDestinationNode needs an AudioContext that must already be
   // bound to the window.
-  bool allowedToStart = AutoplayPolicy::IsAudioContextAllowedToPlay(WrapNotNull(this));
+  bool allowedToStart = AutoplayPolicy::IsAllowedToPlay(*this);
   mDestination = new AudioDestinationNode(this,
                                           aIsOffline,
                                           allowedToStart,
                                           aNumberOfChannels,
                                           aLength,
                                           aSampleRate);
 
   // The context can't be muted until it has a destination.
   if (mute) {
     Mute();
   }
 
-  // If we won't allow audio context to start, we need to suspend all its stream
-  // in order to delay the state changing from 'suspend' to 'start'.
   if (!allowedToStart) {
-    ErrorResult rv;
-    RefPtr<Promise> dummy = Suspend(rv);
-    MOZ_ASSERT(!rv.Failed(), "can't create promise");
-    MOZ_ASSERT(dummy->State() != Promise::PromiseState::Rejected,
-               "suspend failed");
+    // Not allowed to start, delay the transition from `suspended` to `running`.
+    SuspendInternal(nullptr);
+    EnsureAutoplayRequested();
   }
 
   FFTBlock::MainThreadInit();
 }
 
+void
+AudioContext::EnsureAutoplayRequested()
+{
+  nsPIDOMWindowInner* parent = GetParentObject();
+  if (!parent || !parent->AsGlobal()) {
+    return;
+  }
+
+  RefPtr<AutoplayPermissionManager> request =
+    AutoplayPolicy::RequestFor(*(parent->GetExtantDoc()));
+  if (!request) {
+    return;
+  }
+
+  RefPtr<AudioContext> self = this;
+  request->RequestWithPrompt()
+    ->Then(parent->AsGlobal()->AbstractMainThreadFor(TaskCategory::Other),
+           __func__,
+           [ self, request ](
+             bool aApproved) {
+              AUTOPLAY_LOG("%p Autoplay request approved request=%p",
+                          self.get(),
+                          request.get());
+              self->ResumeInternal();
+           },
+           [self, request](nsresult aError) {
+              AUTOPLAY_LOG("%p Autoplay request denied request=%p",
+                          self.get(),
+                          request.get());
+              self->DispatchBlockedEvent();
+           });
+}
+
 nsresult
 AudioContext::Init()
 {
   if (!mIsOffline) {
     nsresult rv = mDestination->CreateAudioChannelAgent();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
@@ -925,21 +961,16 @@ AudioContext::OnStateChanged(void* aProm
             static_cast<int>(mAudioContextState), static_cast<int>(aNewState));
     MOZ_ASSERT(false);
   }
 
 #endif // DEBUG
 #endif // XP_MACOSX
 #endif // WIN32
 
-  MOZ_ASSERT(
-    mIsOffline || aPromise || aNewState == AudioContextState::Running,
-    "We should have a promise here if this is a real-time AudioContext."
-    "Or this is the first time we switch to \"running\".");
-
   if (aPromise) {
     Promise* promise = reinterpret_cast<Promise*>(aPromise);
     // It is possible for the promise to have been removed from
     // mPromiseGripArray if the cycle collector has severed our connections. DO
     // NOT dereference the promise pointer in that case since it may point to
     // already freed memory.
     if (mPromiseGripArray.Contains(promise)) {
       promise->MaybeResolveWithUndefined();
@@ -993,35 +1024,40 @@ AudioContext::Suspend(ErrorResult& aRv)
   }
 
   if (mAudioContextState == AudioContextState::Closed ||
       mCloseCalled) {
     promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
     return promise.forget();
   }
 
-  Destination()->Suspend();
+  mPromiseGripArray.AppendElement(promise);
+  SuspendInternal(promise);
+  return promise.forget();
+}
 
-  mPromiseGripArray.AppendElement(promise);
+void
+AudioContext::SuspendInternal(void* aPromise)
+{
+  Destination()->Suspend();
 
   nsTArray<MediaStream*> streams;
   // If mSuspendCalled is true then we already suspended all our streams,
   // so don't suspend them again (since suspend(); suspend(); resume(); should
   // cancel both suspends). But we still need to do ApplyAudioContextOperation
   // to ensure our new promise is resolved.
   if (!mSuspendCalled) {
     streams = GetAllStreams();
   }
   Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
                                       streams,
-                                      AudioContextOperation::Suspend, promise);
+                                      AudioContextOperation::Suspend,
+                                      aPromise);
 
   mSuspendCalled = true;
-
-  return promise.forget();
 }
 
 already_AddRefed<Promise>
 AudioContext::Resume(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(GetParentObject());
   RefPtr<Promise> promise;
   promise = Promise::Create(parentObject, aRv);
@@ -1037,36 +1073,80 @@ AudioContext::Resume(ErrorResult& aRv)
   if (mAudioContextState == AudioContextState::Closed ||
       mCloseCalled) {
     promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
     return promise.forget();
   }
 
   mPendingResumePromises.AppendElement(promise);
 
-  if (AutoplayPolicy::IsAudioContextAllowedToPlay(WrapNotNull(this))) {
-    Destination()->Resume();
-
-    nsTArray<MediaStream*> streams;
-    // If mSuspendCalled is false then we already resumed all our streams,
-    // so don't resume them again (since suspend(); resume(); resume(); should
-    // be OK). But we still need to do ApplyAudioContextOperation
-    // to ensure our new promise is resolved.
-    if (mSuspendCalled) {
-      streams = GetAllStreams();
-    }
-    Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
-                                        streams,
-                                        AudioContextOperation::Resume, promise);
-    mSuspendCalled = false;
+  const bool isAllowedToPlay = AutoplayPolicy::IsAllowedToPlay(*this);
+  if (isAllowedToPlay) {
+    ResumeInternal();
+  } else {
+    DispatchBlockedEvent();
   }
 
+  AUTOPLAY_LOG("Resume AudioContext %p, IsAllowedToPlay=%d",
+    this, isAllowedToPlay);
   return promise.forget();
 }
 
+void
+AudioContext::ResumeInternal()
+{
+  Destination()->Resume();
+
+  nsTArray<MediaStream*> streams;
+  // If mSuspendCalled is false then we already resumed all our streams,
+  // so don't resume them again (since suspend(); resume(); resume(); should
+  // be OK). But we still need to do ApplyAudioContextOperation
+  // to ensure our new promise is resolved.
+  if (mSuspendCalled) {
+    streams = GetAllStreams();
+  }
+  Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
+                                      streams,
+                                      AudioContextOperation::Resume,
+                                      nullptr);
+  mSuspendCalled = false;
+}
+
+void
+AudioContext::DispatchBlockedEvent()
+{
+  if (!StaticPrefs::MediaBlockEventEnabled()) {
+    return;
+  }
+
+  RefPtr<AudioContext> self = this;
+  RefPtr<nsIRunnable> r = NS_NewRunnableFunction(
+    "AudioContext::AutoplayBlocked",
+    [self] () {
+      nsPIDOMWindowInner* parent = self->GetParentObject();
+      if (!parent) {
+        return;
+      }
+
+      nsIDocument* doc = parent->GetExtantDoc();
+      if (!doc) {
+        return;
+      }
+
+      AUTOPLAY_LOG("Dispatch `blocked` event for AudioContext %p", self.get());
+      nsContentUtils::DispatchTrustedEvent(
+        doc,
+        static_cast<DOMEventTargetHelper*>(self),
+        NS_LITERAL_STRING("blocked"),
+        CanBubble::eNo,
+        Cancelable::eNo);
+  });
+  Dispatch(r.forget());
+}
+
 already_AddRefed<Promise>
 AudioContext::Close(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(GetParentObject());
   RefPtr<Promise> promise;
   promise = Promise::Create(parentObject, aRv);
   if (aRv.Failed()) {
     return nullptr;
--- a/dom/media/webaudio/AudioContext.h
+++ b/dom/media/webaudio/AudioContext.h
@@ -336,16 +336,25 @@ private:
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
   NS_DECL_NSIMEMORYREPORTER
 
   friend struct ::mozilla::WebAudioDecodeJob;
 
   nsTArray<MediaStream*> GetAllStreams() const;
 
+  // Request the prompt to ask for user's approval for autoplay.
+  void EnsureAutoplayRequested();
+
+  void ResumeInternal();
+  void SuspendInternal(void* aPromise);
+
+  // This event is used for testing only.
+  void DispatchBlockedEvent();
+
 private:
   // Each AudioContext has an id, that is passed down the MediaStreams that
   // back the AudioNodes, so we can easily compute the set of all the
   // MediaStreams for a given context, on the MediasStreamGraph side.
   const AudioContextId mId;
   // Note that it's important for mSampleRate to be initialized before
   // mDestination, as mDestination's constructor needs to access it!
   const float mSampleRate;
--- a/editor/libeditor/HTMLEditor.cpp
+++ b/editor/libeditor/HTMLEditor.cpp
@@ -2031,41 +2031,47 @@ nsresult
 HTMLEditor::GetHTMLBackgroundColorState(bool* aMixed,
                                         nsAString& aOutColor)
 {
   //TODO: We don't handle "mixed" correctly!
   NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
   *aMixed = false;
   aOutColor.Truncate();
 
-  RefPtr<Element> element;
-  int32_t selectedCount;
-  nsAutoString tagName;
-  nsresult rv = GetSelectedOrParentTableElement(tagName,
-                                                &selectedCount,
-                                                getter_AddRefs(element));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  while (element) {
+  RefPtr<Selection> selection = GetSelection();
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  ErrorResult error;
+  RefPtr<Element> cellOrRowOrTableElement =
+    GetSelectedOrParentTableElement(*selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+
+  for (RefPtr<Element> element = std::move(cellOrRowOrTableElement);
+       element;
+       element = element->GetParentElement()) {
     // We are in a cell or selected table
     element->GetAttr(kNameSpaceID_None, nsGkAtoms::bgcolor, aOutColor);
 
     // Done if we have a color explicitly set
     if (!aOutColor.IsEmpty()) {
       return NS_OK;
     }
 
     // Once we hit the body, we're done
     if (element->IsHTMLElement(nsGkAtoms::body)) {
       return NS_OK;
     }
 
     // No color is set, but we need to report visible color inherited
-    // from nested cells/tables, so search up parent chain
-    element = element->GetParentElement();
+    // from nested cells/tables, so search up parent chain so that
+    // let's keep checking the ancestors.
   }
 
   // If no table or cell found, get page body
   dom::Element* bodyElement = GetRoot();
   NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER);
 
   bodyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::bgcolor, aOutColor);
   return NS_OK;
@@ -2985,72 +2991,85 @@ HTMLEditor::InsertLinkAroundSelection(El
   return NS_OK;
 }
 
 nsresult
 HTMLEditor::SetHTMLBackgroundColorWithTransaction(const nsAString& aColor)
 {
   MOZ_ASSERT(IsInitialized(), "The HTMLEditor hasn't been initialized yet");
 
+  RefPtr<Selection> selection = GetSelection();
+  if (NS_WARN_IF(!selection)) {
+    return NS_ERROR_FAILURE;
+  }
+
   // Find a selected or enclosing table element to set background on
-  RefPtr<Element> element;
-  int32_t selectedCount;
-  nsAutoString tagName;
-  nsresult rv = GetSelectedOrParentTableElement(tagName, &selectedCount,
-                                                getter_AddRefs(element));
-  NS_ENSURE_SUCCESS(rv, rv);
+  ErrorResult error;
+  bool isCellSelected = false;
+  RefPtr<Element> cellOrRowOrTableElement =
+    GetSelectedOrParentTableElement(*selection, error, &isCellSelected);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
 
   bool setColor = !aColor.IsEmpty();
-
-  RefPtr<nsAtom> bgColorAtom = NS_Atomize("bgcolor");
-  if (element) {
-    if (selectedCount > 0) {
-      RefPtr<Selection> selection = GetSelection();
-      if (NS_WARN_IF(!selection)) {
-        return NS_ERROR_FAILURE;
-      }
+  RefPtr<Element> rootElementOfBackgroundColor;
+  if (cellOrRowOrTableElement) {
+    rootElementOfBackgroundColor = std::move(cellOrRowOrTableElement);
+    // Needs to set or remove background color of each selected cell elements.
+    // Therefore, just the cell contains selection range, we don't need to
+    // do this.  Note that users can select each cell, but with Selection API,
+    // web apps can select <tr> and <td> at same time. With <table>, looks
+    // odd, though.
+    if (isCellSelected ||
+        cellOrRowOrTableElement->IsAnyOfHTMLElements(nsGkAtoms::table,
+                                                    nsGkAtoms::tr)) {
       IgnoredErrorResult ignoredError;
       RefPtr<Element> cellElement =
         GetFirstSelectedTableCellElement(*selection, ignoredError);
       if (cellElement) {
         if (setColor) {
           while (cellElement) {
-            rv =
-              SetAttributeWithTransaction(*cellElement, *bgColorAtom, aColor);
+            nsresult rv =
+              SetAttributeWithTransaction(*cellElement, *nsGkAtoms::bgcolor,
+                                          aColor);
             if (NS_WARN_IF(NS_FAILED(rv))) {
               return rv;
             }
             cellElement =
               GetNextSelectedTableCellElement(*selection, ignoredError);
           }
           return NS_OK;
         }
         while (cellElement) {
-          rv = RemoveAttributeWithTransaction(*cellElement, *bgColorAtom);
+          nsresult rv =
+            RemoveAttributeWithTransaction(*cellElement, *nsGkAtoms::bgcolor);
           if (NS_FAILED(rv)) {
             return rv;
           }
           cellElement =
             GetNextSelectedTableCellElement(*selection, ignoredError);
         }
         return NS_OK;
       }
     }
     // If we failed to find a cell, fall through to use originally-found element
   } else {
     // No table element -- set the background color on the body tag
-    element = GetRoot();
-    if (NS_WARN_IF(!element)) {
+    rootElementOfBackgroundColor = GetRoot();
+    if (NS_WARN_IF(!rootElementOfBackgroundColor)) {
       return NS_ERROR_FAILURE;
     }
   }
   // Use the editor method that goes through the transaction system
   return setColor ?
-           SetAttributeWithTransaction(*element, *bgColorAtom, aColor) :
-           RemoveAttributeWithTransaction(*element, *bgColorAtom);
+           SetAttributeWithTransaction(*rootElementOfBackgroundColor,
+                                       *nsGkAtoms::bgcolor, aColor) :
+           RemoveAttributeWithTransaction(*rootElementOfBackgroundColor,
+                                          *nsGkAtoms::bgcolor);
 }
 
 NS_IMETHODIMP
 HTMLEditor::GetLinkedObjects(nsIArray** aNodeList)
 {
   NS_ENSURE_TRUE(aNodeList, NS_ERROR_NULL_POINTER);
 
   nsresult rv;
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -1086,16 +1086,18 @@ protected: // Shouldn't be used by frien
    * @param aRv                 Returns error.  If given element is <tr> but
    *                            there is no next <tr> element, this returns
    *                            nullptr but does not return error.
    */
   Element*
   GetNextTableRowElement(Element& aTableRowElement,
                          ErrorResult& aRv) const;
 
+  struct CellAndIndexes;
+
   /**
    * CellIndexes store both row index and column index of a table cell.
    */
   struct MOZ_STACK_CLASS CellIndexes final
   {
     int32_t mRow;
     int32_t mColumn;
 
@@ -1143,16 +1145,52 @@ protected: // Shouldn't be used by frien
     /**
      * Update mRowIndex and mColumnIndex with indexes of cell element which
      * contains anchor of Selection.
      *
      * @param                   See above.
      */
     void Update(HTMLEditor& aHTMLEditor, Selection& aSelection,
                 ErrorResult& aRv);
+
+  private:
+    CellIndexes()
+      : mRow(-1)
+      , mColumn(-1)
+    {
+    }
+
+    friend struct CellAndIndexes;
+  };
+
+  struct MOZ_STACK_CLASS CellAndIndexes final
+  {
+    RefPtr<Element> mElement;
+    CellIndexes mIndexes;
+
+    /**
+     * This constructor initializes the members with cell element which is
+     * selected by first range of the Selection.  Note that even if the
+     * first range is in the cell element, this does not treat it as the
+     * cell element is selected.
+     */
+    CellAndIndexes(HTMLEditor& aHTMLEditor, Selection& aSelection,
+                   ErrorResult& aRv)
+    {
+      Update(aHTMLEditor, aSelection, aRv);
+    }
+
+    /**
+     * Update mElement and mIndexes with cell element which is selected by
+     * first range of the Selection.  Note that even if the first range is
+     * in the cell element, this does not treat it as the cell element is
+     * selected.
+     */
+    void Update(HTMLEditor& aHTMLEditor, Selection& aSelection,
+                ErrorResult& aRv);
   };
 
   /**
    * TableSize stores and computes number of rows and columns of a <table>
    * element.
    */
   struct MOZ_STACK_CLASS TableSize final
   {
@@ -1208,16 +1246,36 @@ protected: // Shouldn't be used by frien
     return GetTableCellElementAt(aTableElement, aCellIndexes.mRow,
                                  aCellIndexes.mColumn);
   }
   Element* GetTableCellElementAt(Element& aTableElement,
                                  int32_t aRowIndex,
                                  int32_t aColumnIndex) const;
 
   /**
+   * GetSelectedOrParentTableElement() returns <td>, <th>, <tr> or <table>
+   * element:
+   *   #1 if the first selection range selects a cell, returns it.
+   *   #2 if the first selection range does not select a cell and
+   *      the selection anchor refers a <table>, returns it.
+   *   #3 if the first selection range does not select a cell and
+   *      the selection anchor refers a <tr>, returns it.
+   *   #4 if the first selection range does not select a cell and
+   *      the selection anchor refers a <td>, returns it.
+   *   #5 otherwise, nearest ancestor <td> or <th> element of the
+   *      selection anchor if there is.
+   * In #1 and #4, *aIsCellSelected will be set to true (i.e,, when
+   * a selection range selects a cell element).
+   */
+  already_AddRefed<Element>
+  GetSelectedOrParentTableElement(Selection& aSelection,
+                                  ErrorResult& aRv,
+                                  bool* aIsCellSelected = nullptr) const;
+
+  /**
    * PasteInternal() pasts text with replacing selected content.
    * This tries to dispatch ePaste event first.  If its defaultPrevent() is
    * called, this does nothing but returns NS_OK.
    *
    * @param aClipboardType  nsIClipboard::kGlobalClipboard or
    *                        nsIClipboard::kSelectionClipboard.
    */
   nsresult PasteInternal(int32_t aClipboardType);
--- a/editor/libeditor/HTMLTableEditor.cpp
+++ b/editor/libeditor/HTMLTableEditor.cpp
@@ -2193,25 +2193,24 @@ HTMLEditor::JoinTableCells(bool aMergeNo
   //  is retained after joining. This leaves the target cell selected
   //  as well as the "non-contiguous" cells, so user can see what happened.
 
   RefPtr<Selection> selection = GetSelection();
   if (NS_WARN_IF(!selection)) {
     return NS_ERROR_FAILURE;
   }
 
-  RefPtr<Element> firstCell;
-  int32_t firstRowIndex, firstColIndex;
-  rv = GetFirstSelectedCellInTable(&firstRowIndex, &firstColIndex,
-                                   getter_AddRefs(firstCell));
-  NS_ENSURE_SUCCESS(rv, rv);
-
   ErrorResult error;
+  CellAndIndexes firstSelectedCell(*this, *selection, error);
+  if (NS_WARN_IF(error.Failed())) {
+    return error.StealNSResult();
+  }
+
   bool joinSelectedCells = false;
-  if (firstCell) {
+  if (firstSelectedCell.mElement) {
     RefPtr<Element> secondCell =
       GetNextSelectedTableCellElement(*selection, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
 
     // If only one cell is selected, join with cell to the right
     joinSelectedCells = (secondCell != nullptr);
@@ -2222,88 +2221,96 @@ HTMLEditor::JoinTableCells(bool aMergeNo
     //  and just merge contents if not contiguous
     TableSize tableSize(*this, *table, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
 
     // Get spans for cell we will merge into
     int32_t firstRowSpan, firstColSpan;
-    rv = GetCellSpansAt(table, firstRowIndex, firstColIndex,
+    rv = GetCellSpansAt(table,
+                        firstSelectedCell.mIndexes.mRow,
+                        firstSelectedCell.mIndexes.mColumn,
                         firstRowSpan, firstColSpan);
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
 
     // This defines the last indexes along the "edges"
     //  of the contiguous block of cells, telling us
     //  that we can join adjacent cells to the block
     // Start with same as the first values,
     //  then expand as we find adjacent selected cells
-    int32_t lastRowIndex = firstRowIndex;
-    int32_t lastColIndex = firstColIndex;
+    int32_t lastRowIndex = firstSelectedCell.mIndexes.mRow;
+    int32_t lastColIndex = firstSelectedCell.mIndexes.mColumn;
     int32_t rowIndex, colIndex;
 
     // First pass: Determine boundaries of contiguous rectangular block
     //  that we will join into one cell,
     //  favoring adjacent cells in the same row
-    for (rowIndex = firstRowIndex; rowIndex <= lastRowIndex; rowIndex++) {
+    for (rowIndex = firstSelectedCell.mIndexes.mRow;
+         rowIndex <= lastRowIndex;
+         rowIndex++) {
       int32_t currentRowCount = tableSize.mRowCount;
       // Be sure each row doesn't have rowspan errors
       rv = FixBadRowSpan(table, rowIndex, tableSize.mRowCount);
       NS_ENSURE_SUCCESS(rv, rv);
       // Adjust rowcount by number of rows we removed
       lastRowIndex -= currentRowCount - tableSize.mRowCount;
 
       bool cellFoundInRow = false;
       bool lastRowIsSet = false;
       int32_t lastColInRow = 0;
-      int32_t firstColInRow = firstColIndex;
-      for (colIndex = firstColIndex;
+      int32_t firstColInRow = firstSelectedCell.mIndexes.mColumn;
+      for (colIndex = firstSelectedCell.mIndexes.mColumn;
            colIndex < tableSize.mColumnCount;
            colIndex += std::max(actualColSpan2, 1)) {
         rv = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2),
                            &startRowIndex2, &startColIndex2,
                            &rowSpan2, &colSpan2,
                            &actualRowSpan2, &actualColSpan2, &isSelected2);
         NS_ENSURE_SUCCESS(rv, rv);
 
         if (isSelected2) {
           if (!cellFoundInRow) {
             // We've just found the first selected cell in this row
             firstColInRow = colIndex;
           }
-          if (rowIndex > firstRowIndex && firstColInRow != firstColIndex) {
+          if (rowIndex > firstSelectedCell.mIndexes.mRow &&
+              firstColInRow != firstSelectedCell.mIndexes.mColumn) {
             // We're in at least the second row,
             // but left boundary is "ragged" (not the same as 1st row's start)
             //Let's just end block on previous row
             // and keep previous lastColIndex
             //TODO: We could try to find the Maximum firstColInRow
             //      so our block can still extend down more rows?
             lastRowIndex = std::max(0,rowIndex - 1);
             lastRowIsSet = true;
             break;
           }
           // Save max selected column in this row, including extra colspan
           lastColInRow = colIndex + (actualColSpan2-1);
           cellFoundInRow = true;
         } else if (cellFoundInRow) {
           // No cell or not selected, but at least one cell in row was found
-          if (rowIndex > (firstRowIndex + 1) && colIndex <= lastColIndex) {
+          if (rowIndex > firstSelectedCell.mIndexes.mRow + 1 &&
+              colIndex <= lastColIndex) {
             // Cell is in a column less than current right border in
             //  the third or higher selected row, so stop block at the previous row
             lastRowIndex = std::max(0,rowIndex - 1);
             lastRowIsSet = true;
           }
           // We're done with this row
           break;
         }
       } // End of column loop
 
       // Done with this row
       if (cellFoundInRow) {
-        if (rowIndex == firstRowIndex) {
+        if (rowIndex == firstSelectedCell.mIndexes.mRow) {
           // First row always initializes the right boundary
           lastColIndex = lastColInRow;
         }
 
         // If we didn't determine last row above...
         if (!lastRowIsSet) {
           if (colIndex < lastColIndex) {
             // (don't think we ever get here?)
@@ -2339,19 +2346,21 @@ HTMLEditor::JoinTableCells(bool aMergeNo
         NS_ENSURE_SUCCESS(rv, rv);
 
         // If this is 0, we are past last cell in row, so exit the loop
         if (!actualColSpan2) {
           break;
         }
 
         // Merge only selected cells (skip cell we're merging into, of course)
-        if (isSelected2 && cell2 != firstCell) {
-          if (rowIndex >= firstRowIndex && rowIndex <= lastRowIndex &&
-              colIndex >= firstColIndex && colIndex <= lastColIndex) {
+        if (isSelected2 && cell2 != firstSelectedCell.mElement) {
+          if (rowIndex >= firstSelectedCell.mIndexes.mRow &&
+              rowIndex <= lastRowIndex &&
+              colIndex >= firstSelectedCell.mIndexes.mColumn &&
+              colIndex <= lastColIndex) {
             // We are within the join region
             // Problem: It is very tricky to delete cells as we merge,
             //  since that will upset the cellmap
             //  Instead, build a list of cells to delete and do it later
             NS_ASSERTION(startRowIndex2 == rowIndex, "JoinTableCells: StartRowIndex is in row above");
 
             if (actualColSpan2 > 1) {
               //Check if cell "hangs" off the boundary because of colspan > 1
@@ -2360,25 +2369,29 @@ HTMLEditor::JoinTableCells(bool aMergeNo
               if ( extraColSpan > 0) {
                 rv = SplitCellIntoColumns(table, startRowIndex2, startColIndex2,
                                           actualColSpan2 - extraColSpan,
                                           extraColSpan, nullptr);
                 NS_ENSURE_SUCCESS(rv, rv);
               }
             }
 
-            rv = MergeCells(firstCell, cell2, false);
-            NS_ENSURE_SUCCESS(rv, rv);
+            rv = MergeCells(firstSelectedCell.mElement, cell2, false);
+            if (NS_WARN_IF(NS_FAILED(rv))) {
+              return rv;
+            }
 
             // Add cell to list to delete
             deleteList.AppendElement(cell2.get());
           } else if (aMergeNonContiguousContents) {
             // Cell is outside join region -- just merge the contents
-            rv = MergeCells(firstCell, cell2, false);
-            NS_ENSURE_SUCCESS(rv, rv);
+            rv = MergeCells(firstSelectedCell.mElement, cell2, false);
+            if (NS_WARN_IF(NS_FAILED(rv))) {
+              return rv;
+            }
           }
         }
       }
     }
 
     // All cell contents are merged. Delete the empty cells we accumulated
     // Prevent rules testing until we're done
     AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
@@ -2409,22 +2422,27 @@ HTMLEditor::JoinTableCells(bool aMergeNo
       HTMLEditor::GetCellFromRange(range, getter_AddRefs(deletedCell));
       if (!deletedCell) {
         selection->RemoveRange(*range, IgnoreErrors());
         rangeCount--;
         i--;
       }
     }
 
-    // Set spans for the cell everthing merged into
-    rv = SetRowSpan(firstCell, lastRowIndex-firstRowIndex+1);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = SetColSpan(firstCell, lastColIndex-firstColIndex+1);
-    NS_ENSURE_SUCCESS(rv, rv);
-
+    // Set spans for the cell everything merged into
+    rv = SetRowSpan(firstSelectedCell.mElement,
+                    lastRowIndex - firstSelectedCell.mIndexes.mRow + 1);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    rv = SetColSpan(firstSelectedCell.mElement,
+                    lastColIndex - firstSelectedCell.mIndexes.mColumn + 1);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
 
     // Fixup disturbances in table layout
     DebugOnly<nsresult> rv = NormalizeTable(*selection, *table);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to normalize the table");
   } else {
     // Joining with cell to the right -- get rowspan and colspan data of target cell
     rv = GetCellDataAt(table, startRowIndex, startColIndex,
                        getter_AddRefs(targetCell),
@@ -3215,37 +3233,39 @@ HTMLEditor::GetCellContext(Selection** a
     cell = *aCell;
   }
 
   // ...but if not supplied,
   //    get cell if it's the child of selection anchor node,
   //    or get the enclosing by a cell
   if (!cell) {
     // Find a selected or enclosing table element
-    RefPtr<Element> cellOrTableElement;
-    int32_t selectedCount;
-    nsAutoString tagName;
-    nsresult rv =
-      GetSelectedOrParentTableElement(tagName, &selectedCount,
-                                      getter_AddRefs(cellOrTableElement));
-    NS_ENSURE_SUCCESS(rv, rv);
-    if (tagName.EqualsLiteral("table")) {
+    ErrorResult error;
+    RefPtr<Element> cellOrRowOrTableElement =
+      GetSelectedOrParentTableElement(*selection, error);
+    if (NS_WARN_IF(error.Failed())) {
+      return error.StealNSResult();
+    }
+    if (!cellOrRowOrTableElement) {
+      return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
+    }
+    if (cellOrRowOrTableElement->IsHTMLElement(nsGkAtoms::table)) {
       // We have a selected table, not a cell
       if (aTable) {
-        cellOrTableElement.forget(aTable);
+        cellOrRowOrTableElement.forget(aTable);
       }
       return NS_OK;
     }
-    if (!tagName.EqualsLiteral("td")) {
+    if (!cellOrRowOrTableElement->IsAnyOfHTMLElements(nsGkAtoms::td,
+                                                      nsGkAtoms::th)) {
       return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
     }
 
     // We found a cell
-    MOZ_ASSERT(cellOrTableElement);
-    cell = cellOrTableElement;
+    cell = std::move(cellOrRowOrTableElement);
   }
   if (aCell) {
     // we don't want to cell.forget() here, because we use it below.
     *aCell = do_AddRef(cell).take();
   }
 
   // Get containing table
   table = GetElementOrParentByTagNameInternal(*nsGkAtoms::table, *cell);
@@ -3493,61 +3513,65 @@ HTMLEditor::GetNextSelectedTableCellElem
 
   // Returns nullptr without error if not found.
   return nullptr;
 }
 
 NS_IMETHODIMP
 HTMLEditor::GetFirstSelectedCellInTable(int32_t* aRowIndex,
                                         int32_t* aColumnIndex,
-                                        Element** aCell)
+                                        Element** aCellElement)
 {
-  NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER);
-  *aCell = nullptr;
-  if (aRowIndex) {
-    *aRowIndex = 0;
+  if (NS_WARN_IF(!aRowIndex) || NS_WARN_IF(!aColumnIndex) ||
+      NS_WARN_IF(!aCellElement)) {
+    return NS_ERROR_INVALID_ARG;
   }
-  if (aColumnIndex) {
-    *aColumnIndex = 0;
-  }
+  
+
+  *aRowIndex = 0;
+  *aColumnIndex = 0;
+  *aCellElement = nullptr;
 
   RefPtr<Selection> selection = GetSelection();
   if (NS_WARN_IF(!selection)) {
     return NS_ERROR_FAILURE;
   }
 
   ErrorResult error;
-  RefPtr<Element> firstSelectedCellElement =
-    GetFirstSelectedTableCellElement(*selection, error);
+  CellAndIndexes result(*this, *selection, error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
-  if (NS_WARN_IF(!firstSelectedCellElement)) {
-    return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
-  }
-
-  // We don't want to cell.forget() here, because we use "cell" below.
-  firstSelectedCellElement.forget(aCell);
-
-  if (!aRowIndex && !aColumnIndex) {
-    return NS_OK;
+  result.mElement.forget(aCellElement);
+  *aRowIndex = std::max(result.mIndexes.mRow, 0);
+  *aColumnIndex = std::max(result.mIndexes.mColumn, 0);
+  return NS_OK;
+}
+
+void
+HTMLEditor::CellAndIndexes::Update(HTMLEditor& aHTMLEditor,
+                                   Selection& aSelection,
+                                   ErrorResult& aRv)
+{
+  MOZ_ASSERT(!aRv.Failed());
+
+  mIndexes.mRow = -1;
+  mIndexes.mColumn = -1;
+
+  mElement = aHTMLEditor.GetFirstSelectedTableCellElement(aSelection, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
   }
-
-  // Also return the row and/or column if requested
-  CellIndexes cellIndexes(*firstSelectedCellElement, error);
-  if (NS_WARN_IF(error.Failed())) {
-    return error.StealNSResult();
+  if (!mElement) {
+    return;
   }
-  if (aRowIndex) {
-    *aRowIndex = cellIndexes.mRow;
-  }
-  if (aColumnIndex) {
-    *aColumnIndex = cellIndexes.mColumn;
-  }
-  return NS_OK;
+
+  mIndexes.Update(*mElement, aRv);
+  NS_WARNING_ASSERTION(!aRv.Failed(),
+    "Selected element is found, but failed to compute its indexes");
 }
 
 void
 HTMLEditor::SetSelectionAfterTableEdit(Element* aTable,
                                        int32_t aRow,
                                        int32_t aCol,
                                        int32_t aDirection,
                                        bool aSelected)
@@ -3626,82 +3650,139 @@ HTMLEditor::SetSelectionAfterTableEdit(E
   // Last resort: Set selection to start of doc
   // (it's very bad to not have a valid selection!)
   SetSelectionAtDocumentStart(selection);
 }
 
 NS_IMETHODIMP
 HTMLEditor::GetSelectedOrParentTableElement(nsAString& aTagName,
                                             int32_t* aSelectedCount,
-                                            Element** aTableElement)
+                                            Element** aCellOrRowOrTableElement)
 {
-  NS_ENSURE_ARG_POINTER(aTableElement);
-  NS_ENSURE_ARG_POINTER(aSelectedCount);
-  *aTableElement = nullptr;
+  if (NS_WARN_IF(!aSelectedCount) || NS_WARN_IF(!aCellOrRowOrTableElement)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
   aTagName.Truncate();
+  *aCellOrRowOrTableElement = nullptr;
   *aSelectedCount = 0;
 
   RefPtr<Selection> selection = GetSelection();
   if (NS_WARN_IF(!selection)) {
     return NS_ERROR_FAILURE;
   }
 
-  // Try to get the first selected cell
-  ErrorResult error;
-  RefPtr<Element> tableOrCellElement =
-    GetFirstSelectedTableCellElement(*selection, error);
-  if (NS_WARN_IF(error.Failed())) {
-    return error.StealNSResult();
+  bool isCellSelected = false;
+  ErrorResult aRv;
+  RefPtr<Element> cellOrRowOrTableElement =
+    GetSelectedOrParentTableElement(*selection, aRv, &isCellSelected);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return aRv.StealNSResult();
+  }
+  if (!cellOrRowOrTableElement) {
+    return NS_OK;
+  }
+
+  if (isCellSelected) {
+    aTagName.AssignLiteral("td");
+    *aSelectedCount = selection->RangeCount();
+    cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
+    return NS_OK;
+  }
+
+  if (cellOrRowOrTableElement->IsAnyOfHTMLElements(nsGkAtoms::td,
+                                                   nsGkAtoms::th)) {
+    aTagName.AssignLiteral("td");
+    // Keep *aSelectedCount as 0.
+    cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
+    return NS_OK;
+  }
+
+  if (cellOrRowOrTableElement->IsHTMLElement(nsGkAtoms::table)) {
+    aTagName.AssignLiteral("table");
+    *aSelectedCount = 1;
+    cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
+    return NS_OK;
   }
 
-  if (tableOrCellElement) {
-      // Each cell is in its own selection range,
-      //  so count signals multiple-cell selection
-      *aSelectedCount = selection->RangeCount();
-      aTagName = NS_LITERAL_STRING("td");
-  } else {
-    nsCOMPtr<nsINode> anchorNode = selection->GetAnchorNode();
-    if (NS_WARN_IF(!anchorNode)) {
-      return NS_ERROR_FAILURE;
+  if (cellOrRowOrTableElement->IsHTMLElement(nsGkAtoms::tr)) {
+    aTagName.AssignLiteral("tr");
+    *aSelectedCount = 1;
+    cellOrRowOrTableElement.forget(aCellOrRowOrTableElement);
+    return NS_OK;
+  }
+
+  MOZ_ASSERT_UNREACHABLE("Which element was returned?");
+  return NS_ERROR_UNEXPECTED;
+}
+
+already_AddRefed<Element>
+HTMLEditor::GetSelectedOrParentTableElement(
+              Selection& aSelection,
+              ErrorResult& aRv,
+              bool* aIsCellSelected /* = nullptr */) const
+{
+  MOZ_ASSERT(!aRv.Failed());
+
+  if (aIsCellSelected) {
+    *aIsCellSelected = false;
+  }
+
+  // Try to get the first selected cell, first.
+  RefPtr<Element> cellElement =
+    GetFirstSelectedTableCellElement(aSelection, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  if (cellElement) {
+    if (aIsCellSelected) {
+      *aIsCellSelected = true;
     }
-
-    // Get child of anchor node, if exists
-    if (anchorNode->HasChildNodes()) {
-      nsINode* selectedNode = selection->GetChildAtAnchorOffset();
-      if (selectedNode) {
-        if (selectedNode->IsHTMLElement(nsGkAtoms::td)) {
-          tableOrCellElement = selectedNode->AsElement();
-          aTagName = NS_LITERAL_STRING("td");
-          // Each cell is in its own selection range,
-          //  so count signals multiple-cell selection
-          *aSelectedCount = selection->RangeCount();
-        } else if (selectedNode->IsHTMLElement(nsGkAtoms::table)) {
-          tableOrCellElement = selectedNode->AsElement();
-          aTagName.AssignLiteral("table");
-          *aSelectedCount = 1;
-        } else if (selectedNode->IsHTMLElement(nsGkAtoms::tr)) {
-          tableOrCellElement = selectedNode->AsElement();
-          aTagName.AssignLiteral("tr");
-          *aSelectedCount = 1;
+    return cellElement.forget();
+  }
+
+  const RangeBoundary& anchorRef = aSelection.AnchorRef();
+  if (NS_WARN_IF(!anchorRef.IsSet())) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  // If anchor selects a <td>, <table> or <tr>, return it.
+  if (anchorRef.Container()->HasChildNodes()) {
+    nsIContent* selectedContent = anchorRef.GetChildAtOffset();
+    if (selectedContent) {
+      // XXX Why do we ignore <th> element in this case?
+      if (selectedContent->IsHTMLElement(nsGkAtoms::td)) {
+        // FYI: If first range selects a <tr> element, but the other selects
+        //      a <td> element, you can reach here.
+        // Each cell is in its own selection range in this case.
+        // XXX Although, other ranges may not select cells, though.
+        if (aIsCellSelected) {
+          *aIsCellSelected = true;
         }
+        return do_AddRef(selectedContent->AsElement());
       }
-    }
-    if (!tableOrCellElement) {
-      // Didn't find a table element -- find a cell parent
-      tableOrCellElement =
-        GetElementOrParentByTagNameInternal(*nsGkAtoms::td, *anchorNode);
-      if (tableOrCellElement) {
-        aTagName = NS_LITERAL_STRING("td");
+      if (selectedContent->IsAnyOfHTMLElements(nsGkAtoms::table,
+                                               nsGkAtoms::tr)) {
+        return do_AddRef(selectedContent->AsElement());
       }
     }
   }
-  if (tableOrCellElement) {
-    tableOrCellElement.forget(aTableElement);
+
+  // Then, look for a cell element (either <td> or <th>) which contains
+  // the anchor container.
+  cellElement = GetElementOrParentByTagNameInternal(*nsGkAtoms::td,
+                                                    *anchorRef.Container());
+  if (!cellElement) {
+    return nullptr; // Not in table.
   }
-  return NS_OK;
+  // Don't set *aIsCellSelected to true in this case because it does NOT
+  // select a cell, just in a cell.
+  return cellElement.forget();
 }
 
 NS_IMETHODIMP
 HTMLEditor::GetSelectedCellsType(Element* aElement,
                                  uint32_t* aSelectionType)
 {
   NS_ENSURE_ARG_POINTER(aSelectionType);
   *aSelectionType = 0;
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -285,17 +285,19 @@ skip-if = android_version == '24'
 [test_nsIHTMLEditor_getSelectedElement.html]
 [test_nsIHTMLEditor_selectElement.html]
 [test_nsIHTMLEditor_setCaretAfterElement.html]
 [test_nsIHTMLObjectResizer_hideResizers.html]
 [test_nsITableEditor_getCellAt.html]
 [test_nsITableEditor_getCellIndexes.html]
 [test_nsITableEditor_getFirstRow.html]
 [test_nsITableEditor_getFirstSelectedCell.html]
+[test_nsITableEditor_getFirstSelectedCellInTable.html]
 [test_nsITableEditor_getNextSelectedCell.html]
+[test_nsITableEditor_getSelectedOrParentTableElement.html]
 [test_nsITableEditor_getTableSize.html]
 [test_resizers_appearance.html]
 [test_resizers_resizing_elements.html]
 skip-if = toolkit == 'android' || (verify && debug && os == 'win') # bug 1147989 and bug 1485293
 [test_root_element_replacement.html]
 [test_select_all_without_body.html]
 [test_spellcheck_pref.html]
 skip-if = toolkit == 'android'
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_nsITableEditor_getFirstSelectedCellInTable.html
@@ -0,0 +1,201 @@
+<!DOCTYPE>
+<html>
+<head>
+  <title>Test for nsITableEditor.getFirstSelectedCellInTable()</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="display">
+</div>
+<div id="content" contenteditable></div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  let editor = document.getElementById("content");
+  let selection = document.getSelection();
+
+  selection.collapse(editor, 0);
+  let rowWrapper = {};
+  let colWrapper = {};
+  let cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(rowWrapper, colWrapper));
+  is(cell, null,
+     "nsITableEditor.getFirstSelectedCellInTable() should return null if Selection does not select cells");
+  is(rowWrapper.value, 0,
+     "nsITableEditor.getFirstSelectedCellInTable() should return 0 to row number if Selection does not select cells");
+  is(colWrapper.value, 0,
+     "nsITableEditor.getFirstSelectedCellInTable() should return 0 to row number if Selection does not select cells");
+
+  editor.innerHTML =
+    '<table id="table">' +
+      '<tr id="r1"><td id="c1-1">cell1-1</td><td id="c1-2">cell1-2</td><td id="c1-3">cell1-3</td><td id="c1-4" colspan="2" rowspan="2">cell1-4</td></tr>' +
+      '<tr id="r2"><th id="c2-1" rowspan="2">cell2-1</th><td id="c2-2">cell2-2<td id="c2-3">cell2-3</td></tr>' +
+      '<tr id="r3"><td id="c3-2">cell3-2</td><td id="c3-3">cell3-3</td><td id="c3-4" colspan="2">cell3-4</td></tr>' +
+      '<tr id="r4"><td id="c4-1" rowspan="4">cell4-1</td><td id="c4-2">cell4-2</td><td id="c4-3">cell4-3</td><th id="c4-4">cell4-4</th><td id="c4-5">cell4-5</td></tr>' +
+      '<tr id="r5"><th id="c5-2">cell5-2</th><th id="c5-3" colspan="2">cell5-3</th><td id="c5-5">cell5-5</td></tr>' +
+      '<tr id="r6"><td id="c6-2">cell6-2</td><td id="c6-3">cell6-3</td><td id="c6-4"><p>cell6-4</p></td><td id="c6-5">cell6-5</td></tr>' +
+      '<tr id="r7"><td id="c7-2" colspan="4">cell7-2</td></tr>' +
+    "</table>";
+
+  let tr = document.getElementById("r1");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(rowWrapper, colWrapper));
+  is(cell, document.getElementById("c1-1"),
+     "#1-1 nsITableEditor.getFirstSelectedCellInTable() should return the first cell element in the first row");
+  is(rowWrapper.value, 0,
+     "#1-1 nsITableEditor.getFirstSelectedCellInTable() should return 0 to row number for the first row");
+  is(colWrapper.value, 0,
+     "#1-1 nsITableEditor.getFirstSelectedCellInTable() should return 0 to column number for the first column");
+
+  tr = document.getElementById("r1");
+  selection.setBaseAndExtent(tr, 3, tr, 4);
+  cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(rowWrapper, colWrapper));
+  is(cell, document.getElementById("c1-4"),
+     "#1-4 nsITableEditor.getFirstSelectedCellInTable() should return the last cell element whose colspan and rowspan are 2 in the first row");
+  is(rowWrapper.value, 0,
+     "#1-4 nsITableEditor.getFirstSelectedCellInTable() should return 0 to row number for the first row");
+  is(colWrapper.value, 3,
+     "#1-4 nsITableEditor.getFirstSelectedCellInTable() should return 3 to column number for the forth column");
+
+  tr = document.getElementById("r2");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(rowWrapper, colWrapper));
+  is(cell, document.getElementById("c2-1"),
+     "#2-1 nsITableEditor.getFirstSelectedCellInTable() should return the first cell element in the second row");
+  is(rowWrapper.value, 1,
+     "#2-1 nsITableEditor.getFirstSelectedCellInTable() should return 1 to row number for the second row");
+  is(colWrapper.value, 0,
+     "#2-1 nsITableEditor.getFirstSelectedCellInTable() should return 0 to column number for the first column");
+
+  tr = document.getElementById("r7");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(rowWrapper, colWrapper));
+  is(cell, document.getElementById("c7-2"),
+     "#7-2 nsITableEditor.getFirstSelectedCellInTable() should return the second cell element in the last row");
+  is(rowWrapper.value, 6,
+     "#7-2 nsITableEditor.getFirstSelectedCellInTable() should return 6 to row number for the seventh row");
+  is(colWrapper.value, 1,
+     "#7-2 nsITableEditor.getFirstSelectedCellInTable() should return 1 to column number for the second column");
+
+  selection.removeAllRanges();
+  let range = document.createRange();
+  range.selectNode(document.getElementById("c2-2"));
+  selection.addRange(range);
+  range = document.createRange();
+  range.selectNode(document.getElementById("c2-3"));
+  selection.addRange(range);
+  cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(rowWrapper, colWrapper));
+  is(cell, document.getElementById("c2-2"),
+     "#2-2 nsITableEditor.getFirstSelectedCellInTable() should return the second cell element in the second row");
+  is(rowWrapper.value, 1,
+     "#2-2 nsITableEditor.getFirstSelectedCellInTable() should return 1 to row number for the second row");
+  is(colWrapper.value, 1,
+     "#2-2 nsITableEditor.getFirstSelectedCellInTable() should return 1 to column number for the second column");
+
+  selection.removeAllRanges();
+  range = document.createRange();
+  range.selectNode(document.getElementById("c3-4"));
+  selection.addRange(range);
+  range = document.createRange();
+  range.selectNode(document.getElementById("c5-2"));
+  selection.addRange(range);
+  cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(rowWrapper, colWrapper));
+  is(cell, document.getElementById("c3-4"),
+     "#3-4 nsITableEditor.getFirstSelectedCellInTable() should return the last cell element in the third row");
+  is(rowWrapper.value, 2,
+     "#3-4 nsITableEditor.getFirstSelectedCellInTable() should return 2 to row number for the third row");
+  is(colWrapper.value, 3,
+     "#3-4 nsITableEditor.getFirstSelectedCellInTable() should return 3 to column number for the forth column");
+
+  cell = document.getElementById("c6-4");
+  selection.selectAllChildren(cell);
+  cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(rowWrapper, colWrapper));
+  is(cell, null,
+     "nsITableEditor.getFirstSelectedCellInTable() should return null if neither <td> nor <th> element node is selected");
+  is(rowWrapper.value, 0,
+     "#3-4 nsITableEditor.getFirstSelectedCellInTable() should return 0 to row number if neither <td> nor <th> element node is selected");
+  is(colWrapper.value, 0,
+     "#3-4 nsITableEditor.getFirstSelectedCellInTable() should return 0 to column number if neither <td> nor <th> element node is selected");
+
+  cell = document.getElementById("c6-5");
+  selection.setBaseAndExtent(cell.firstChild, 0, cell.firstChild, 0);
+  cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(rowWrapper, colWrapper));
+  is(cell, null,
+     "nsITableEditor.getFirstSelectedCellInTable() should return null if a text node is selected");
+  is(rowWrapper.value, 0,
+     "#3-4 nsITableEditor.getFirstSelectedCellInTable() should return 0 to row number if a text node is selected");
+  is(colWrapper.value, 0,
+     "#3-4 nsITableEditor.getFirstSelectedCellInTable() should return 0 to column number if a text node is selected");
+
+  // XXX If cell is not selected, nsITableEditor.getFirstSelectedCellInTable()
+  //     returns null without throwing exception, however, if there is no
+  //     selection ranges, throwing an exception.  This inconsistency is odd.
+  selection.removeAllRanges();
+  try {
+    cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(rowWrapper, colWrapper));
+    ok(false, "nsITableEditor.getFirstSelectedCellInTable() should throw an exception if there is no selection ranges");
+  } catch (e) {
+    ok(true, "nsITableEditor.getFirstSelectedCellInTable() should throw an exception if there is no selection ranges");
+  }
+
+  tr = document.getElementById("r6");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  try {
+    cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable());
+    ok(false, "nsITableEditor.getFirstSelectedCellInTable() should throw an exception if it does not have argument");
+  } catch (e) {
+    ok(true, "nsITableEditor.getFirstSelectedCellInTable() should throw an exception if it does not have argument");
+  }
+
+  tr = document.getElementById("r6");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  try {
+    cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(null));
+    ok(false, "nsITableEditor.getFirstSelectedCellInTable() should throw an exception if its argument is only one null");
+  } catch (e) {
+    ok(true, "nsITableEditor.getFirstSelectedCellInTable() should throw an exception if its argument is only one null");
+  }
+
+  tr = document.getElementById("r6");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  try {
+    cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(null, null));
+    ok(false, "nsITableEditor.getFirstSelectedCellInTable() should throw an exception if its arguments are all null");
+  } catch (e) {
+    ok(true, "nsITableEditor.getFirstSelectedCellInTable() should throw an exception if its arguments are all null");
+  }
+
+  tr = document.getElementById("r6");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  try {
+    cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(rowWrapper, null));
+    ok(false, "nsITableEditor.getFirstSelectedCellInTable() should throw an exception if its column argument is null");
+  } catch (e) {
+    ok(true, "nsITableEditor.getFirstSelectedCellInTable() should throw an exception if its column argument is null");
+  }
+
+  tr = document.getElementById("r6");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  try {
+    cell = SpecialPowers.unwrap(getTableEditor().getFirstSelectedCellInTable(null, colWrapper));
+    ok(false, "nsITableEditor.getFirstSelectedCellInTable() should throw an exception if its row argument is null");
+  } catch (e) {
+    ok(true, "nsITableEditor.getFirstSelectedCellInTable() should throw an exception if its row argument is null");
+  }
+
+  SimpleTest.finish();
+});
+
+function getTableEditor() {
+  let editingSession = SpecialPowers.wrap(window).docShell.editingSession;
+  return editingSession.getEditorForWindow(window).QueryInterface(SpecialPowers.Ci.nsITableEditor);
+}
+
+</script>
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_nsITableEditor_getSelectedOrParentTableElement.html
@@ -0,0 +1,283 @@
+<!DOCTYPE>
+<html>
+<head>
+  <title>Test for nsITableEditor.getSelectedOrParentTableElement()</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<div id="display">
+</div>
+<div id="content" contenteditable></div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  let editor = document.getElementById("content");
+  let selection = document.getSelection();
+
+  selection.collapse(editor, 0);
+  let tagNameWrapper = {};
+  let countWrapper = {};
+  let cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(cell, null,
+     "nsITableEditor.getSelectedOrParentTableElement() should return null if Selection does not select cells nor in <table>");
+  is(tagNameWrapper.value, "",
+     "nsITableEditor.getSelectedOrParentTableElement() should return empty string to tag name if Selection does not select cells nor in <table>");
+  is(countWrapper.value, 0,
+     "nsITableEditor.getSelectedOrParentTableElement() should return 0 to count if Selection does not select cells nor in <table>");
+
+  editor.innerHTML =
+    '<table id="table">' +
+      '<tr id="r1"><td id="c1-1">cell1-1</td><td id="c1-2">cell1-2</td><td id="c1-3">cell1-3</td><td id="c1-4" colspan="2" rowspan="2">cell1-4</td></tr>' +
+      '<tr id="r2"><th id="c2-1" rowspan="2">cell2-1</th><th id="c2-2">cell2-2</th><td id="c2-3">cell2-3</td></tr>' +
+      '<tr id="r3"><td id="c3-2">cell3-2</td><td id="c3-3">cell3-3</td><td id="c3-4" colspan="2">cell3-4</td></tr>' +
+      '<tr id="r4"><td id="c4-1" rowspan="4">cell4-1</td><td id="c4-2">cell4-2</td><td id="c4-3">' +
+        '<table id="table2">' +
+          '<tr id="r2-1"><th id="c2-1-1">cell2-1-1-</td><td id="c2-1-2">cell2-1-2</td></tr>' +
+          '<tr id="r2-2"><td id="c2-2-1">cell2-2-1-</td><th id="c2-2-2">cell2-2-2</th></tr>' +
+        "</table>" +
+        '</td><th id="c4-4">cell4-4</th><td id="c4-5">cell4-5</td></tr>' +
+      '<tr id="r5"><th id="c5-2">cell5-2</th><th id="c5-3" colspan="2">cell5-3</th><td id="c5-5">cell5-5</td></tr>' +
+      '<tr id="r6"><td id="c6-2">cell6-2</td><td id="c6-3">cell6-3</td><td id="c6-4"><p>cell6-4</p></td><td id="c6-5">cell6-5</td></tr>' +
+      '<tr id="r7"><td id="c7-2" colspan="4">cell7-2</td></tr>' +
+    "</table>";
+
+  let tr = document.getElementById("r1");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(cell, document.getElementById("c1-1"),
+     "#1-1 nsITableEditor.getSelectedOrParentTableElement() should return the first cell element in the first row");
+  is(tagNameWrapper.value, "td",
+     "#1-1 nsITableEditor.getSelectedOrParentTableElement() should return 'td' to tag name when a cell is selected");
+  is(countWrapper.value, 1,
+     "#1-1 nsITableEditor.getSelectedOrParentTableElement() should return 1 to count when a cell is selected");
+
+  tr = document.getElementById("r1");
+  selection.setBaseAndExtent(tr, 3, tr, 4);
+  cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(cell, document.getElementById("c1-4"),
+     "#1-4 nsITableEditor.getSelectedOrParentTableElement() should return the last cell element whose colspan and rowspan are 2 in the first row");
+  is(tagNameWrapper.value, "td",
+     "#1-4 nsITableEditor.getSelectedOrParentTableElement() should return 'td' to tag name when a cell is selected");
+  is(countWrapper.value, 1,
+     "#1-4 nsITableEditor.getSelectedOrParentTableElement() should return 1 to count when a cell is selected");
+
+  tr = document.getElementById("r2");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(cell, document.getElementById("c2-1"),
+     "#2-1 nsITableEditor.getSelectedOrParentTableElement() should return the first cell element in the second row");
+  is(tagNameWrapper.value, "td",
+     "#2-1 nsITableEditor.getSelectedOrParentTableElement() should return 'td' to tag name when a cell is selected but even if the cell is <th>");
+  is(countWrapper.value, 1,
+     "#2-1 nsITableEditor.getSelectedOrParentTableElement() should return 1 to count when a cell is selected");
+
+  tr = document.getElementById("r7");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(cell, document.getElementById("c7-2"),
+     "#7-2 nsITableEditor.getSelectedOrParentTableElement() should return the second cell element in the last row");
+  is(tagNameWrapper.value, "td",
+     "#7-2 nsITableEditor.getSelectedOrParentTableElement() should return 'td' to tag name when a cell is selected");
+  is(countWrapper.value, 1,
+     "#7-2 nsITableEditor.getSelectedOrParentTableElement() should return 1 to count when a cell is selected");
+
+  selection.removeAllRanges();
+  let range = document.createRange();
+  range.selectNode(document.getElementById("c2-2"));
+  selection.addRange(range);
+  range = document.createRange();
+  range.selectNode(document.getElementById("c2-3"));
+  selection.addRange(range);
+  cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(cell, document.getElementById("c2-2"),
+     "#2-2 nsITableEditor.getSelectedOrParentTableElement() should return the second cell element in the second row");
+  is(tagNameWrapper.value, "td",
+     "#2-2 nsITableEditor.getSelectedOrParentTableElement() should return 'td' to tag name when first range selects a cell");
+  is(countWrapper.value, 2,
+     "#2-2 nsITableEditor.getSelectedOrParentTableElement() should return 2 to count when there are 2 selection ranges");
+
+  selection.removeAllRanges();
+  range = document.createRange();
+  range.selectNode(document.getElementById("c3-4"));
+  selection.addRange(range);
+  range = document.createRange();
+  range.selectNode(document.getElementById("c5-2"));
+  selection.addRange(range);
+  cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(cell, document.getElementById("c3-4"),
+     "#3-4 nsITableEditor.getSelectedOrParentTableElement() should return the last cell element in the third row");
+  is(tagNameWrapper.value, "td",
+     "#3-4 nsITableEditor.getSelectedOrParentTableElement() should return 'td' to tag name when first range selects a cell");
+  is(countWrapper.value, 2,
+     "#3-4 nsITableEditor.getSelectedOrParentTableElement() should return 2 to count when there are 2 selection ranges");
+
+  cell = document.getElementById("c2-2");
+  selection.removeAllRanges();
+  range = document.createRange();
+  range.setStart(cell.firstChild, 0);
+  selection.addRange(range);
+  cell = document.getElementById("c2-1-1");
+  range = document.createRange();
+  range.setStart(cell.firstChild, 1);
+  range.setEnd(cell.firstChild, 2);
+  selection.addRange(range);
+  cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(cell, document.getElementById("c2-1-1"),
+     "#2-1-1 nsITableEditor.getSelectedOrParentTableElement() should return the cell which contains the last selection range if first selection range does not select a cell");
+  is(tagNameWrapper.value, "td",
+     "#2-1-1 nsITableEditor.getSelectedOrParentTableElement() should return 'td' to tag name when the first range does not select a cell and the last range is in a cell");
+  is(countWrapper.value, 0,
+     "#2-1-1 nsITableEditor.getSelectedOrParentTableElement() should return 0 to count when the first range does not select a cell");
+
+  tr = document.getElementById("r2-2");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(cell, document.getElementById("c2-2-1"),
+     "#2-2-1 nsITableEditor.getSelectedOrParentTableElement() should return the first cell element in the first row of nested <table>");
+  is(tagNameWrapper.value, "td",
+     "#2-2-1 nsITableEditor.getSelectedOrParentTableElement() should return 'td' to tag name when a cell in nested <table> is selected");
+  is(countWrapper.value, 1,
+     "#2-2-1 nsITableEditor.getSelectedOrParentTableElement() should return 1 to count when a cell in nested <table> is selected");
+
+  cell = document.getElementById("c2-1-2");
+  selection.setBaseAndExtent(cell.firstChild, 0, cell.firstChild, 0);
+  cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(cell, document.getElementById("c2-1-2"),
+     "#2-1-2 nsITableEditor.getSelectedOrParentTableElement() should return the first cell element in the first row of nested <table>");
+  is(tagNameWrapper.value, "td",
+     "#2-1-2 nsITableEditor.getSelectedOrParentTableElement() should return 'td' to tag name when a cell in nested <table> contains the first selection range");
+  is(countWrapper.value, 0,
+     "#2-1-2 nsITableEditor.getSelectedOrParentTableElement() should return 0 to count when a cell in nested <table> contains the first selection range");
+
+  let table = document.getElementById("table2");
+  selection.removeAllRanges();
+  range = document.createRange();
+  range.selectNode(table);
+  selection.addRange(range);
+  table = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(table, document.getElementById("table2"),
+     "nsITableEditor.getSelectedOrParentTableElement() should return a <table> element which is selected");
+  is(tagNameWrapper.value, "table",
+     "nsITableEditor.getSelectedOrParentTableElement() should return 'table' to tag name when a <table> is selected");
+  is(countWrapper.value, 1,
+     "nsITableEditor.getSelectedOrParentTableElement() should return 1 to count when a <table> is selected");
+
+  selection.removeAllRanges();
+  range = document.createRange();
+  range.selectNode(document.getElementById("r2-1"));
+  selection.addRange(range);
+  range = document.createRange();
+  range.selectNode(document.getElementById("r2-2"));
+  selection.addRange(range);
+  table = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(table, document.getElementById("r2-2"),
+     "nsITableEditor.getSelectedOrParentTableElement() should return a <tr> element which is selected by the last selection range");
+  is(tagNameWrapper.value, "tr",
+     "nsITableEditor.getSelectedOrParentTableElement() should return 'tr' to tag name when a <tr> is selected");
+  is(countWrapper.value, 1,
+     "nsITableEditor.getSelectedOrParentTableElement() should return 1 to count when a <tr> is selected");
+
+  selection.removeAllRanges();
+  range = document.createRange();
+  range.selectNode(document.getElementById("r1"));
+  selection.addRange(range);
+  range = document.createRange();
+  range.selectNode(document.getElementById("c5-5"));
+  selection.addRange(range);
+  cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(cell, document.getElementById("c5-5"),
+     "#5-5 nsITableEditor.getSelectedOrParentTableElement() should return the cell selected by the last range when first range selects <tr>");
+  is(tagNameWrapper.value, "td",
+     "#5-5 nsITableEditor.getSelectedOrParentTableElement() should return 'td' to tag name if the last range selects the cell when first range selects <tr>");
+  is(countWrapper.value, 2,
+     "#5-5 nsITableEditor.getSelectedOrParentTableElement() should return 2 to count if the last range selects <td> when first range selects <tr>");
+
+  selection.removeAllRanges();
+  range = document.createRange();
+  range.selectNode(document.getElementById("r1"));
+  selection.addRange(range);
+  range = document.createRange();
+  range.selectNode(document.getElementById("c5-2"));
+  selection.addRange(range);
+  cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+  is(cell, null,
+     "#5-5 nsITableEditor.getSelectedOrParentTableElement() should return null if the last range selects <th> when first range selects <tr>");
+  is(tagNameWrapper.value, "",
+     "#5-5 nsITableEditor.getSelectedOrParentTableElement() should return empty string to tag name if the last range selects <th> when first range selects <tr>");
+  is(countWrapper.value, 0,
+     "#5-5 nsITableEditor.getSelectedOrParentTableElement() should return 0 to count if the last range selects <th> when first range selects <tr>");
+
+  // XXX If cell is not selected, nsITableEditor.getSelectedOrParentTableElement()
+  //     returns null without throwing exception, however, if there is no
+  //     selection ranges, throwing an exception.  This inconsistency is odd.
+  selection.removeAllRanges();
+  try {
+    cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, countWrapper));
+    ok(false, "nsITableEditor.getSelectedOrParentTableElement() should throw an exception if there is no selection ranges");
+  } catch (e) {
+    ok(true, "nsITableEditor.getSelectedOrParentTableElement() should throw an exception if there is no selection ranges");
+  }
+
+  tr = document.getElementById("r6");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  try {
+    cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement());
+    ok(false, "nsITableEditor.getSelectedOrParentTableElement() should throw an exception if it does not have argument");
+  } catch (e) {
+    ok(true, "nsITableEditor.getSelectedOrParentTableElement() should throw an exception if it does not have argument");
+  }
+
+  tr = document.getElementById("r6");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  try {
+    cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(null));
+    ok(false, "nsITableEditor.getSelectedOrParentTableElement() should throw an exception if its argument is only one null");
+  } catch (e) {
+    ok(true, "nsITableEditor.getSelectedOrParentTableElement() should throw an exception if its argument is only one null");
+  }
+
+  tr = document.getElementById("r6");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  try {
+    cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(null, null));
+    ok(false, "nsITableEditor.getSelectedOrParentTableElement() should throw an exception if its arguments are all null");
+  } catch (e) {
+    ok(true, "nsITableEditor.getSelectedOrParentTableElement() should throw an exception if its arguments are all null");
+  }
+
+  tr = document.getElementById("r6");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  try {
+    cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(tagNameWrapper, null));
+    ok(false, "nsITableEditor.getSelectedOrParentTableElement() should throw an exception if its count argument is null");
+  } catch (e) {
+    ok(true, "nsITableEditor.getSelectedOrParentTableElement() should throw an exception if its count argument is null");
+  }
+
+  tr = document.getElementById("r6");
+  selection.setBaseAndExtent(tr, 0, tr, 1);
+  try {
+    cell = SpecialPowers.unwrap(getTableEditor().getSelectedOrParentTableElement(null, countWrapper));
+    ok(false, "nsITableEditor.getSelectedOrParentTableElement() should throw an exception if its tag name argument is null");
+  } catch (e) {
+    ok(true, "nsITableEditor.getSelectedOrParentTableElement() should throw an exception if its tag name argument is null");
+  }
+
+  SimpleTest.finish();
+});
+
+function getTableEditor() {
+  var Ci = SpecialPowers.Ci;
+  var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
+  return editingSession.getEditorForWindow(window).QueryInterface(Ci.nsITableEditor);
+}
+
+</script>
+</body>
+
+</html>
--- a/editor/nsITableEditor.idl
+++ b/editor/nsITableEditor.idl
@@ -260,31 +260,40 @@ interface nsITableEditor : nsISupports
   Element getFirstRow(in Element aTableElement);
 
   /** Preferred direction to search for neighboring cell
     * when trying to locate a cell to place caret in after
     * a table editing action.
     * Used for aDirection param in SetSelectionAfterTableEdit
     */
 
-  /** Examine the current selection and find
-    *   a selected TABLE, TD or TH, or TR element.
-    *   or return the parent TD or TH if selection is inside a table cell
-    *   Returns null if no table element is found.
-    *
-    * @param aTagName         The tagname of returned element
-    *                         Note that "td" will be returned if name
-    *                         is actually "th"
-    * @param aCount           How many table elements were selected
-    *                         This tells us if we have multiple cells selected
-    *                           (0 if element is a parent cell of selection)
-    * @return                 The table element (table, row, or first selected cell)
-    *
-    */
-  Element getSelectedOrParentTableElement(out AString aTagName, out long aCount);
+  /**
+   * getSelectedOrParentTableElement() returns a <td>, <th>, <tr> or <table>.
+   * If first selection range selects a <td> or <th>, returns it.  aTagName
+   * is set to "td" even if the result is a <th> and aCount is set to
+   * Selection.rangeCount.
+   * If first selection range does not select <td> nor <th>, but selection
+   * anchor refers <table>, returns it.  aTagName is set to "table" and
+   * aCount is set to 1.
+   * If first selection range does not select <td> nor <th>, but selection
+   * anchor refers <tr>, returns it.  aTagName is set to "tr" and aCount is
+   * set to 1.
+   * If first selection range does not select <td> nor <th>, but selection
+   * anchor refers <td> (not include <th>!), returns it.  aTagName is set to
+   * "td" and aCount is set to 0.
+   * Otherwise, if container of selection anchor is in a <td> or <th>,
+   * returns it.  aTagName is set to "td" but aCount is set to 0.
+   * Otherwise, returns null, aTagName is set to empty string and aCount is
+   * set to 0.  I.e., does not throw exception even if a cell is not found.
+   * NOTE: Calling this resets internal counter of getFirstSelectedCell()
+   *       and getNextSelectedCell().  I.e., getNextSelectedCell() will
+   *       return second selected cell element.
+   */
+  Element getSelectedOrParentTableElement(out AString aTagName,
+                                          out long aCount);
 
   /** Generally used after GetSelectedOrParentTableElement
     *   to test if selected cells are complete rows or columns
     *
     * @param aElement           Any table or cell element or any element
     *                           inside a table
     *                           Used to get enclosing table.
     *                           If null, selection's anchorNode is used
@@ -315,30 +324,34 @@ interface nsITableEditor : nsISupports
    *                               only when this returns a <td> or <th>
    *                               element.  Otherwise, returns null.
    * @return                       A <td> or <th> element if first range of
    *                               Selection selects only one table cell
    *                               element.
    */
   Element getFirstSelectedCell(out Range aFirstRangeOfSelection);
 
-  /** Get first selected element in the table
-    *   This is the upper-left-most selected cell in table,
-    *   ignoring the order that the user selected them (order in the selection ranges)
-    * Assumes cell-selection model where each cell
-    * is in a separate range (selection parent node is table row)
-    * @param aCell       Selected cell or null if ranges don't contain
-    *                    cell selections
-    * @param aRowIndex   Optional: if not null, return row index of 1st cell
-    * @param aColIndex   Optional: if not null, return column index of 1st cell
-    *
-    * Returns the DOM cell element
-    *   (in C++: returns NS_EDITOR_ELEMENT_NOT_FOUND if an element is not found
-    *    passes NS_SUCCEEDED macro)
-    */
+  /**
+   * getFirstSelectedCellInTable() returns a cell element, its row index and
+   * its column index if first range of Selection selects a cell.  Note that
+   * that "selects a cell" means that the range container is a <tr> element
+   * and endOffset is startOffset + 1.  So, even if first range of Selection
+   * is in a cell element, this treats the range does not select a cell.
+   * NOTE: Calling this resets internal counter of getFirstSelectedCell()
+   *       and getNextSelectedCell().  I.e., getNextSelectedCell() will
+   *       return second selected cell element.
+   *
+   * @param aRowIndex    [OUT} Returns row index of the found cell.  If not
+   *                     found, returns 0.
+   * @param aColumnIndex [OUT] Returns column index of the found cell.  If
+   *                     not found, returns 0.
+   * @return             The cell element which is selected by the first
+   *                     range of Selection.  Even if this is not found,
+   *                     this returns null, not throwing exception.
+   */
   Element getFirstSelectedCellInTable(out long aRowIndex, out long aColIndex);
 
   /**
    * getNextSelectedCell() is a stateful method to retrieve selected table
    * cell elements which are selected by 2nd or later ranges of Selection.
    * When you call getFirstSelectedCell(), it resets internal counter of
    * this method.  Then, following calls of getNextSelectedCell() scans the
    * remaining ranges of Selection.  If a range selects a <td> or <th>
--- a/intl/locale/LocaleService.cpp
+++ b/intl/locale/LocaleService.cpp
@@ -117,25 +117,16 @@ ReadRequestedLocales(nsTArray<nsCString>
       SplitLocaleListStringIntoArray(str, aRetVal);
     }
   } else {
     nsAutoCString defaultLocale;
     LocaleService::GetInstance()->GetDefaultLocale(defaultLocale);
     aRetVal.AppendElement(defaultLocale);
   }
 
-  // Last fallback locale is a locale for the requested locale chain.
-  // In the future we'll want to make the fallback chain differ per-locale.
-  //
-  // Notice: This is not the same as DefaultLocale,
-  // which follows the default locale the build is in.
-  LocaleService::GetInstance()->GetLastFallbackLocale(str);
-  if (!aRetVal.Contains(str)) {
-    aRetVal.AppendElement(str);
-  }
   return true;
 }
 
 LocaleService::LocaleService(bool aIsServer)
   :mIsServer(aIsServer)
 {
 }
 
--- a/intl/locale/tests/unit/test_localeService.js
+++ b/intl/locale/tests/unit/test_localeService.js
@@ -168,18 +168,17 @@ add_test(function test_setAvailableLocal
 add_test(function test_getRequestedLocales_sanitize() {
   Services.prefs.setCharPref(PREF_REQUESTED_LOCALES, "de,2,#$@#,pl,ąó,!a2,DE-at,,;");
 
   let locales = localeService.getRequestedLocales();
   Assert.equal(locales[0], "de");
   Assert.equal(locales[1], "pl");
   Assert.equal(locales[2], "de-AT");
   Assert.equal(locales[3], "und");
-  Assert.equal(locales[4], localeService.lastFallbackLocale);
-  Assert.equal(locales.length, 5);
+  Assert.equal(locales.length, 4);
 
   Services.prefs.clearUserPref(PREF_REQUESTED_LOCALES);
 
   run_next_test();
 });
 
 add_test(function test_handle_ja_JP_mac() {
   const bkpAvLocales = localeService.getAvailableLocales();
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -341,16 +341,22 @@ js::GetBuiltinClass(JSContext* cx, Handl
 #endif
     } else {
         *cls = ESClass::Other;
     }
 
     return true;
 }
 
+JS_FRIEND_API(bool)
+js::IsArgumentsObject(HandleObject obj)
+{
+    return obj->is<ArgumentsObject>();
+}
+
 JS_FRIEND_API(const char*)
 js::ObjectClassName(JSContext* cx, HandleObject obj)
 {
     cx->check(obj);
     return GetObjectClassName(cx, obj);
 }
 
 JS_FRIEND_API(JS::Zone*)
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -219,16 +219,19 @@ JS_InitializePropertiesFromCompatibleNat
                                                   JS::HandleObject dst,
                                                   JS::HandleObject src);
 
 namespace js {
 
 JS_FRIEND_API(bool)
 GetBuiltinClass(JSContext* cx, JS::HandleObject obj, ESClass* cls);
 
+JS_FRIEND_API(bool)
+IsArgumentsObject(JS::HandleObject obj);
+
 JS_FRIEND_API(const char*)
 ObjectClassName(JSContext* cx, JS::HandleObject obj);
 
 JS_FRIEND_API(void)
 ReportOverRecursed(JSContext* maybecx);
 
 JS_FRIEND_API(bool)
 AddRawValueRoot(JSContext* cx, JS::Value* vp, const char* name);
--- a/js/xpconnect/idl/mozIJSSubScriptLoader.idl
+++ b/js/xpconnect/idl/mozIJSSubScriptLoader.idl
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIPrincipal;
 interface nsIObserver;
 
-[scriptable, uuid(19533e7b-f321-4ef1-bc59-6e812dc2a733)]
+[scriptable, builtinclass, uuid(19533e7b-f321-4ef1-bc59-6e812dc2a733)]
 interface mozIJSSubScriptLoader : nsISupports
 {
     /**
      * This method should only be called from JS!
      * In JS, the signature looks like:
      * rv loadSubScript (url [, obj] [, charset]);
      * @param url the url of the sub-script, it MUST be either a file:,
      *            resource:, blob:, or chrome: url, and MUST be local.
--- a/js/xpconnect/idl/nsIXPCScriptable.idl
+++ b/js/xpconnect/idl/nsIXPCScriptable.idl
@@ -58,17 +58,17 @@ interface nsIXPConnectWrappedNative;
 %}
 
 /**
  * Note: This is not really an XPCOM interface.  For example, callers must
  * guarantee that they set the *_retval of the various methods that return a
  * boolean to PR_TRUE before making the call.  Implementations may skip writing
  * to *_retval unless they want to return PR_FALSE.
  */
-[uuid(19b70b26-7c3f-437f-a04a-2a8f9e28b617)]
+[builtinclass, uuid(19b70b26-7c3f-437f-a04a-2a8f9e28b617)]
 interface nsIXPCScriptable : nsISupports
 {
     readonly attribute AUTF8String className;
     [notxpcom,nostdcall] uint32_t getScriptableFlags();
     [notxpcom,nostdcall] jsClassPtr getClass();
     [notxpcom,nostdcall] JSClassPtr getJSClass();
 
     void   preCreate(in nsISupports nativeObj, in JSContextPtr cx,
--- a/js/xpconnect/idl/nsIXPConnect.idl
+++ b/js/xpconnect/idl/nsIXPConnect.idl
@@ -34,23 +34,23 @@ class nsWrapperCache;
 // forward declarations...
 interface nsIPrincipal;
 interface nsIClassInfo;
 interface nsIVariant;
 interface nsIObjectInputStream;
 interface nsIObjectOutputStream;
 
 /***************************************************************************/
-[uuid(73e6ff4a-ab99-4d99-ac00-ba39ccb8e4d7)]
+[builtinclass, uuid(73e6ff4a-ab99-4d99-ac00-ba39ccb8e4d7)]
 interface nsIXPConnectJSObjectHolder : nsISupports
 {
     [notxpcom, nostdcall] JSObjectPtr GetJSObject();
 };
 
-[uuid(e787be29-db5d-4a45-a3d6-1de1d6b85c30)]
+[builtinclass, uuid(e787be29-db5d-4a45-a3d6-1de1d6b85c30)]
 interface nsIXPConnectWrappedNative : nsIXPConnectJSObjectHolder
 {
     /* attribute 'JSObject' inherited from nsIXPConnectJSObjectHolder */
 
     void debugDump(in short depth);
 
 %{C++
     /**
@@ -79,17 +79,17 @@ do_QueryWrappedNative(nsIXPConnectWrappe
                       nsresult *aError)
 
 {
     return nsQueryInterfaceWithError(aWrappedNative->Native(), aError);
 }
 
 %}
 
-[uuid(3a01b0d6-074b-49ed-bac3-08c76366cae4)]
+[builtinclass, uuid(3a01b0d6-074b-49ed-bac3-08c76366cae4)]
 interface nsIXPConnectWrappedJS : nsIXPConnectJSObjectHolder
 {
     /* attribute 'JSObject' inherited from nsIXPConnectJSObjectHolder */
     readonly attribute InterfaceInfoPtr InterfaceInfo;
     readonly attribute nsIIDPtr         InterfaceIID;
 
     // Match the GetJSObject() signature.
     [notxpcom, nostdcall] JSObjectPtr GetJSObjectGlobal();
@@ -177,17 +177,17 @@ interface nsIXPConnectWrappedJSUnmarkGra
  *   iii) Avoiding the explicit interface makes it easier for both the caller
  *        and the component.
  *
  *  Anyway, some future implementation of nsIXPCSecurityManager might want
  *  do special processing on 'nsIXPCSecurityManager::CanGetProperty' when
  *  the interface id is that of nsIXPCWrappedJSObjectGetter.
  */
 
-[scriptable, uuid(254bb2e0-6439-11d4-8fe0-0010a4e73d9a)]
+[builtinclass, uuid(254bb2e0-6439-11d4-8fe0-0010a4e73d9a)]
 interface nsIXPCWrappedJSObjectGetter : nsISupports
 {
     readonly attribute nsISupports neverCalled;
 };
 
 /***************************************************************************/
 
 
--- a/js/xpconnect/idl/xpcIJSWeakReference.idl
+++ b/js/xpconnect/idl/xpcIJSWeakReference.idl
@@ -1,15 +1,15 @@
 /* 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.idl"
 
-[scriptable, uuid(75767928-ecb1-4e6c-9f55-c118b297fcef)]
+[scriptable, builtinclass, uuid(75767928-ecb1-4e6c-9f55-c118b297fcef)]
 interface xpcIJSWeakReference : nsISupports
 {
   /**
    * To be called from JS only.
    *
    * Returns the referenced JS object or null if the JS object has
    * been garbage collected.
    */
--- a/js/xpconnect/idl/xpccomponents.idl
+++ b/js/xpconnect/idl/xpccomponents.idl
@@ -21,102 +21,102 @@ interface nsIJSIID;
 interface nsIPrincipal;
 interface nsIStackFrame;
 webidl Element;
 
 /**
 * interface of Components.interfacesByID
 * (interesting stuff only reflected into JavaScript)
 */
-[scriptable, uuid(f235ef76-9919-478b-aa0f-282d994ddf76)]
+[scriptable, builtinclass, uuid(f235ef76-9919-478b-aa0f-282d994ddf76)]
 interface nsIXPCComponents_InterfacesByID : nsISupports
 {
 };
 
 /**
 * interface of Components.interfaces
 * (interesting stuff only reflected into JavaScript)
 */
-[scriptable, uuid(b8c31bba-79db-4a1d-930d-4cdd68713f9e)]
+[scriptable, builtinclass, uuid(b8c31bba-79db-4a1d-930d-4cdd68713f9e)]
 interface nsIXPCComponents_Interfaces : nsISupports
 {
 };
 
 /**
 * interface of Components.classes
 * (interesting stuff only reflected into JavaScript)
 */
-[scriptable, uuid(978ff520-d26c-11d2-9842-006008962422)]
+[scriptable, builtinclass, uuid(978ff520-d26c-11d2-9842-006008962422)]
 interface nsIXPCComponents_Classes : nsISupports
 {
   // Make it so that |cid| gets mapped to |idString|.
   void initialize(in nsIJSCID cid, in string idString);
 };
 
 /**
 * interface of Components.classesByID
 * (interesting stuff only reflected into JavaScript)
 */
-[scriptable, uuid(336a9590-4d19-11d3-9893-006008962422)]
+[scriptable, builtinclass, uuid(336a9590-4d19-11d3-9893-006008962422)]
 interface nsIXPCComponents_ClassesByID : nsISupports
 {
 };
 
 /**
 * interface of Components.results
 * (interesting stuff only reflected into JavaScript)
 */
-[scriptable, uuid(2fc229a0-5860-11d3-9899-006008962422)]
+[scriptable, builtinclass, uuid(2fc229a0-5860-11d3-9899-006008962422)]
 interface nsIXPCComponents_Results : nsISupports
 {
 };
 
 /**
 * interface of Components.ID
 * (interesting stuff only reflected into JavaScript)
 */
-[scriptable, uuid(7994a6e0-e028-11d3-8f5d-0010a4e73d9a)]
+[scriptable, builtinclass, uuid(7994a6e0-e028-11d3-8f5d-0010a4e73d9a)]
 interface nsIXPCComponents_ID : nsISupports
 {
 };
 
 /**
 * interface of Components.Exception
 * (interesting stuff only reflected into JavaScript)
 */
-[scriptable, uuid(5bf039c0-e028-11d3-8f5d-0010a4e73d9a)]
+[scriptable, builtinclass, uuid(5bf039c0-e028-11d3-8f5d-0010a4e73d9a)]
 interface nsIXPCComponents_Exception : nsISupports
 {
 };
 
 /**
 * interface of Components.Constructor
 * (interesting stuff only reflected into JavaScript)
 */
-[scriptable, uuid(88655640-e028-11d3-8f5d-0010a4e73d9a)]
+[scriptable, builtinclass, uuid(88655640-e028-11d3-8f5d-0010a4e73d9a)]
 interface nsIXPCComponents_Constructor : nsISupports
 {
 };
 
 /**
 * interface of object returned by Components.Constructor
 * (additional interesting stuff only reflected into JavaScript)
 */
-[scriptable, uuid(c814ca20-e0dc-11d3-8f5f-0010a4e73d9a)]
+[scriptable, builtinclass, uuid(c814ca20-e0dc-11d3-8f5f-0010a4e73d9a)]
 interface nsIXPCConstructor : nsISupports
 {
     readonly attribute nsIJSCID classID;
     readonly attribute nsIJSIID interfaceID;
     readonly attribute string   initializer;
 };
 
 /**
 * interface of object returned by Components.utils.Sandbox.
 */
-[scriptable, uuid(4f8ae0dc-d266-4a32-875b-6a9de71a8ce9)]
+[scriptable, builtinclass, uuid(4f8ae0dc-d266-4a32-875b-6a9de71a8ce9)]
 interface nsIXPCComponents_utils_Sandbox : nsISupports
 {
 };
 
 /**
  * interface for callback to be passed to Cu.schedulePreciseGC
  */
 [scriptable, function, uuid(71000535-b0fd-44d1-8ce0-909760e3953c)]
@@ -132,17 +132,17 @@ interface ScheduledGCCallback : nsISuppo
 interface nsIBlockThreadedExecutionCallback : nsISupports
 {
     void callback();
 };
 
 /**
 * interface of Components.utils
 */
-[scriptable, uuid(86003fe3-ee9a-4620-91dc-eef8b1e58815)]
+[scriptable, builtinclass, uuid(86003fe3-ee9a-4620-91dc-eef8b1e58815)]
 interface nsIXPCComponents_Utils : nsISupports
 {
 
     /* reportError is designed to be called from JavaScript only.
      *
      * It will report a JS Error object to the JS console, and return. It
      * is meant for use in exception handler blocks which want to "eat"
      * an exception, but still want to report it to the console.
@@ -725,28 +725,28 @@ interface nsIXPCComponents_Utils : nsISu
 /**
 * Interface for the 'Components' object.
 *
 * The first interface contains things that are available to non-chrome XBL code
 * that runs in a scope with an ExpandedPrincipal. The second interface
 * includes members that are only exposed to chrome.
 */
 
-[scriptable, uuid(eeeada2f-86c0-4609-b2bf-4bf2351b1ce6)]
+[scriptable, builtinclass, uuid(eeeada2f-86c0-4609-b2bf-4bf2351b1ce6)]
 interface nsIXPCComponentsBase : nsISupports
 {
     readonly attribute nsIXPCComponents_Interfaces      interfaces;
     readonly attribute nsIXPCComponents_InterfacesByID  interfacesByID;
     readonly attribute nsIXPCComponents_Results         results;
 
     boolean isSuccessCode(in nsresult result);
 
 };
 
-[scriptable, uuid(aa28aaf6-70ce-4b03-9514-afe43c7dfda8)]
+[scriptable, builtinclass, uuid(aa28aaf6-70ce-4b03-9514-afe43c7dfda8)]
 interface nsIXPCComponents : nsIXPCComponentsBase
 {
     readonly attribute nsIXPCComponents_Classes         classes;
     readonly attribute nsIXPCComponents_ClassesByID     classesByID;
     // Will return null if there is no JS stack right now.
     readonly attribute nsIStackFrame                    stack;
     readonly attribute nsIComponentManager              manager;
     readonly attribute nsIXPCComponents_Utils           utils;
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_xrayed_arguments.js
@@ -0,0 +1,16 @@
+function run_test() {
+  var sbContent = Cu.Sandbox(null);
+  let xrayedArgs = sbContent.eval("(function(a, b) { return arguments; })('hi', 42)");
+
+  function checkArgs(a) {
+    Assert.equal(a.length, 2);
+    Assert.equal(a[0], 'hi');
+    Assert.equal(a[1], 42);
+  }
+
+  // Check Xrays to the args.
+  checkArgs(xrayedArgs);
+
+  // Make sure the spread operator works.
+  checkArgs([...xrayedArgs]);
+}
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -130,16 +130,17 @@ head = head_watchdog.js
 head = head_watchdog.js
 [test_watchdog_default.js]
 head = head_watchdog.js
 skip-if = (verify && debug && os == 'android')
 [test_watchdog_hibernate.js]
 head = head_watchdog.js
 [test_weak_keys.js]
 [test_xpcwn_tamperproof.js]
+[test_xrayed_arguments.js]
 [test_xrayed_iterator.js]
 [test_xray_named_element_access.js]
 [test_xray_SavedFrame.js]
 [test_xray_SavedFrame-02.js]
 [test_xray_regexp.js]
 [test_resolve_dead_promise.js]
 [test_asyncLoadSubScriptError.js]
 [test_function_names.js]
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -1035,16 +1035,39 @@ JSXrayTraits::createHolder(JSContext* cx
     bool isPrototype = false;
     JSProtoKey key = IdentifyStandardInstance(target);
     if (key == JSProto_Null) {
         isPrototype = true;
         key = IdentifyStandardPrototype(target);
     }
     MOZ_ASSERT(key != JSProto_Null);
 
+    // Special case: pretend Arguments objects are arrays for Xrays.
+    //
+    // Arguments objects are strange beasts - they inherit Object.prototype,
+    // and implement iteration by defining an |own| property for
+    // Symbol.iterator. Since this value is callable, Array/Object Xrays will
+    // filter it out, causing the Xray view to be non-iterable, which in turn
+    // breaks consumers.
+    //
+    // We can't trust the iterator value from the content compartment,
+    // but the generic one on Array.prototype works well enough. So we force
+    // the Xray view of Arguments objects to inherit Array.prototype, which
+    // in turn allows iteration via the inherited Array.prototype[Symbol.iterator].
+    // This doesn't emulate any of the weird semantics of Arguments iterators,
+    // but is probably good enough.
+    //
+    // Note that there are various Xray traps that do other special behavior for
+    // JSProto_Array, but they also provide that special behavior for
+    // JSProto_Object, and since Arguments would otherwise get JSProto_Object,
+    // this does not cause any behavior change at those sites.
+    if (key == JSProto_Object && js::IsArgumentsObject(target)) {
+        key = JSProto_Array;
+    }
+
     // Store it on the holder.
     RootedValue v(cx);
     v.setNumber(static_cast<uint32_t>(key));
     js::SetReservedSlot(holder, SLOT_PROTOKEY, v);
     v.setBoolean(isPrototype);
     js::SetReservedSlot(holder, SLOT_ISPROTOTYPE, v);
 
     // If this is a function, also compute whether it serves as a constructor
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -651,16 +651,19 @@ struct nsFrameItems : public nsFrameList
   // Appends the frame to the end of the list
   void AddChild(nsIFrame* aChild);
 };
 
 void
 nsFrameItems::AddChild(nsIFrame* aChild)
 {
   MOZ_ASSERT(aChild, "nsFrameItems::AddChild");
+  MOZ_ASSERT(!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) ||
+             aChild->GetPlaceholderFrame(),
+             "An out-of-flow child without a placeholder frame?");
 
   // It'd be really nice if we could just AppendFrames(kPrincipalList, aChild) here,
   // but some of our callers put frames that have different
   // parents (caption, I'm looking at you) on the same framelist, and
   // nsFrameList asserts if you try to do that.
   if (IsEmpty()) {
     SetFrames(aChild);
   }
@@ -685,36 +688,23 @@ struct nsAbsoluteItems : nsFrameItems {
   // XXXbz Does this need a debug-only assignment operator that nulls out the
   // childList in the nsAbsoluteItems we're copying?  Introducing a difference
   // between debug and non-debug behavior seems bad, so I guess not...
   ~nsAbsoluteItems() {
     NS_ASSERTION(!FirstChild(),
                  "Dangling child list.  Someone forgot to insert it?");
   }
 #endif
-
-  // Appends the frame to the end of the list
-  void AddChild(nsIFrame* aChild);
 };
 
 nsAbsoluteItems::nsAbsoluteItems(nsContainerFrame* aContainingBlock)
   : containingBlock(aContainingBlock)
 {
 }
 
-// Additional behavior is that it sets the frame's NS_FRAME_OUT_OF_FLOW flag
-void
-nsAbsoluteItems::AddChild(nsIFrame* aChild)
-{
-  aChild->AddStateBits(NS_FRAME_OUT_OF_FLOW);
-  NS_ASSERTION(aChild->GetPlaceholderFrame(),
-               "Child without placeholder being added to nsAbsoluteItems?");
-  nsFrameItems::AddChild(aChild);
-}
-
 // -----------------------------------------------------------
 
 // Structure for saving the existing state when pushing/poping containing
 // blocks. The destructor restores the state to its previous state
 class MOZ_STACK_CLASS nsFrameConstructorSaveState {
 public:
   typedef nsIFrame::ChildListID ChildListID;
   nsFrameConstructorSaveState();
@@ -2985,18 +2975,17 @@ nsCSSFrameConstructor::CreatePlaceholder
                                                  nsIFrame*         aPrevInFlow,
                                                  nsFrameState      aTypeBit)
 {
   RefPtr<ComputedStyle> placeholderStyle = aPresShell->StyleSet()->
     ResolveStyleForPlaceholder();
 
   // The placeholder frame gets a pseudo style.
   nsPlaceholderFrame* placeholderFrame =
-    (nsPlaceholderFrame*)NS_NewPlaceholderFrame(aPresShell, placeholderStyle,
-                                                aTypeBit);
+    NS_NewPlaceholderFrame(aPresShell, placeholderStyle, aTypeBit);
 
   placeholderFrame->Init(aContent, aParentFrame, aPrevInFlow);
 
   // Associate the placeholder/out-of-flow with each other.
   placeholderFrame->SetOutOfFlowFrame(aFrame);
   aFrame->SetProperty(nsIFrame::PlaceholderFrameProperty(), placeholderFrame);
 
   aFrame->AddStateBits(NS_FRAME_OUT_OF_FLOW);
--- a/layout/generic/nsPlaceholderFrame.cpp
+++ b/layout/generic/nsPlaceholderFrame.cpp
@@ -21,17 +21,17 @@
 #include "nsLayoutUtils.h"
 #include "nsPresContext.h"
 #include "nsIFrameInlines.h"
 #include "nsIContentInlines.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
-nsIFrame*
+nsPlaceholderFrame*
 NS_NewPlaceholderFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle,
                        nsFrameState aTypeBits)
 {
   return new (aPresShell) nsPlaceholderFrame(aStyle, aTypeBits);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsPlaceholderFrame)
 
--- a/layout/generic/nsPlaceholderFrame.h
+++ b/layout/generic/nsPlaceholderFrame.h
@@ -34,19 +34,20 @@
 
 #ifndef nsPlaceholderFrame_h___
 #define nsPlaceholderFrame_h___
 
 #include "mozilla/Attributes.h"
 #include "nsFrame.h"
 #include "nsGkAtoms.h"
 
-nsIFrame* NS_NewPlaceholderFrame(nsIPresShell* aPresShell,
-                                 mozilla::ComputedStyle* aStyle,
-                                 nsFrameState aTypeBits);
+class nsPlaceholderFrame;
+nsPlaceholderFrame* NS_NewPlaceholderFrame(nsIPresShell* aPresShell,
+                                           mozilla::ComputedStyle* aStyle,
+                                           nsFrameState aTypeBits);
 
 #define PLACEHOLDER_TYPE_MASK    (PLACEHOLDER_FOR_FLOAT | \
                                   PLACEHOLDER_FOR_ABSPOS | \
                                   PLACEHOLDER_FOR_FIXEDPOS | \
                                   PLACEHOLDER_FOR_POPUP | \
                                   PLACEHOLDER_FOR_TOPLAYER)
 
 /**
@@ -59,19 +60,20 @@ public:
 #ifdef DEBUG
   NS_DECL_QUERYFRAME
 #endif
 
   /**
    * Create a new placeholder frame.  aTypeBit must be one of the
    * PLACEHOLDER_FOR_* constants above.
    */
-  friend nsIFrame* NS_NewPlaceholderFrame(nsIPresShell* aPresShell,
-                                          ComputedStyle* aStyle,
-                                          nsFrameState aTypeBits);
+  friend nsPlaceholderFrame* NS_NewPlaceholderFrame(nsIPresShell* aPresShell,
+                                                    ComputedStyle* aStyle,
+                                                    nsFrameState aTypeBits);
+
   nsPlaceholderFrame(ComputedStyle* aStyle, nsFrameState aTypeBits)
     : nsFrame(aStyle, kClassID)
     , mOutOfFlowFrame(nullptr)
   {
     MOZ_ASSERT(aTypeBits == PLACEHOLDER_FOR_FLOAT ||
                aTypeBits == PLACEHOLDER_FOR_ABSPOS ||
                aTypeBits == PLACEHOLDER_FOR_FIXEDPOS ||
                aTypeBits == PLACEHOLDER_FOR_POPUP ||
new file mode 100644
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1411707_findbar.py
@@ -0,0 +1,42 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from __future__ import absolute_import
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+    """Bug 1411707 - Migrate the findbar XBL binding to a Custom Element, part {index}."""
+
+    ctx.add_transforms(
+        'toolkit/toolkit/main-window/findbar.ftl',
+        'toolkit/toolkit/main-window/findbar.ftl',
+        transforms_from(
+"""
+findbar-next =
+    .tooltiptext = { COPY(from_path, "next.tooltip") }
+findbar-previous =
+    .tooltiptext = { COPY(from_path, "previous.tooltip") }
+
+findbar-find-button-close =
+    .tooltiptext = { COPY(from_path, "findCloseButton.tooltip") }
+
+findbar-highlight-all =
+    .label = { COPY(from_path, "highlightAll.label") }
+    .accesskey = { COPY(from_path, "highlightAll.accesskey") }
+    .tooltiptext = { COPY(from_path, "highlightAll.tooltiptext") }
+
+findbar-case-sensitive =
+    .label = { COPY(from_path, "caseSensitive.label") }
+    .accesskey = { COPY(from_path, "caseSensitive.accesskey") }
+    .tooltiptext = { COPY(from_path, "caseSensitive.tooltiptext") }
+
+findbar-entire-word =
+    .label = { COPY(from_path, "entireWord.label") }
+    .accesskey = { COPY(from_path, "entireWord.accesskey") }
+    .tooltiptext = { COPY(from_path, "entireWord.tooltiptext") }
+""", from_path="toolkit/chrome/global/findbar.dtd")
+    )
--- a/testing/mozharness/mozharness/mozilla/testing/codecoverage.py
+++ b/testing/mozharness/mozharness/mozilla/testing/codecoverage.py
@@ -414,17 +414,17 @@ class CodeCoverageMixin(SingleTestMixin)
                         continue
 
                     # TODO: Optimize this part which loads JSONs
                     # with a size of about 40Mb into memory for diffing later.
                     # Bug 1460064 is filed for this.
                     with open(grcov_file, 'r') as f:
                         data = json.load(f)
 
-                    if suite in test:
+                    if suite in os.path.split(test)[-1]:
                         baseline_tests_suite_cov[suite] = data
                     else:
                         _, baseline_filetype = os.path.splitext(test)
                         baseline_tests_ext_cov[baseline_filetype] = data
 
             dest = os.path.join(dirs['abs_blob_upload_dir'], 'per-test-coverage-reports.zip')
             with zipfile.ZipFile(dest, 'w', zipfile.ZIP_DEFLATED) as z:
                 for suite, data in self.per_test_reports.items():
--- a/testing/profiles/raptor/user.js
+++ b/testing/profiles/raptor/user.js
@@ -1,6 +1,7 @@
 // Preferences file used by the raptor harness
 /* globals user_pref */
 user_pref("dom.performance.time_to_non_blank_paint.enabled", true);
+user_pref("dom.performance.time_to_dom_content_flushed.enabled", true);
 
 // required for geckoview logging
 user_pref("geckoview.console.enabled", true);
--- a/testing/raptor/raptor/manifest.py
+++ b/testing/raptor/raptor/manifest.py
@@ -71,16 +71,18 @@ def write_test_settings_json(test_detail
             "type": test_details['type'],
             "test_url": test_url,
             "page_cycles": int(test_details['page_cycles'])
         }
     }
 
     if test_details['type'] == "pageload":
         test_settings['raptor-options']['measure'] = {}
+        if "dcf" in test_details['measure']:
+            test_settings['raptor-options']['measure']['dcf'] = True
         if "fnbpaint" in test_details['measure']:
             test_settings['raptor-options']['measure']['fnbpaint'] = True
         if "fcp" in test_details['measure']:
             test_settings['raptor-options']['measure']['fcp'] = True
         if "hero" in test_details['measure']:
             test_settings['raptor-options']['measure']['hero'] = test_details['hero'].split()
     if test_details.get("page_timeout", None) is not None:
         test_settings['raptor-options']['page_timeout'] = int(test_details['page_timeout'])
--- a/testing/raptor/raptor/tests/raptor-tp6.ini
+++ b/testing/raptor/raptor/tests/raptor-tp6.ini
@@ -14,41 +14,41 @@ page_cycles = 25
 unit = ms
 lower_is_better = true
 alert_threshold = 2.0
 
 [raptor-tp6-amazon-firefox]
 apps = firefox
 test_url = https://www.amazon.com/s/url=search-alias%3Daps&field-keywords=laptop
 playback_recordings = amazon.mp
-measure = fnbpaint, hero
+measure = fnbpaint, hero, dcf
 hero = hero1
 
 [raptor-tp6-facebook-firefox]
 apps = firefox
 test_url = https://www.facebook.com
 playback_recordings = facebook.mp
-measure = fnbpaint, hero
+measure = fnbpaint, hero, dcf
 hero = hero1
 
 [raptor-tp6-google-firefox]
 apps = firefox
 # note: use the full url as the first part (without '&cad=h') redirects
 # to the url + '&cad=h'; that redirection causes measure.js content
 # to be loaded into that page also; resulting in 2 fnbpaint values etc.
 test_url = https://www.google.com/search?hl=en&q=barack+obama&cad=h
 playback_recordings = google-search.mp
-measure = fnbpaint, hero
+measure = fnbpaint, hero, dcf
 hero = hero1
 
 [raptor-tp6-youtube-firefox]
 apps = firefox
 test_url = https://www.youtube.com
 playback_recordings = youtube.mp
-measure = fnbpaint, hero
+measure = fnbpaint, hero, dcf
 hero = hero1
 
 [raptor-tp6-amazon-chrome]
 apps = chrome
 test_url = https://www.amazon.com/s/url=search-alias%3Daps&field-keywords=laptop
 playback_recordings = amazon.mp
 measure = fcp, hero
 hero = hero1
--- a/testing/raptor/webext/raptor/measure.js
+++ b/testing/raptor/webext/raptor/measure.js
@@ -12,16 +12,22 @@ var getHero = false;
 var heroesToCapture = [];
 
 // measure firefox time-to-first-non-blank-paint
 // note: this browser pref must be enabled:
 // dom.performance.time_to_non_blank_paint.enabled = True
 // default only; this is set via control server settings json
 var getFNBPaint = false;
 
+// measure firefox domContentFlushed
+// note: this browser pref must be enabled:
+// dom.performance.time_to_dom_content_flushed.enabled = True
+// default only; this is set via control server settings json
+var getDCF = false;
+
 // measure google's first-contentful-paint
 // default only; this is set via control server settings json
 var getFCP = false;
 
 // performance.timing measurement used as 'starttime'
 var startMeasure = "fetchStart";
 
 function contentHandler() {
@@ -52,16 +58,24 @@ function setup(settings) {
   if (settings.measure.fnbpaint !== undefined) {
     getFNBPaint = settings.measure.fnbpaint;
     if (getFNBPaint) {
       console.log("will be measuring fnbpaint");
       measureFNBPaint();
     }
   }
 
+  if (settings.measure.dcf !== undefined) {
+    getDCF = settings.measure.dcf;
+    if (getDCF) {
+      console.log("will be measuring dcf");
+      measureDCF();
+    }
+  }
+
   if (settings.measure.fcp !== undefined) {
     getFCP = settings.measure.fcp;
     if (getFCP) {
       console.log("will be measuring first-contentful-paint");
       measureFirstContentfulPaint();
     }
   }
 
@@ -137,16 +151,39 @@ function measureFNBPaint() {
       console.log("\nfnbpaint is not yet available (0), retry number " + gRetryCounter + "...\n");
       window.setTimeout(measureFNBPaint, 100);
     } else {
       console.log("\nunable to get a value for fnbpaint after " + gRetryCounter + " retries\n");
     }
   }
 }
 
+function measureDCF() {
+  var x = window.performance.timing.timeToDOMContentFlushed;
+
+  if (typeof(x) == "undefined") {
+    console.log("ERROR: domContentFlushed is undefined; ensure the pref is enabled");
+    return;
+  }
+  if (x > 0) {
+    console.log("got domContentFlushed: " + x);
+    gRetryCounter = 0;
+    var startTime = perfData.timing.fetchStart;
+    sendResult("dcf", x - startTime);
+  } else {
+    gRetryCounter += 1;
+    if (gRetryCounter <= 10) {
+      console.log("\dcf is not yet available (0), retry number " + gRetryCounter + "...\n");
+      window.setTimeout(measureDCF, 100);
+    } else {
+      console.log("\nunable to get a value for dcf after " + gRetryCounter + " retries\n");
+    }
+  }
+}
+
 function measureFirstContentfulPaint() {
   // see https://developer.mozilla.org/en-US/docs/Web/API/PerformancePaintTiming
   var resultType = "fcp";
   var result = 0;
 
   let performanceEntries = perfData.getEntriesByType("paint");
 
   if (performanceEntries.length >= 2) {
--- a/testing/raptor/webext/raptor/runner.js
+++ b/testing/raptor/webext/raptor/runner.js
@@ -31,21 +31,23 @@ var benchmarkPort = null;
 var testType;
 var pageCycles = 0;
 var pageCycle = 0;
 var testURL;
 var testTabID = 0;
 var getHero = false;
 var getFNBPaint = false;
 var getFCP = false;
+var getDCF = false;
 var isHeroPending = false;
 var pendingHeroes = [];
 var settings = {};
 var isFNBPaintPending = false;
 var isFCPPending = false;
+var isDCFPending = false;
 var isBenchmarkPending = false;
 var pageTimeout = 10000; // default pageload timeout
 
 var results = {"name": "",
                "page": "",
                "type": "",
                "lower_is_better": true,
                "alert_threshold": 2.0,
@@ -88,16 +90,19 @@ function getTestSettings() {
         }
         console.log("using page timeout (ms): " + pageTimeout);
 
         if (testType == "pageload") {
           if (settings.measure !== undefined) {
             if (settings.measure.fnbpaint !== undefined) {
               getFNBPaint = settings.measure.fnbpaint;
             }
+            if (settings.measure.dcf !== undefined) {
+              getDCF = settings.measure.dcf;
+            }
             if (settings.measure.fcp !== undefined) {
               getFCP = settings.measure.fcp;
             }
             if (settings.measure.hero !== undefined) {
               if (settings.measure.hero.length !== 0) {
                 getHero = true;
               }
             }
@@ -167,17 +172,17 @@ async function testTabUpdated(tab) {
   nextCycle();
 }
 
 function waitForResult() {
   console.log("awaiting results...");
   return new Promise(resolve => {
     function checkForResult() {
       if (testType == "pageload") {
-        if (!isHeroPending && !isFNBPaintPending && !isFCPPending) {
+        if (!isHeroPending && !isFNBPaintPending && !isFCPPending && !isDCFPending) {
           cancelTimeoutAlarm("raptor-page-timeout");
           resolve();
         } else {
           setTimeout(checkForResult, 5);
         }
       } else if (testType == "benchmark") {
         if (!isBenchmarkPending) {
           cancelTimeoutAlarm("raptor-page-timeout");
@@ -210,16 +215,18 @@ function nextCycle() {
         if (getHero) {
           isHeroPending = true;
           pendingHeroes = Array.from(settings.measure.hero);
         }
         if (getFNBPaint)
           isFNBPaintPending = true;
         if (getFCP)
           isFCPPending = true;
+        if (getDCF)
+          isDCFPending = true;
       } else if (testType == "benchmark") {
         isBenchmarkPending = true;
       }
       // update the test page - browse to our test URL
       ext.tabs.update(testTabID, {url: testURL}, testTabUpdated);
     }, pageCycleDelay);
   } else {
     verifyResults();
@@ -283,16 +290,19 @@ function resultListener(request, sender,
           if (pendingHeroes.length == 0) {
             console.log("measured all expected hero elements");
             isHeroPending = false;
           }
         }
       } else if (request.type == "fnbpaint") {
         results.measurements.fnbpaint.push(request.value);
         isFNBPaintPending = false;
+      } else if (request.type == "dcf") {
+        results.measurements.dcf.push(request.value);
+        isDCFPending = false;
       } else if (request.type == "fcp") {
         results.measurements.fcp.push(request.value);
         isFCPPending = false;
       }
     } else if (testType == "benchmark") {
       // benchmark results received (all results for that complete benchmark run)
       console.log("received results from benchmark");
       results.measurements[request.type].push(request.value);
deleted file mode 100644
--- a/testing/web-platform/meta/webdriver/tests/execute_async_script/collections.py.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[collections.py]
-  [test_arguments]
-    bug: 1453057
-    expected: FAIL
-
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_findbar.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_findbar.js
@@ -23,17 +23,17 @@ add_task(async function test_support_too
     files: {
       "image1.png": BACKGROUND,
     },
   });
 
   await extension.startup();
   await gBrowser.getFindBar();
 
-  let findbar_button = document.getAnonymousElementByAttribute(gFindBar, "anonid", "highlight");
+  let findbar_button = gFindBar.getElement("highlight");
 
   info("Checking findbar background is set as toolbar color");
   Assert.equal(window.getComputedStyle(gFindBar).backgroundColor,
                hexToCSS(TOOLBAR_COLOR),
                "Findbar background color should be the same as toolbar background color.");
 
   info("Checking findbar and button text color is set as toolbar text color");
   Assert.equal(window.getComputedStyle(gFindBar).color,
@@ -68,24 +68,21 @@ add_task(async function test_support_too
     files: {
       "image1.png": BACKGROUND,
     },
   });
 
   await extension.startup();
   await gBrowser.getFindBar();
 
-  let findbar_textbox =
-    document.getAnonymousElementByAttribute(gFindBar, "anonid", "findbar-textbox");
+  let findbar_textbox = gFindBar.getElement("findbar-textbox");
 
-  let findbar_prev_button =
-    document.getAnonymousElementByAttribute(gFindBar, "anonid", "find-previous");
+  let findbar_prev_button = gFindBar.getElement("find-previous");
 
-  let findbar_next_button =
-    document.getAnonymousElementByAttribute(gFindBar, "anonid", "find-next");
+  let findbar_next_button = gFindBar.getElement("find-next");
 
   info("Checking findbar textbox background is set as toolbar field background color");
   Assert.equal(window.getComputedStyle(findbar_textbox).backgroundColor,
                hexToCSS(TOOLBAR_FIELD_COLOR),
                "Findbar textbox background color should be the same as toolbar field color.");
 
   info("Checking findbar textbox color is set as toolbar field text color");
   Assert.equal(window.getComputedStyle(findbar_textbox).color,
--- a/toolkit/components/normandy/actions/AddonStudyAction.jsm
+++ b/toolkit/components/normandy/actions/AddonStudyAction.jsm
@@ -135,17 +135,19 @@ class AddonStudyAction extends BaseActio
     // add-on installed but no record of it, which would leave it permanently
     // installed.
 
     const { addonUrl, name, description } = recipe.arguments;
 
     const downloadDeferred = PromiseUtils.defer();
     const installDeferred = PromiseUtils.defer();
 
-    const install = await AddonManager.getInstallForURL(addonUrl, "application/x-xpinstall");
+    const install = await AddonManager.getInstallForURL(addonUrl, "application/x-xpinstall",
+                                                        null, null, null, null, null,
+                                                        {source: "internal"});
 
     const listener = {
       onDownloadFailed() {
         downloadDeferred.reject(new AddonStudyEnrollError(name, "download-failure"));
       },
 
       onDownloadEnded() {
         downloadDeferred.resolve();
--- a/toolkit/components/normandy/test/browser/browser_actions_AddonStudyAction.js
+++ b/toolkit/components/normandy/test/browser/browser_actions_AddonStudyAction.js
@@ -124,16 +124,19 @@ decorate_task(
     const recipe = addonStudyRecipeFactory({ arguments: { name: "success", addonUrl } });
     const action = new AddonStudyAction();
     await action.runRecipe(recipe);
 
     await webExtStartupPromise;
     addon = await AddonManager.getAddonByID(FIXTURE_ADDON_ID);
     ok(addon, "After start is called, the add-on is installed");
 
+    Assert.deepEqual(addon.installTelemetryInfo, {source: "internal"},
+                     "Got the expected installTelemetryInfo");
+
     const study = await AddonStudies.get(recipe.id);
     Assert.deepEqual(
       study,
       {
         recipeId: recipe.id,
         name: recipe.arguments.name,
         description: recipe.arguments.description,
         addonId: FIXTURE_ADDON_ID,
--- a/toolkit/content/customElements.js
+++ b/toolkit/content/customElements.js
@@ -151,27 +151,27 @@ function getInterfaceProxy(obj) {
 
   return obj._customInterfaceProxy;
 }
 
 // Attach the base class to the window so other scripts can use it:
 window.MozXULElement = MozXULElement;
 
 for (let script of [
-  "chrome://global/content/elements/stringbundle.js",
   "chrome://global/content/elements/general.js",
   "chrome://global/content/elements/textbox.js",
   "chrome://global/content/elements/tabbox.js",
 ]) {
   Services.scriptloader.loadSubScript(script, window);
 }
 
-customElements.setElementCreationCallback("printpreview-toolbar", type => {
-  Services.scriptloader.loadSubScript(
-    "chrome://global/content/printPreviewToolbar.js", window);
-});
-
-customElements.setElementCreationCallback("editor", type => {
-  Services.scriptloader.loadSubScript(
-    "chrome://global/content/elements/editor.js", window);
-});
+for (let [tag, script] of [
+  ["findbar", "chrome://global/content/elements/findbar.js"],
+  ["stringbundle", "chrome://global/content/elements/stringbundle.js"],
+  ["printpreview-toolbar", "chrome://global/content/printPreviewToolbar.js"],
+  ["editor", "chrome://global/content/elements/editor.js"],
+]) {
+  customElements.setElementCreationCallback(tag, () => {
+    Services.scriptloader.loadSubScript(script, window);
+  });
+}
 
 }
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -70,17 +70,16 @@ toolkit.jar:
    content/global/bindings/checkbox.xml        (widgets/checkbox.xml)
    content/global/bindings/colorpicker.xml     (widgets/colorpicker.xml)
    content/global/bindings/datekeeper.js       (widgets/datekeeper.js)
    content/global/bindings/datepicker.js       (widgets/datepicker.js)
    content/global/bindings/datetimepopup.xml   (widgets/datetimepopup.xml)
    content/global/bindings/datetimebox.xml     (widgets/datetimebox.xml)
    content/global/bindings/datetimebox.css     (widgets/datetimebox.css)
 *  content/global/bindings/dialog.xml          (widgets/dialog.xml)
-*  content/global/bindings/findbar.xml         (widgets/findbar.xml)
    content/global/bindings/general.xml         (widgets/general.xml)
    content/global/bindings/groupbox.xml        (widgets/groupbox.xml)
    content/global/bindings/menu.xml            (widgets/menu.xml)
    content/global/bindings/menulist.xml        (widgets/menulist.xml)
    content/global/bindings/notification.xml    (widgets/notification.xml)
    content/global/bindings/numberbox.xml       (widgets/numberbox.xml)
    content/global/bindings/popup.xml           (widgets/popup.xml)
    content/global/bindings/progressmeter.xml   (widgets/progressmeter.xml)
@@ -94,17 +93,18 @@ toolkit.jar:
 *  content/global/bindings/textbox.xml         (widgets/textbox.xml)
    content/global/bindings/timekeeper.js       (widgets/timekeeper.js)
    content/global/bindings/timepicker.js       (widgets/timepicker.js)
    content/global/bindings/toolbar.xml         (widgets/toolbar.xml)
    content/global/bindings/toolbarbutton.xml   (widgets/toolbarbutton.xml)
    content/global/bindings/tree.xml            (widgets/tree.xml)
    content/global/bindings/videocontrols.xml   (widgets/videocontrols.xml)
 *  content/global/bindings/wizard.xml          (widgets/wizard.xml)
-   content/global/elements/editor.js           (widgets/editor.js)
+   content/global/elements/findbar.js          (widgets/findbar.js)
+   content/global/elements/editor.js          (widgets/editor.js)
    content/global/elements/general.js          (widgets/general.js)
    content/global/elements/stringbundle.js     (widgets/stringbundle.js)
    content/global/elements/tabbox.js           (widgets/tabbox.js)
    content/global/elements/textbox.js          (widgets/textbox.js)
    content/global/elements/videocontrols.js    (widgets/videocontrols.js)
 #ifdef XP_MACOSX
    content/global/macWindowMenu.js
 #endif
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -46,16 +46,19 @@ support-files =
 [browser_autoplay_policy_request_permission.js]
 support-files =
   file_empty.html
   gizmo.mp4
 [browser_autoplay_policy_user_gestures.js]
 support-files =
   gizmo.mp4
   file_video.html
+[browser_autoplay_policy_web_audio.js]
+support-files =
+  file_empty.html
 [browser_autoplay_videoDocument.js]
 [browser_autoscroll_disabled.js]
 skip-if = true # Bug 1312652
 [browser_block_autoplay_media.js]
 tags = audiochannel
 [browser_block_autoplay_media_pausedAfterPlay.js]
 tags = audiochannel
 [browser_block_autoplay_playAfterTabVisible.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_autoplay_policy_web_audio.js
@@ -0,0 +1,201 @@
+/**
+ * This test is used for testing whether WebAudio can be started correctly in
+ * different scenarios, such as
+ * 1) site has existing 'autoplay-media' permission for allowing autoplay
+ * 2) site has existing 'autoplay-media' permission for blocking autoplay
+ * 3) site doesn't have permission, user clicks 'allow' button on the doorhanger
+ * 4) site doesn't have permission, user clicks 'deny' button on the doorhanger
+ * 5) site doesn't have permission, user ignores the doorhanger
+ */
+"use strict";
+
+ChromeUtils.import("resource:///modules/SitePermissions.jsm", this);
+const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_empty.html";
+
+function setup_test_preference() {
+  return SpecialPowers.pushPrefEnv({"set": [
+    ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.PROMPT],
+    ["media.autoplay.enabled.user-gestures-needed", true],
+    ["media.autoplay.ask-permission", true],
+    ["media.autoplay.block-webaudio", true],
+    ["media.autoplay.block-event.enabled", true],
+  ]});
+}
+
+function createAudioContext() {
+  content.ac = new content.AudioContext();
+  const ac = content.ac;
+
+  ac.allowedToStart = new Promise(resolve => {
+    ac.addEventListener("statechange", function() {
+      if (ac.state === "running") {
+        resolve();
+      }
+    }, {once: true});
+  });
+
+  ac.notAllowedToStart = new Promise(resolve => {
+    ac.addEventListener("blocked", function() {
+      resolve();
+    }, {once: true});
+  });
+}
+
+async function checkIfAudioContextIsAllowedToStart(isAllowedToStart) {
+  const ac = content.ac;
+  if (isAllowedToStart) {
+    await ac.allowedToStart;
+    ok(true, `AudioContext is running.`);
+  } else {
+    await ac.notAllowedToStart;
+    ok(true, `AudioContext is not started yet.`);
+  }
+}
+
+async function resumeAudioContext(isAllowedToStart) {
+  const ac = content.ac;
+  const resumePromise = ac.resume();
+  const blockedPromise = new Promise(resolve => {
+    ac.addEventListener("blocked", function() {
+      resolve();
+    }, {once: true});
+  });
+
+  if (isAllowedToStart) {
+    await resumePromise;
+    ok(ac.state === "running", `AudioContext is running.`);
+  } else {
+    await blockedPromise;
+    ok(ac.state === "suspended", `AudioContext is suspended.`);
+  }
+}
+
+function checkAudioContextState(state) {
+  ok(content.ac.state === state,
+     `AudioContext state is ${content.ac.state}, expected state is ${state}`);
+}
+
+function connectAudibleNodeToContext() {
+  info(`- connect audible node to context graph -`);
+  const ac = content.ac;
+  const dest = ac.destination;
+  const osc = ac.createOscillator();
+  osc.connect(dest);
+  osc.start();
+}
+
+async function testAutoplayExistingPermission(args) {
+  info(`- starting \"${args.name}\" -`);
+  const tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, PAGE);
+  const browser = tab.linkedBrowser;
+
+  info(`- set the permission -`);
+  const promptShow = () =>
+    PopupNotifications.getNotification("autoplay-media", browser);
+  SitePermissions.set(browser.currentURI, "autoplay-media", args.permission);
+  ok(!promptShow(), `should not be showing permission prompt yet`);
+
+  info(`- create audio context -`);
+  // We want the same audio context to be used across different content
+  // tasks, so it needs to be loaded by a frame script.
+  const mm = tab.linkedBrowser.messageManager;
+  mm.loadFrameScript("data:,(" + createAudioContext.toString() + ")();", false);
+
+  info(`- check AudioContext status -`);
+  const isAllowedToStart = args.permission === SitePermissions.ALLOW;
+  await ContentTask.spawn(browser, isAllowedToStart,
+                          checkIfAudioContextIsAllowedToStart);
+  await ContentTask.spawn(browser, isAllowedToStart,
+                          resumeAudioContext);
+
+  info(`- remove tab -`);
+  SitePermissions.remove(browser.currentURI, "autoplay-media");
+  await BrowserTestUtils.removeTab(tab);
+}
+
+async function testAutoplayUnknownPermission(args) {
+  info(`- starting \"${args.name}\" -`);
+  const tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, PAGE);
+  const browser = tab.linkedBrowser;
+
+  info(`- set the 'autoplay-media' permission -`);
+  const promptShow = () =>
+    PopupNotifications.getNotification("autoplay-media", browser);
+  SitePermissions.set(browser.currentURI, "autoplay-media", SitePermissions.UNKNOWN);
+  ok(!promptShow(), `should not be showing permission prompt yet`);
+
+  info(`- create audio context -`);
+  const popupShow = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+  // We want the same audio context to be used across different content
+  // tasks, so it needs to be loaded by a frame script.
+  const mm = tab.linkedBrowser.messageManager;
+  mm.loadFrameScript("data:,(" + createAudioContext.toString() + ")();", false);
+  await popupShow;
+  ok(promptShow(), `should now be showing permission prompt`);
+
+  info(`- AudioContext should not be started before user responds to doorhanger -`);
+  await ContentTask.spawn(browser, "suspended",
+                          checkAudioContextState);
+
+  if (args.ignoreDoorhanger) {
+    const popupHide = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+    await ContentTask.spawn(browser, null, () => {
+      info(`- user ingores the doorhanger and interacts with page directly -`);
+      content.document.notifyUserGestureActivation();
+    });
+
+    await ContentTask.spawn(browser, true,
+                            resumeAudioContext);
+    ok(promptShow(), `doorhanger would only be dismissed when audible media starts`);
+    await ContentTask.spawn(browser, null,
+                            connectAudibleNodeToContext);
+    await popupHide;
+    ok(true, `doorhanger should dismiss after AudioContext starts audible`);
+  } else {
+    info(`- simulate clicking button on doorhanger-`);
+    if (args.button == "allow") {
+      PopupNotifications.panel.firstElementChild.button.click();
+    } else if (args.button == "block") {
+      PopupNotifications.panel.firstChild.secondaryButton.click();
+    } else {
+      ok(false, `Invalid button field`);
+    }
+
+    info(`- check AudioContext status -`);
+    const isAllowedToStart = args.button === "allow";
+    await ContentTask.spawn(browser, isAllowedToStart,
+                            checkIfAudioContextIsAllowedToStart);
+    await ContentTask.spawn(browser, isAllowedToStart,
+                            resumeAudioContext);
+  }
+
+  info(`- remove tab -`);
+  SitePermissions.remove(browser.currentURI, "autoplay-media");
+  await BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function start_test() {
+  info("- setup test preference -");
+  await setup_test_preference();
+
+  await testAutoplayExistingPermission({
+    name: "Prexisting allow permission",
+    permission: SitePermissions.ALLOW,
+  });
+  await testAutoplayExistingPermission({
+    name: "Prexisting block permission",
+    permission: SitePermissions.BLOCK,
+  });
+  await testAutoplayUnknownPermission({
+    name: "Unknown permission and click allow button on doorhanger",
+    button: "allow",
+  });
+  await testAutoplayUnknownPermission({
+    name: "Unknown permission and click block button on doorhanger",
+    button: "block",
+  });
+  await testAutoplayUnknownPermission({
+    name: "Unknown permission and ignore doorhanger",
+    ignoreDoorhanger: true,
+  });
+});
--- a/toolkit/content/widgets.css
+++ b/toolkit/content/widgets.css
@@ -5,16 +5,17 @@
 /* ===== widgets.css =====================================================
    == Styles ported from XBL <resources>, loaded by "global.css".
    ======================================================================= */
 
 @import url("chrome://global/content/autocomplete.css");
 @import url("chrome://global/skin/autocomplete.css");
 @import url("chrome://global/skin/dialog.css");
 @import url("chrome://global/skin/dropmarker.css");
+@import url("chrome://global/skin/findBar.css");
 @import url("chrome://global/skin/groupbox.css");
 @import url("chrome://global/skin/menu.css");
 @import url("chrome://global/skin/menulist.css");
 @import url("chrome://global/skin/notification.css");
 @import url("chrome://global/skin/popup.css");
 @import url("chrome://global/skin/progressmeter.css");
 @import url("chrome://global/skin/richlistbox.css");
 @import url("chrome://global/skin/splitter.css");
rename from toolkit/content/widgets/findbar.xml
rename to toolkit/content/widgets/findbar.js
--- a/toolkit/content/widgets/findbar.xml
+++ b/toolkit/content/widgets/findbar.js
@@ -1,1381 +1,1211 @@
-<?xml version="1.0"?>
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Wrap to prevent accidentally leaking to window scope:
+{
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-<!-- 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/. -->
+class MozFindbar extends XULElement {
+  constructor() {
+    super();
+    MozXULElement.insertFTLIfNeeded("toolkit/main-window/findbar.ftl");
+    this.destroy = this.destroy.bind(this);
+
+    // We have to guard against `this.close` being |null| due to an unknown
+    // issue, which is tracked in bug 957999.
+    this.addEventListener("keypress", (event) => {
+      if (event.keyCode == event.DOM_VK_ESCAPE) {
+        if (this.close)
+          this.close();
+        event.preventDefault();
+      }
+    }, true);
 
-<!DOCTYPE bindings [
-<!ENTITY % findBarDTD SYSTEM "chrome://global/locale/findbar.dtd" >
-%findBarDTD;
-]>
+    this.content = MozXULElement.parseXULToFragment(`
+      <hbox anonid="findbar-container" class="findbar-container" flex="1" align="center">
+        <hbox anonid="findbar-textbox-wrapper" align="stretch">
+          <textbox anonid="findbar-textbox" class="findbar-textbox findbar-find-fast" />
+          <toolbarbutton anonid="find-previous" class="findbar-find-previous tabbable" data-l10n-attrs="tooltiptext" data-l10n-id="findbar-previous" oncommand="onFindAgainCommand(true);" disabled="true" />
+          <toolbarbutton anonid="find-next" class="findbar-find-next tabbable" data-l10n-id="findbar-next" oncommand="onFindAgainCommand(false);" disabled="true" />
+        </hbox>
+        <toolbarbutton anonid="highlight" class="findbar-highlight findbar-button tabbable" data-l10n-id="findbar-highlight-all" oncommand="toggleHighlight(this.checked);" type="checkbox" />
+        <toolbarbutton anonid="find-case-sensitive" class="findbar-case-sensitive findbar-button tabbable" data-l10n-id="findbar-case-sensitive" oncommand="_setCaseSensitivity(this.checked ? 1 : 0);" type="checkbox" />
+        <toolbarbutton anonid="find-entire-word" class="findbar-entire-word findbar-button tabbable" data-l10n-id="findbar-entire-word" oncommand="toggleEntireWord(this.checked);" type="checkbox" />
+        <label anonid="match-case-status" class="findbar-find-fast" />
+        <label anonid="entire-word-status" class="findbar-find-fast" />
+        <label anonid="found-matches" class="findbar-find-fast found-matches" hidden="true" />
+        <image anonid="find-status-icon" class="findbar-find-fast find-status-icon" />
+        <description anonid="find-status" control="findbar-textbox" class="findbar-find-fast findbar-find-status" />
+      </hbox>
+      <toolbarbutton anonid="find-closebutton" class="findbar-closebutton close-icon" data-l10n-id="findbar-find-button-close" oncommand="close();" />
+    `);
+  }
+
+  connectedCallback() {
+    this.appendChild(document.importNode(this.content, true));
+
+    this.hidden = true;
+
+    /**
+     * Please keep in sync with toolkit/modules/FindBarChild.jsm
+     */
+    this.FIND_NORMAL = 0;
 
-<bindings id="findbarBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-   xmlns:xbl="http://www.mozilla.org/xbl">
+    this.FIND_TYPEAHEAD = 1;
+
+    this.FIND_LINKS = 2;
+
+    this.__findMode = 0;
+
+    this._flashFindBar = 0;
+
+    this._initialFlashFindBarCount = 6;
+
+    /**
+     * - For tests that need to know when the find bar is finished
+     * - initializing, we store a promise to notify on.
+     */
+    this._startFindDeferred = null;
+
+    this._browser = null;
+
+    this.__prefsvc = null;
+
+    this._observer = {
+      _self: this,
+
+      QueryInterface: ChromeUtils.generateQI(["nsIObserver",
+                                              "nsISupportsWeakReference"]),
+
+      observe(aSubject, aTopic, aPrefName) {
+        if (aTopic != "nsPref:changed")
+          return;
+
+        let prefsvc = this._self._prefsvc;
 
-  <!-- Private binding -->
-  <binding id="findbar-textbox"
-           extends="chrome://global/content/bindings/textbox.xml#textbox">
-    <implementation>
+        switch (aPrefName) {
+          case "accessibility.typeaheadfind":
+            this._self._findAsYouType = prefsvc.getBoolPref(aPrefName);
+              break;
+          case "accessibility.typeaheadfind.manual":
+            this._self._manualFAYT = prefsvc.getBoolPref(aPrefName);
+            break;
+          case "accessibility.typeaheadfind.timeout":
+            this._self.quickFindTimeoutLength = prefsvc.getIntPref(aPrefName);
+            break;
+          case "accessibility.typeaheadfind.linksonly":
+            this._self._typeAheadLinksOnly = prefsvc.getBoolPref(aPrefName);
+            break;
+          case "accessibility.typeaheadfind.casesensitive":
+            this._self._setCaseSensitivity(prefsvc.getIntPref(aPrefName));
+            break;
+          case "findbar.entireword":
+            this._self._entireWord = prefsvc.getBoolPref(aPrefName);
+            this._self.toggleEntireWord(this._self._entireWord, true);
+            break;
+          case "findbar.highlightAll":
+            this._self.toggleHighlight(prefsvc.getBoolPref(aPrefName), true);
+            break;
+          case "findbar.modalHighlight":
+            this._self._useModalHighlight = prefsvc.getBoolPref(aPrefName);
+            if (this._self.browser.finder)
+              this._self.browser.finder.onModalHighlightChange(this._self._useModalHighlight);
+            break;
+        }
+      },
+    };
+
+    this._destroyed = false;
+
+    this._pluralForm = null;
+
+    this._strBundle = null;
+
+    this._xulBrowserWindow = null;
+
+    // These elements are accessed frequently and are therefore cached
+    this._findField = this.getElement("findbar-textbox");
+    this._foundMatches = this.getElement("found-matches");
+    this._findStatusIcon = this.getElement("find-status-icon");
+    this._findStatusDesc = this.getElement("find-status");
+
+    this._foundURL = null;
+
+    let prefsvc = this._prefsvc;
+
+    this.quickFindTimeoutLength =
+      prefsvc.getIntPref("accessibility.typeaheadfind.timeout");
+    this._flashFindBar =
+      prefsvc.getIntPref("accessibility.typeaheadfind.flashBar");
+    this._useModalHighlight = prefsvc.getBoolPref("findbar.modalHighlight");
 
-      <field name="_findbar">null</field>
-      <property name="findbar" readonly="true">
-        <getter>
-          return this._findbar ?
-                 this._findbar : this._findbar = document.getBindingParent(this);
-        </getter>
-      </property>
+    prefsvc.addObserver("accessibility.typeaheadfind",
+                            this._observer);
+    prefsvc.addObserver("accessibility.typeaheadfind.manual",
+                        this._observer);
+    prefsvc.addObserver("accessibility.typeaheadfind.linksonly",
+                        this._observer);
+    prefsvc.addObserver("accessibility.typeaheadfind.casesensitive",
+                        this._observer);
+    prefsvc.addObserver("findbar.entireword", this._observer);
+    prefsvc.addObserver("findbar.highlightAll", this._observer);
+    prefsvc.addObserver("findbar.modalHighlight", this._observer);
+
+    this._findAsYouType =
+      prefsvc.getBoolPref("accessibility.typeaheadfind");
+    this._manualFAYT =
+      prefsvc.getBoolPref("accessibility.typeaheadfind.manual");
+    this._typeAheadLinksOnly =
+      prefsvc.getBoolPref("accessibility.typeaheadfind.linksonly");
+    this._typeAheadCaseSensitive =
+      prefsvc.getIntPref("accessibility.typeaheadfind.casesensitive");
+    this._entireWord = prefsvc.getBoolPref("findbar.entireword");
+    this._highlightAll = prefsvc.getBoolPref("findbar.highlightAll");
+
+    // Convenience
+    this.nsITypeAheadFind = Ci.nsITypeAheadFind;
+    this.nsISelectionController = Ci.nsISelectionController;
+    this._findSelection = this.nsISelectionController.SELECTION_FIND;
+
+    this._findResetTimeout = -1;
 
-      <method name="_handleEnter">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          if (this.findbar._findMode == this.findbar.FIND_NORMAL) {
-            let findString = this.findbar._findField;
+    // Make sure the FAYT keypress listener is attached by initializing the
+    // browser property
+    if (this.getAttribute("browserid")) {
+      // eslint-disable-next-line no-self-assign
+      setTimeout(function(aSelf) { aSelf.browser = aSelf.browser; }, 0, this);
+    }
+
+    window.addEventListener("unload", this.destroy);
+
+    this._findField.addEventListener("input", (event) => {
+      // We should do nothing during composition.  E.g., composing string
+      // before converting may matches a forward word of expected word.
+      // After that, even if user converts the composition string to the
+      // expected word, it may find second or later searching word in the
+      // document.
+      if (this._isIMEComposing) {
+        return;
+      }
+
+      const value = this._findField.value;
+      if (this._hadValue && !value) {
+        this._willfullyDeleted = true;
+        this._hadValue = false;
+      } else if (value.trim()) {
+        this._hadValue = true;
+        this._willfullyDeleted = false;
+      }
+      this._find(value);
+    });
+
+    this._findField.addEventListener("keypress", (event) => {
+      switch (event.keyCode) {
+        case KeyEvent.DOM_VK_RETURN:
+          if (this._findMode == this.FIND_NORMAL) {
+            let findString = this._findField;
             if (!findString.value)
               return;
-            if (aEvent.getModifierState("Accel")) {
-              this.findbar.getElement("highlight").click();
+            if (event.getModifierState("Accel")) {
+              this.getElement("highlight").click();
               return;
             }
 
-            this.findbar.onFindAgainCommand(aEvent.shiftKey);
+            this.onFindAgainCommand(event.shiftKey);
           } else {
-            this.findbar._finishFAYT(aEvent);
+            this._finishFAYT(event);
           }
-        ]]></body>
-      </method>
+          break;
+        case KeyEvent.DOM_VK_TAB:
+          let shouldHandle = !event.altKey && !event.ctrlKey &&
+            !event.metaKey;
+          if (shouldHandle &&
+            this._findMode != this.FIND_NORMAL) {
 
-      <method name="_handleTab">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          let shouldHandle = !aEvent.altKey && !aEvent.ctrlKey &&
-                             !aEvent.metaKey;
-          if (shouldHandle &&
-              this.findbar._findMode != this.findbar.FIND_NORMAL) {
-
-            this.findbar._finishFAYT(aEvent);
+            this._finishFAYT(event);
           }
-        ]]></body>
-      </method>
-    </implementation>
+          break;
+        case KeyEvent.DOM_VK_PAGE_UP:
+        case KeyEvent.DOM_VK_PAGE_DOWN:
+          if (!event.altKey && !event.ctrlKey &&
+              !event.metaKey && !event.shiftKey) {
+            this.browser.finder.keyPress(event);
+            event.preventDefault();
+          }
+          break;
+        case KeyEvent.DOM_VK_UP:
+        case KeyEvent.DOM_VK_DOWN:
+          this.browser.finder.keyPress(event);
+          event.preventDefault();
+          break;
+      }
+    });
 
-    <handlers>
-      <handler event="input"><![CDATA[
-        // We should do nothing during composition.  E.g., composing string
-        // before converting may matches a forward word of expected word.
-        // After that, even if user converts the composition string to the
-        // expected word, it may find second or later searching word in the
-        // document.
-        if (this.findbar._isIMEComposing) {
-          return;
-        }
+    this._findField.addEventListener("blur", (event) => {
+      // Note: This code used to remove the selection
+      // if it matched an editable.
+      this.browser.finder.enableSelection();
+    });
+
+    this._findField.addEventListener("focus", (event) => {
+      if (/Mac/.test(navigator.platform)) {
+        this._onFindFieldFocus();
+      }
+      this._updateBrowserWithState();
+    });
 
-        if (this._hadValue && !this.value) {
-          this._willfullyDeleted = true;
-          this._hadValue = false;
-        } else if (this.value.trim()) {
-          this._hadValue = true;
-          this._willfullyDeleted = false;
-        }
-        this.findbar._find(this.value);
-      ]]></handler>
+    this._findField.addEventListener("compositionstart", (event) => {
+      // Don't close the find toolbar while IME is composing.
+      let findbar = this;
+      findbar._isIMEComposing = true;
+      if (findbar._quickFindTimeout) {
+        clearTimeout(findbar._quickFindTimeout);
+        findbar._quickFindTimeout = null;
+        findbar._updateBrowserWithState();
+      }
+    });
 
-      <handler event="keypress"><![CDATA[
-        let shouldHandle = !event.altKey && !event.ctrlKey &&
-                           !event.metaKey && !event.shiftKey;
+    this._findField.addEventListener("compositionend", (event) => {
+      this._isIMEComposing = false;
+      if (this._findMode != this.FIND_NORMAL)
+        this._setFindCloseTimeout();
+    });
 
-        switch (event.keyCode) {
-          case KeyEvent.DOM_VK_RETURN:
-            this._handleEnter(event);
-            break;
-          case KeyEvent.DOM_VK_TAB:
-            this._handleTab(event);
-            break;
-          case KeyEvent.DOM_VK_PAGE_UP:
-          case KeyEvent.DOM_VK_PAGE_DOWN:
-            if (shouldHandle) {
-              this.findbar.browser.finder.keyPress(event);
-              event.preventDefault();
-            }
-            break;
-          case KeyEvent.DOM_VK_UP:
-          case KeyEvent.DOM_VK_DOWN:
-            this.findbar.browser.finder.keyPress(event);
-            event.preventDefault();
-            break;
-        }
-      ]]></handler>
+    this._findField.addEventListener("dragover", (event) => {
+      if (event.dataTransfer.types.includes("text/plain"))
+        event.preventDefault();
+    });
+
+    this._findField.addEventListener("drop", (event) => {
+      let value = event.dataTransfer.getData("text/plain");
+      this._findField.value = value;
+      this._find(value);
+      event.stopPropagation();
+      event.preventDefault();
+    });
+  }
 
-      <handler event="blur"><![CDATA[
-        let findbar = this.findbar;
-        // Note: This code used to remove the selection
-        // if it matched an editable.
-        findbar.browser.finder.enableSelection();
-      ]]></handler>
+  set _findMode(val) {
+    this.__findMode = val;
+    this._updateBrowserWithState();
+    return val;
+  }
 
-      <handler event="focus"><![CDATA[
-        let findbar = this.findbar;
-        if (/Mac/.test(navigator.platform)) {
-          findbar._onFindFieldFocus();
-        }
-        findbar._updateBrowserWithState();
-      ]]></handler>
+  get _findMode() {
+    return this.__findMode;
+  }
+
+  set prefillWithSelection(val) {
+    this.setAttribute("prefillwithselection", val);
+    return val;
+  }
 
-      <handler event="compositionstart"><![CDATA[
-        // Don't close the find toolbar while IME is composing.
-        let findbar = this.findbar;
-        findbar._isIMEComposing = true;
-        if (findbar._quickFindTimeout) {
-          clearTimeout(findbar._quickFindTimeout);
-          findbar._quickFindTimeout = null;
-          findbar._updateBrowserWithState();
-        }
-      ]]></handler>
+  get prefillWithSelection() {
+    return this.getAttribute("prefillwithselection") != "false";
+  }
 
-      <handler event="compositionend"><![CDATA[
-        let findbar = this.findbar;
-        findbar._isIMEComposing = false;
-        if (findbar._findMode != findbar.FIND_NORMAL)
-          findbar._setFindCloseTimeout();
-      ]]></handler>
+  get findMode() {
+    return this._findMode;
+  }
+
+  get hasTransactions() {
+    if (this._findField.value)
+      return true;
 
-      <handler event="dragover"><![CDATA[
-        if (event.dataTransfer.types.includes("text/plain"))
-          event.preventDefault();
-      ]]></handler>
+    // Watch out for lazy editor init
+    if (this._findField.editor) {
+      let tm = this._findField.editor.transactionManager;
+      return !!(tm.numberOfUndoItems || tm.numberOfRedoItems);
+    }
+    return false;
+  }
 
-      <handler event="drop"><![CDATA[
-        let value = event.dataTransfer.getData("text/plain");
-        this.value = value;
-        this.findbar._find(value);
-        event.stopPropagation();
-        event.preventDefault();
-      ]]></handler>
-    </handlers>
-  </binding>
-
-  <binding id="findbar">
-    <resources>
-      <stylesheet src="chrome://global/skin/findBar.css"/>
-    </resources>
+  set browser(val) {
+    if (this._browser) {
+      if (this._browser.messageManager) {
+        this._browser.messageManager.removeMessageListener("Findbar:Keypress", this);
+        this._browser.messageManager.removeMessageListener("Findbar:Mouseup", this);
+      }
+      let finder = this._browser.finder;
+      if (finder)
+        finder.removeResultListener(this);
+    }
 
-    <content hidden="true">
-    <xul:hbox anonid="findbar-container" class="findbar-container" flex="1" align="center">
-      <xul:hbox anonid="findbar-textbox-wrapper" align="stretch">
-        <xul:textbox anonid="findbar-textbox"
-                     class="findbar-textbox findbar-find-fast"
-                     xbl:inherits="flash"/>
-        <xul:toolbarbutton anonid="find-previous"
-                           class="findbar-find-previous tabbable"
-                           tooltiptext="&previous.tooltip;"
-                           oncommand="onFindAgainCommand(true);"
-                           disabled="true"
-                           xbl:inherits="accesskey=findpreviousaccesskey"/>
-        <xul:toolbarbutton anonid="find-next"
-                           class="findbar-find-next tabbable"
-                           tooltiptext="&next.tooltip;"
-                           oncommand="onFindAgainCommand(false);"
-                           disabled="true"
-                           xbl:inherits="accesskey=findnextaccesskey"/>
-      </xul:hbox>
-      <xul:toolbarbutton anonid="highlight"
-                         class="findbar-highlight findbar-button tabbable"
-                         label="&highlightAll.label;"
-                         accesskey="&highlightAll.accesskey;"
-                         tooltiptext="&highlightAll.tooltiptext;"
-                         oncommand="toggleHighlight(this.checked);"
-                         type="checkbox"
-                         xbl:inherits="accesskey=highlightaccesskey"/>
-      <xul:toolbarbutton anonid="find-case-sensitive"
-                         class="findbar-case-sensitive findbar-button tabbable"
-                         label="&caseSensitive.label;"
-                         accesskey="&caseSensitive.accesskey;"
-                         tooltiptext="&caseSensitive.tooltiptext;"
-                         oncommand="_setCaseSensitivity(this.checked ? 1 : 0);"
-                         type="checkbox"
-                         xbl:inherits="accesskey=matchcaseaccesskey"/>
-      <xul:toolbarbutton anonid="find-entire-word"
-                         class="findbar-entire-word findbar-button tabbable"
-                         label="&entireWord.label;"
-                         accesskey="&entireWord.accesskey;"
-                         tooltiptext="&entireWord.tooltiptext;"
-                         oncommand="toggleEntireWord(this.checked);"
-                         type="checkbox"
-                         xbl:inherits="accesskey=entirewordaccesskey"/>
-      <xul:label anonid="match-case-status" class="findbar-find-fast"/>
-      <xul:label anonid="entire-word-status" class="findbar-find-fast"/>
-      <xul:label anonid="found-matches" class="findbar-find-fast found-matches" hidden="true"/>
-      <xul:image anonid="find-status-icon" class="findbar-find-fast find-status-icon"/>
-      <xul:description anonid="find-status"
-                       control="findbar-textbox"
-                       class="findbar-find-fast findbar-find-status">
-      <!-- Do not use value, first child is used because it provides a11y with text change events -->
-      </xul:description>
-    </xul:hbox>
-    <xul:toolbarbutton anonid="find-closebutton"
-                       class="findbar-closebutton close-icon"
-                       tooltiptext="&findCloseButton.tooltip;"
-                       oncommand="close();"/>
-    </content>
+    this._browser = val;
+    if (this._browser) {
+      // Need to do this to ensure the correct initial state.
+      this._updateBrowserWithState();
+      this._browser.messageManager.addMessageListener("Findbar:Keypress", this);
+      this._browser.messageManager.addMessageListener("Findbar:Mouseup", this);
+      this._browser.finder.addResultListener(this);
+
+      this._findField.value = this._browser._lastSearchString;
+    }
+    return val;
+  }
+
+  get browser() {
+    if (!this._browser) {
+      this._browser =
+        document.getElementById(this.getAttribute("browserid"));
+    }
+    return this._browser;
+  }
+
+  get _prefsvc() {
+    return Services.prefs;
+  }
 
-    <implementation>
-      <!-- Please keep in sync with toolkit/modules/FindBarChild.jsm -->
-      <field name="FIND_NORMAL">0</field>
-      <field name="FIND_TYPEAHEAD">1</field>
-      <field name="FIND_LINKS">2</field>
+  get pluralForm() {
+    if (!this._pluralForm) {
+      this._pluralForm = ChromeUtils.import(
+        "resource://gre/modules/PluralForm.jsm", {}).PluralForm;
+    }
+    return this._pluralForm;
+  }
 
-      <field name="__findMode">0</field>
-      <property name="_findMode" onget="return this.__findMode;"
-                onset="this.__findMode = val; this._updateBrowserWithState(); return val;"/>
-
-      <field name="_flashFindBar">0</field>
-      <field name="_initialFlashFindBarCount">6</field>
-
-      <!--
-        - For tests that need to know when the find bar is finished
-        - initializing, we store a promise to notify on.
-        -->
-      <field name="_startFindDeferred">null</field>
+  get strBundle() {
+    if (!this._strBundle) {
+      this._strBundle = Services.strings.createBundle(
+        "chrome://global/locale/findbar.properties");
+    }
+    return this._strBundle;
+  }
 
-      <property name="prefillWithSelection"
-                onget="return this.getAttribute('prefillwithselection') != 'false'"
-                onset="this.setAttribute('prefillwithselection', val); return val;"/>
+  getElement(aAnonymousID) {
+    return this.querySelector(`[anonid=${aAnonymousID}]`);
+  }
 
-      <method name="getElement">
-        <parameter name="aAnonymousID"/>
-        <body><![CDATA[
-          return document.getAnonymousElementByAttribute(this,
-                                                         "anonid",
-                                                         aAnonymousID);
-        ]]></body>
-      </method>
+  /**
+   * This is necessary because the destructor isn't called when
+   * we are removed from a document that is not destroyed. This
+   * needs to be explicitly called in this case
+   */
+  destroy() {
+    if (this._destroyed)
+      return;
+    window.removeEventListener("unload", this.destroy);
+    this._destroyed = true;
 
-      <property name="findMode"
-                readonly="true"
-                onget="return this._findMode;"/>
+    if (this.browser && this.browser.finder)
+      this.browser.finder.destroy();
 
-      <property name="hasTransactions" readonly="true">
-        <getter><![CDATA[
-          if (this._findField.value)
-            return true;
+    this.browser = null;
 
-          // Watch out for lazy editor init
-          if (this._findField.editor) {
-            let tm = this._findField.editor.transactionManager;
-            return !!(tm.numberOfUndoItems || tm.numberOfRedoItems);
-          }
-          return false;
-        ]]></getter>
-      </property>
+    let prefsvc = this._prefsvc;
+    prefsvc.removeObserver("accessibility.typeaheadfind",
+                            this._observer);
+    prefsvc.removeObserver("accessibility.typeaheadfind.manual",
+                            this._observer);
+    prefsvc.removeObserver("accessibility.typeaheadfind.linksonly",
+                            this._observer);
+    prefsvc.removeObserver("accessibility.typeaheadfind.casesensitive",
+                            this._observer);
+    prefsvc.removeObserver("findbar.entireword", this._observer);
+    prefsvc.removeObserver("findbar.highlightAll", this._observer);
+    prefsvc.removeObserver("findbar.modalHighlight", this._observer);
 
-      <field name="_browser">null</field>
-      <property name="browser">
-        <getter><![CDATA[
-          if (!this._browser) {
-            this._browser =
-              document.getElementById(this.getAttribute("browserid"));
-          }
-          return this._browser;
-        ]]></getter>
-        <setter><![CDATA[
-          if (this._browser) {
-            if (this._browser.messageManager) {
-              this._browser.messageManager.removeMessageListener("Findbar:Keypress", this);
-              this._browser.messageManager.removeMessageListener("Findbar:Mouseup", this);
-            }
-            let finder = this._browser.finder;
-            if (finder)
-              finder.removeResultListener(this);
-          }
+    // Clear all timers that might still be running.
+    this._cancelTimers();
+  }
 
-          this._browser = val;
-          if (this._browser) {
-            // Need to do this to ensure the correct initial state.
-            this._updateBrowserWithState();
-            this._browser.messageManager.addMessageListener("Findbar:Keypress", this);
-            this._browser.messageManager.addMessageListener("Findbar:Mouseup", this);
-            this._browser.finder.addResultListener(this);
-
-            this._findField.value = this._browser._lastSearchString;
-          }
-          return val;
-        ]]></setter>
-      </property>
-
-      <field name="__prefsvc">null</field>
-      <property name="_prefsvc">
-        <getter><![CDATA[
-          if (!this.__prefsvc) {
-            this.__prefsvc = Cc["@mozilla.org/preferences-service;1"]
-              .getService(Ci.nsIPrefBranch);
-          }
-          return this.__prefsvc;
-        ]]></getter>
-      </property>
+  _cancelTimers() {
+    if (this._flashFindBarTimeout) {
+      clearInterval(this._flashFindBarTimeout);
+      this._flashFindBarTimeout = null;
+    }
+    if (this._quickFindTimeout) {
+      clearTimeout(this._quickFindTimeout);
+      this._quickFindTimeout = null;
+    }
+    if (this._findResetTimeout) {
+      clearTimeout(this._findResetTimeout);
+      this._findResetTimeout = null;
+    }
+  }
 
-      <field name="_observer"><![CDATA[({
-        _self: this,
-
-        QueryInterface: ChromeUtils.generateQI(["nsIObserver",
-                                                "nsISupportsWeakReference"]),
+  _setFindCloseTimeout() {
+    if (this._quickFindTimeout)
+      clearTimeout(this._quickFindTimeout);
 
-        observe(aSubject, aTopic, aPrefName) {
-          if (aTopic != "nsPref:changed")
-            return;
-
-          let prefsvc = this._self._prefsvc;
+    // Don't close the find toolbar while IME is composing OR when the
+    // findbar is already hidden.
+    if (this._isIMEComposing || this.hidden) {
+      this._quickFindTimeout = null;
+      this._updateBrowserWithState();
+      return;
+    }
 
-          switch (aPrefName) {
-            case "accessibility.typeaheadfind":
-              this._self._findAsYouType = prefsvc.getBoolPref(aPrefName);
-              break;
-            case "accessibility.typeaheadfind.manual":
-              this._self._manualFAYT = prefsvc.getBoolPref(aPrefName);
-              break;
-            case "accessibility.typeaheadfind.timeout":
-              this._self.quickFindTimeoutLength = prefsvc.getIntPref(aPrefName);
-              break;
-            case "accessibility.typeaheadfind.linksonly":
-              this._self._typeAheadLinksOnly = prefsvc.getBoolPref(aPrefName);
-              break;
-            case "accessibility.typeaheadfind.casesensitive":
-              this._self._setCaseSensitivity(prefsvc.getIntPref(aPrefName));
-              break;
-            case "findbar.entireword":
-              this._self._entireWord = prefsvc.getBoolPref(aPrefName);
-              this._self.toggleEntireWord(this._self._entireWord, true);
-              break;
-            case "findbar.highlightAll":
-              this._self.toggleHighlight(prefsvc.getBoolPref(aPrefName), true);
-              break;
-            case "findbar.modalHighlight":
-              this._self._useModalHighlight = prefsvc.getBoolPref(aPrefName);
-              if (this._self.browser.finder)
-                this._self.browser.finder.onModalHighlightChange(this._self._useModalHighlight);
-              break;
-          }
-        },
-      })]]></field>
+    if (this.quickFindTimeoutLength < 1) {
+      this._quickFindTimeout = null;
+    } else {
+      this._quickFindTimeout = setTimeout(() => {
+        if (this._findMode != this.FIND_NORMAL)
+            this.close();
+          this._quickFindTimeout = null;
+      }, this.quickFindTimeoutLength);
+    }
+    this._updateBrowserWithState();
+  }
 
-      <field name="_destroyed">false</field>
-
-      <constructor><![CDATA[
-        // These elements are accessed frequently and are therefore cached
-        this._findField = this.getElement("findbar-textbox");
-        this._foundMatches = this.getElement("found-matches");
-        this._findStatusIcon = this.getElement("find-status-icon");
-        this._findStatusDesc = this.getElement("find-status");
-
-        this._foundURL = null;
-
-        let prefsvc = this._prefsvc;
+  /**
+   * - Updates the search match count after each find operation on a new string.
+   * - @param aRes
+   * -        the result of the find operation
+   */
+  _updateMatchesCount() {
+    if (!this._dispatchFindEvent("matchescount"))
+      return;
 
-        this.quickFindTimeoutLength =
-          prefsvc.getIntPref("accessibility.typeaheadfind.timeout");
-        this._flashFindBar =
-          prefsvc.getIntPref("accessibility.typeaheadfind.flashBar");
-        this._useModalHighlight = prefsvc.getBoolPref("findbar.modalHighlight");
-
-        prefsvc.addObserver("accessibility.typeaheadfind",
-                            this._observer);
-        prefsvc.addObserver("accessibility.typeaheadfind.manual",
-                            this._observer);
-        prefsvc.addObserver("accessibility.typeaheadfind.linksonly",
-                            this._observer);
-        prefsvc.addObserver("accessibility.typeaheadfind.casesensitive",
-                            this._observer);
-        prefsvc.addObserver("findbar.entireword", this._observer);
-        prefsvc.addObserver("findbar.highlightAll", this._observer);
-        prefsvc.addObserver("findbar.modalHighlight", this._observer);
+    this.browser.finder.requestMatchesCount(this._findField.value,
+      this._findMode == this.FIND_LINKS);
+  }
 
-        this._findAsYouType =
-          prefsvc.getBoolPref("accessibility.typeaheadfind");
-        this._manualFAYT =
-          prefsvc.getBoolPref("accessibility.typeaheadfind.manual");
-        this._typeAheadLinksOnly =
-          prefsvc.getBoolPref("accessibility.typeaheadfind.linksonly");
-        this._typeAheadCaseSensitive =
-          prefsvc.getIntPref("accessibility.typeaheadfind.casesensitive");
-        this._entireWord = prefsvc.getBoolPref("findbar.entireword");
-        this._highlightAll = prefsvc.getBoolPref("findbar.highlightAll");
+  /**
+   * - Turns highlight on or off.
+   * - @param aHighlight (boolean)
+   * -        Whether to turn the highlight on or off
+   * - @param aFromPrefObserver (boolean)
+   * -        Whether the callee is the pref observer, which means we should
+   * -        not set the same pref again.
+   */
+  toggleHighlight(aHighlight, aFromPrefObserver) {
+    if (aHighlight === this._highlightAll) {
+      return;
+    }
 
-        // Convenience
-        this.nsITypeAheadFind = Ci.nsITypeAheadFind;
-        this.nsISelectionController = Ci.nsISelectionController;
-        this._findSelection = this.nsISelectionController.SELECTION_FIND;
+    this.browser.finder.onHighlightAllChange(aHighlight);
+
+    this._setHighlightAll(aHighlight, aFromPrefObserver);
 
-        this._findResetTimeout = -1;
+    if (!this._dispatchFindEvent("highlightallchange")) {
+      return;
+    }
 
-        // Make sure the FAYT keypress listener is attached by initializing the
-        // browser property
-        if (this.getAttribute("browserid")) {
-          // eslint-disable-next-line no-self-assign
-          setTimeout(function(aSelf) { aSelf.browser = aSelf.browser; }, 0, this);
-        }
-      ]]></constructor>
+    let word = this._findField.value;
+    // Bug 429723. Don't attempt to highlight ""
+    if (aHighlight && !word)
+      return;
 
-      <destructor><![CDATA[
-        this.destroy();
-      ]]></destructor>
+    this.browser.finder.highlight(aHighlight, word,
+      this._findMode == this.FIND_LINKS);
 
-      <!-- This is necessary because the destructor isn't called when
-           we are removed from a document that is not destroyed. This
-           needs to be explicitly called in this case -->
-      <method name="destroy">
-        <body><![CDATA[
-          if (this._destroyed)
-            return;
-          this._destroyed = true;
+    // Update the matches count
+    this._updateMatchesCount(this.nsITypeAheadFind.FIND_FOUND);
+  }
 
-          if (this.browser && this.browser.finder)
-            this.browser.finder.destroy();
-
-          this.browser = null;
-
-          let prefsvc = this._prefsvc;
-          prefsvc.removeObserver("accessibility.typeaheadfind",
-                                 this._observer);
-          prefsvc.removeObserver("accessibility.typeaheadfind.manual",
-                                 this._observer);
-          prefsvc.removeObserver("accessibility.typeaheadfind.linksonly",
-                                 this._observer);
-          prefsvc.removeObserver("accessibility.typeaheadfind.casesensitive",
-                                 this._observer);
-          prefsvc.removeObserver("findbar.entireword", this._observer);
-          prefsvc.removeObserver("findbar.highlightAll", this._observer);
-          prefsvc.removeObserver("findbar.modalHighlight", this._observer);
-
-          // Clear all timers that might still be running.
-          this._cancelTimers();
-        ]]></body>
-      </method>
+  /**
+   * - Updates the highlight-all mode of the findbar and its UI.
+   * - @param aHighlight (boolean)
+   * -        Whether to turn the highlight on or off.
+   * - @param aFromPrefObserver (boolean)
+   * -        Whether the callee is the pref observer, which means we should
+   * -        not set the same pref again.
+   */
+  _setHighlightAll(aHighlight, aFromPrefObserver) {
+    if (typeof aHighlight != "boolean") {
+      aHighlight = this._highlightAll;
+    }
+    if (aHighlight !== this._highlightAll && !aFromPrefObserver) {
+      this._prefsvc.setBoolPref("findbar.highlightAll", aHighlight);
+    }
+    this._highlightAll = aHighlight;
+    let checkbox = this.getElement("highlight");
+    checkbox.checked = this._highlightAll;
+  }
 
-      <method name="_cancelTimers">
-        <body><![CDATA[
-          if (this._flashFindBarTimeout) {
-            clearInterval(this._flashFindBarTimeout);
-            this._flashFindBarTimeout = null;
-          }
-          if (this._quickFindTimeout) {
-            clearTimeout(this._quickFindTimeout);
-            this._quickFindTimeout = null;
-          }
-          if (this._findResetTimeout) {
-            clearTimeout(this._findResetTimeout);
-            this._findResetTimeout = null;
-          }
-        ]]></body>
-      </method>
+  /**
+   * - Updates the case-sensitivity mode of the findbar and its UI.
+   * - @param [optional] aString
+   * -        The string for which case sensitivity might be turned on.
+   * -        This only used when case-sensitivity is in auto mode,
+   * -        @see _shouldBeCaseSensitive. The default value for this
+   * -        parameter is the find-field value.
+   */
+  _updateCaseSensitivity(aString) {
+    let val = aString || this._findField.value;
 
-      <method name="_setFindCloseTimeout">
-        <body><![CDATA[
-          if (this._quickFindTimeout)
-            clearTimeout(this._quickFindTimeout);
+    let caseSensitive = this._shouldBeCaseSensitive(val);
+    let checkbox = this.getElement("find-case-sensitive");
+    let statusLabel = this.getElement("match-case-status");
+    checkbox.checked = caseSensitive;
+
+    statusLabel.value = caseSensitive ? this._caseSensitiveStr : "";
 
-          // Don't close the find toolbar while IME is composing OR when the
-          // findbar is already hidden.
-          if (this._isIMEComposing || this.hidden) {
-            this._quickFindTimeout = null;
-            this._updateBrowserWithState();
-            return;
-          }
+    // Show the checkbox on the full Find bar in non-auto mode.
+    // Show the label in all other cases.
+    let hideCheckbox = this._findMode != this.FIND_NORMAL ||
+      (this._typeAheadCaseSensitive != 0 &&
+        this._typeAheadCaseSensitive != 1);
+    checkbox.hidden = hideCheckbox;
+    statusLabel.hidden = !hideCheckbox;
+
+    this.browser.finder.caseSensitive = caseSensitive;
+  }
 
-          if (this.quickFindTimeoutLength < 1) {
-            this._quickFindTimeout = null;
-          } else {
-            this._quickFindTimeout = setTimeout(() => {
-              if (this._findMode != this.FIND_NORMAL)
-                  this.close();
-               this._quickFindTimeout = null;
-            }, this.quickFindTimeoutLength);
-          }
-          this._updateBrowserWithState();
-        ]]></body>
-      </method>
+  /**
+   * - Sets the findbar case-sensitivity mode
+   * - @param aCaseSensitivity (int)
+   * -   0 - case insensitive
+   * -   1 - case sensitive
+   * -   2 - auto = case sensitive iff match string contains upper case letters
+   * -   @see _shouldBeCaseSensitive
+   */
+  _setCaseSensitivity(aCaseSensitivity) {
+    this._typeAheadCaseSensitive = aCaseSensitivity;
+    this._updateCaseSensitivity();
+    this._findFailedString = null;
+    this._find();
 
-      <field name="_pluralForm">null</field>
-      <property name="pluralForm">
-        <getter><![CDATA[
-          if (!this._pluralForm) {
-            this._pluralForm = ChromeUtils.import(
-                               "resource://gre/modules/PluralForm.jsm", {}).PluralForm;
-          }
-          return this._pluralForm;
-        ]]></getter>
-      </property>
+    this._dispatchFindEvent("casesensitivitychange");
+  }
 
-      <!--
-        - Updates the search match count after each find operation on a new string.
-        - @param aRes
-        -        the result of the find operation
-        -->
-      <method name="_updateMatchesCount">
-        <body><![CDATA[
-          if (!this._dispatchFindEvent("matchescount"))
-            return;
-
-          this.browser.finder.requestMatchesCount(this._findField.value,
-            this._findMode == this.FIND_LINKS);
-        ]]></body>
-      </method>
+  /**
+   * - Updates the entire-word mode of the findbar and its UI.
+   */
+  _setEntireWord() {
+    let entireWord = this._entireWord;
+    let checkbox = this.getElement("find-entire-word");
+    let statusLabel = this.getElement("entire-word-status");
+    checkbox.checked = entireWord;
 
-      <!--
-        - Turns highlight on or off.
-        - @param aHighlight (boolean)
-        -        Whether to turn the highlight on or off
-        - @param aFromPrefObserver (boolean)
-        -        Whether the callee is the pref observer, which means we should
-        -        not set the same pref again.
-        -->
-      <method name="toggleHighlight">
-        <parameter name="aHighlight"/>
-        <parameter name="aFromPrefObserver"/>
-        <body><![CDATA[
-          if (aHighlight === this._highlightAll) {
-            return;
-          }
+    statusLabel.value = entireWord ? this._entireWordStr : "";
+
+    // Show the checkbox on the full Find bar in non-auto mode.
+    // Show the label in all other cases.
+    let hideCheckbox = this._findMode != this.FIND_NORMAL;
+    checkbox.hidden = hideCheckbox;
+    statusLabel.hidden = !hideCheckbox;
+
+    this.browser.finder.entireWord = entireWord;
+  }
 
-          this.browser.finder.onHighlightAllChange(aHighlight);
-
-          this._setHighlightAll(aHighlight, aFromPrefObserver);
-
-          if (!this._dispatchFindEvent("highlightallchange")) {
-            return;
-          }
+  /**
+   * - Sets the findbar entire-word mode
+   * - @param aEntireWord (boolean)
+   * - Whether or not entire-word mode should be turned on.
+   */
+  toggleEntireWord(aEntireWord, aFromPrefObserver) {
+    if (!aFromPrefObserver) {
+      // Just set the pref; our observer will change the find bar behavior.
+      this._prefsvc.setBoolPref("findbar.entireword", aEntireWord);
+      return;
+    }
 
-          let word = this._findField.value;
-          // Bug 429723. Don't attempt to highlight ""
-          if (aHighlight && !word)
-            return;
+    this._findFailedString = null;
+    this._find();
+  }
 
-          this.browser.finder.highlight(aHighlight, word,
-            this._findMode == this.FIND_LINKS);
-
-          // Update the matches count
-          this._updateMatchesCount(this.nsITypeAheadFind.FIND_FOUND);
-        ]]></body>
-      </method>
+  /**
+   * - Opens and displays the find bar.
+   * -
+   * - @param aMode
+   * -        the find mode to be used, which is either FIND_NORMAL,
+   * -        FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
+   * -        find mode if any or FIND_NORMAL.
+   * - @returns true if the find bar wasn't previously open, false otherwise.
+   */
+  open(aMode) {
+    if (aMode != undefined)
+      this._findMode = aMode;
 
-      <!--
-        - Updates the highlight-all mode of the findbar and its UI.
-        - @param aHighlight (boolean)
-        -        Whether to turn the highlight on or off.
-        - @param aFromPrefObserver (boolean)
-        -        Whether the callee is the pref observer, which means we should
-        -        not set the same pref again.
-        -->
-      <method name="_setHighlightAll">
-        <parameter name="aHighlight"/>
-        <parameter name="aFromPrefObserver"/>
-        <body><![CDATA[
-          if (typeof aHighlight != "boolean") {
-            aHighlight = this._highlightAll;
-          }
-          if (aHighlight !== this._highlightAll && !aFromPrefObserver) {
-            this._prefsvc.setBoolPref("findbar.highlightAll", aHighlight);
-          }
-          this._highlightAll = aHighlight;
-          let checkbox = this.getElement("highlight");
-          checkbox.checked = this._highlightAll;
-        ]]></body>
-      </method>
+    if (!this._notFoundStr) {
+      var stringsBundle = this.strBundle;
+      this._notFoundStr = stringsBundle.GetStringFromName("NotFound");
+      this._wrappedToTopStr =
+        stringsBundle.GetStringFromName("WrappedToTop");
+      this._wrappedToBottomStr =
+        stringsBundle.GetStringFromName("WrappedToBottom");
+      this._normalFindStr =
+        stringsBundle.GetStringFromName("NormalFind");
+      this._fastFindStr =
+        stringsBundle.GetStringFromName("FastFind");
+      this._fastFindLinksStr =
+        stringsBundle.GetStringFromName("FastFindLinks");
+      this._caseSensitiveStr =
+        stringsBundle.GetStringFromName("CaseSensitive");
+      this._entireWordStr =
+        stringsBundle.GetStringFromName("EntireWord");
+    }
 
-      <!--
-        - Updates the case-sensitivity mode of the findbar and its UI.
-        - @param [optional] aString
-        -        The string for which case sensitivity might be turned on.
-        -        This only used when case-sensitivity is in auto mode,
-        -        @see _shouldBeCaseSensitive. The default value for this
-        -        parameter is the find-field value.
-        -->
-      <method name="_updateCaseSensitivity">
-        <parameter name="aString"/>
-        <body><![CDATA[
-          let val = aString || this._findField.value;
+    this._findFailedString = null;
 
-          let caseSensitive = this._shouldBeCaseSensitive(val);
-          let checkbox = this.getElement("find-case-sensitive");
-          let statusLabel = this.getElement("match-case-status");
-          checkbox.checked = caseSensitive;
-
-          statusLabel.value = caseSensitive ? this._caseSensitiveStr : "";
+    this._updateFindUI();
+    if (this.hidden) {
+      this.removeAttribute("noanim");
+      this.hidden = false;
 
-          // Show the checkbox on the full Find bar in non-auto mode.
-          // Show the label in all other cases.
-          let hideCheckbox = this._findMode != this.FIND_NORMAL ||
-            (this._typeAheadCaseSensitive != 0 &&
-             this._typeAheadCaseSensitive != 1);
-          checkbox.hidden = hideCheckbox;
-          statusLabel.hidden = !hideCheckbox;
+      this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
 
-          this.browser.finder.caseSensitive = caseSensitive;
-        ]]></body>
-      </method>
+      let event = document.createEvent("Events");
+      event.initEvent("findbaropen", true, false);
+      this.dispatchEvent(event);
 
-      <!--
-        - Sets the findbar case-sensitivity mode
-        - @param aCaseSensitivity (int)
-        -   0 - case insensitive
-        -   1 - case sensitive
-        -   2 - auto = case sensitive iff match string contains upper case letters
-        -   @see _shouldBeCaseSensitive
-        -->
-      <method name="_setCaseSensitivity">
-        <parameter name="aCaseSensitivity"/>
-        <body><![CDATA[
-          this._typeAheadCaseSensitive = aCaseSensitivity;
-          this._updateCaseSensitivity();
-          this._findFailedString = null;
-          this._find();
+      this.browser.finder.onFindbarOpen();
+
+      return true;
+    }
+    return false;
+  }
 
-          this._dispatchFindEvent("casesensitivitychange");
-        ]]></body>
-      </method>
+  /**
+   * - Closes the findbar.
+   */
+  close(aNoAnim) {
+    if (this.hidden)
+      return;
 
-      <!--
-        - Updates the entire-word mode of the findbar and its UI.
-        -->
-      <method name="_setEntireWord">
-        <body><![CDATA[
-          let entireWord = this._entireWord;
-          let checkbox = this.getElement("find-entire-word");
-          let statusLabel = this.getElement("entire-word-status");
-          checkbox.checked = entireWord;
+    if (aNoAnim)
+      this.setAttribute("noanim", true);
+    this.hidden = true;
 
-          statusLabel.value = entireWord ? this._entireWordStr : "";
+    // 'focusContent()' iterates over all listeners in the chrome
+    // process, so we need to call it from here.
+    this.browser.finder.focusContent();
+    this.browser.finder.onFindbarClose();
 
-          // Show the checkbox on the full Find bar in non-auto mode.
-          // Show the label in all other cases.
-          let hideCheckbox = this._findMode != this.FIND_NORMAL;
-          checkbox.hidden = hideCheckbox;
-          statusLabel.hidden = !hideCheckbox;
+    this._cancelTimers();
+    this._updateBrowserWithState();
 
-          this.browser.finder.entireWord = entireWord;
-        ]]></body>
-      </method>
+    this._findFailedString = null;
+  }
 
-      <!--
-        - Sets the findbar entire-word mode
-        - @param aEntireWord (boolean)
-        - Whether or not entire-word mode should be turned on.
-        -->
-      <method name="toggleEntireWord">
-        <parameter name="aEntireWord"/>
-        <parameter name="aFromPrefObserver"/>
-        <body><![CDATA[
-          if (!aFromPrefObserver) {
-            // Just set the pref; our observer will change the find bar behavior.
-            this._prefsvc.setBoolPref("findbar.entireword", aEntireWord);
-            return;
-          }
+  clear() {
+    this.browser.finder.removeSelection();
+    this._findField.reset();
+    this.toggleHighlight(false);
+    this._updateStatusUI();
+    this._enableFindButtons(false);
+  }
 
-          this._findFailedString = null;
-          this._find();
-        ]]></body>
-      </method>
+  _dispatchKeypressEvent(aTarget, fakeEvent) {
+    if (!aTarget)
+      return;
 
-      <field name="_strBundle">null</field>
-      <property name="strBundle">
-        <getter><![CDATA[
-          if (!this._strBundle) {
-            this._strBundle =
-              Cc["@mozilla.org/intl/stringbundle;1"]
-                .getService(Ci.nsIStringBundleService)
-                .createBundle("chrome://global/locale/findbar.properties");
-          }
-          return this._strBundle;
-        ]]></getter>
-      </property>
+    // The event information comes from the child process. If we need more
+    // properties/information here, change the list of sent properties in
+    // browser-content.js
+    let event = new aTarget.ownerGlobal.KeyboardEvent(fakeEvent.type, fakeEvent);
+    aTarget.dispatchEvent(event);
+  }
 
-      <!--
-        - Opens and displays the find bar.
-        -
-        - @param aMode
-        -        the find mode to be used, which is either FIND_NORMAL,
-        -        FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
-        -        find mode if any or FIND_NORMAL.
-        - @returns true if the find bar wasn't previously open, false otherwise.
-        -->
-      <method name="open">
-        <parameter name="aMode"/>
-        <body><![CDATA[
-          if (aMode != undefined)
-            this._findMode = aMode;
+  _updateStatusUIBar(aFoundURL) {
+    if (!this._xulBrowserWindow) {
+      try {
+        this._xulBrowserWindow =
+          window.docShell
+                .treeOwner
+                .QueryInterface(Ci.nsIInterfaceRequestor)
+                .getInterface(Ci.nsIXULWindow)
+                .XULBrowserWindow;
+      } catch (ex) {}
+      if (!this._xulBrowserWindow)
+        return false;
+    }
 
-          if (!this._notFoundStr) {
-            var stringsBundle = this.strBundle;
-            this._notFoundStr = stringsBundle.GetStringFromName("NotFound");
-            this._wrappedToTopStr =
-              stringsBundle.GetStringFromName("WrappedToTop");
-            this._wrappedToBottomStr =
-              stringsBundle.GetStringFromName("WrappedToBottom");
-            this._normalFindStr =
-              stringsBundle.GetStringFromName("NormalFind");
-            this._fastFindStr =
-              stringsBundle.GetStringFromName("FastFind");
-            this._fastFindLinksStr =
-              stringsBundle.GetStringFromName("FastFindLinks");
-            this._caseSensitiveStr =
-              stringsBundle.GetStringFromName("CaseSensitive");
-            this._entireWordStr =
-              stringsBundle.GetStringFromName("EntireWord");
-          }
+    // Call this has the same effect like hovering over link,
+    // the browser shows the URL as a tooltip.
+    this._xulBrowserWindow.setOverLink(aFoundURL || "", null);
+    return true;
+  }
 
-          this._findFailedString = null;
-
-          this._updateFindUI();
-          if (this.hidden) {
-            this.removeAttribute("noanim");
-            this.hidden = false;
-
-            this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
+  _finishFAYT(aKeypressEvent) {
+    this.browser.finder.focusContent();
 
-            let event = document.createEvent("Events");
-            event.initEvent("findbaropen", true, false);
-            this.dispatchEvent(event);
-
-            this.browser.finder.onFindbarOpen();
-
-            return true;
-          }
-          return false;
-        ]]></body>
-      </method>
+    if (aKeypressEvent)
+      aKeypressEvent.preventDefault();
 
-      <!--
-        - Closes the findbar.
-        -->
-      <method name="close">
-        <parameter name="aNoAnim"/>
-        <body><![CDATA[
-          if (this.hidden)
-            return;
+    this.browser.finder.keyPress(aKeypressEvent);
 
-          if (aNoAnim)
-            this.setAttribute("noanim", true);
-          this.hidden = true;
-
-          // 'focusContent()' iterates over all listeners in the chrome
-          // process, so we need to call it from here.
-          this.browser.finder.focusContent();
-          this.browser.finder.onFindbarClose();
-
-          this._cancelTimers();
-          this._updateBrowserWithState();
+    this.close();
+    return true;
+  }
 
-          this._findFailedString = null;
-        ]]></body>
-      </method>
-
-      <method name="clear">
-        <body><![CDATA[
-          this.browser.finder.removeSelection();
-          this._findField.reset();
-          this.toggleHighlight(false);
-          this._updateStatusUI();
-          this._enableFindButtons(false);
-        ]]></body>
-      </method>
-
-      <method name="_dispatchKeypressEvent">
-        <parameter name="aTarget"/>
-        <parameter name="fakeEvent"/>
-        <body><![CDATA[
-          if (!aTarget)
-            return;
+  _shouldBeCaseSensitive(aString) {
+    if (this._typeAheadCaseSensitive == 0)
+      return false;
+    if (this._typeAheadCaseSensitive == 1)
+      return true;
 
-          // The event information comes from the child process. If we need more
-          // properties/information here, change the list of sent properties in
-          // browser-content.js
-          let event = new aTarget.ownerGlobal.KeyboardEvent(fakeEvent.type, fakeEvent);
-          aTarget.dispatchEvent(event);
-        ]]></body>
-      </method>
+    return aString != aString.toLowerCase();
+  }
 
-      <field name="_xulBrowserWindow">null</field>
-      <method name="_updateStatusUIBar">
-        <parameter name="aFoundURL"/>
-        <body><![CDATA[
-          if (!this._xulBrowserWindow) {
-            try {
-              this._xulBrowserWindow =
-                window.docShell
-                      .treeOwner
-                      .QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIXULWindow)
-                      .XULBrowserWindow;
-            } catch (ex) { }
-            if (!this._xulBrowserWindow)
-              return false;
-          }
-
-          // Call this has the same effect like hovering over link,
-          // the browser shows the URL as a tooltip.
-          this._xulBrowserWindow.setOverLink(aFoundURL || "", null);
-          return true;
-        ]]></body>
-      </method>
-
-      <method name="_finishFAYT">
-        <parameter name="aKeypressEvent"/>
-        <body><![CDATA[
-          this.browser.finder.focusContent();
-
-          if (aKeypressEvent)
-            aKeypressEvent.preventDefault();
+  /**
+   * We get a fake event object through an IPC message when FAYT is being used
+   * from within the browser. We then stuff that input in the find bar here.
+   */
+  _onBrowserKeypress(aFakeEvent) {
+    const FAYT_LINKS_KEY = "'";
+    const FAYT_TEXT_KEY = "/";
 
-          this.browser.finder.keyPress(aKeypressEvent);
-
-          this.close();
-          return true;
-        ]]></body>
-      </method>
-
-      <method name="_shouldBeCaseSensitive">
-        <parameter name="aString"/>
-        <body><![CDATA[
-          if (this._typeAheadCaseSensitive == 0)
-            return false;
-          if (this._typeAheadCaseSensitive == 1)
-            return true;
-
-          return aString != aString.toLowerCase();
-        ]]></body>
-      </method>
+    if (!this.hidden && this._findField.inputField == document.activeElement) {
+      this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
+      return;
+    }
 
-      <!-- We get a fake event object through an IPC message when FAYT is being used
-           from within the browser. We then stuff that input in the find bar here. -->
-      <method name="_onBrowserKeypress">
-        <parameter name="aFakeEvent"/>
-        <body><![CDATA[
-          const FAYT_LINKS_KEY = "'";
-          const FAYT_TEXT_KEY = "/";
+    if (this._findMode != this.FIND_NORMAL && this._quickFindTimeout) {
+      this._findField.select();
+      this._findField.focus();
+      this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
+      return;
+    }
 
-          if (!this.hidden && this._findField.inputField == document.activeElement) {
-            this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
-            return;
-          }
-
-          if (this._findMode != this.FIND_NORMAL && this._quickFindTimeout) {
-            this._findField.select();
-            this._findField.focus();
-            this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
-            return;
-          }
-
-          let key = aFakeEvent.charCode ? String.fromCharCode(aFakeEvent.charCode) : null;
-          let manualstartFAYT = (key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY) &&
+    let key = aFakeEvent.charCode ? String.fromCharCode(aFakeEvent.charCode) : null;
+    let manualstartFAYT = (key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY) &&
                                 this._manualFAYT;
-          let autostartFAYT = !manualstartFAYT && this._findAsYouType &&
-                              key && key != " ";
-          if (manualstartFAYT || autostartFAYT) {
-            let mode = (key == FAYT_LINKS_KEY ||
-                        (autostartFAYT && this._typeAheadLinksOnly)) ?
-              this.FIND_LINKS : this.FIND_TYPEAHEAD;
+    let autostartFAYT = !manualstartFAYT && this._findAsYouType &&
+      key && key != " ";
+    if (manualstartFAYT || autostartFAYT) {
+      let mode = (key == FAYT_LINKS_KEY ||
+          (autostartFAYT && this._typeAheadLinksOnly)) ?
+        this.FIND_LINKS : this.FIND_TYPEAHEAD;
 
-            // Clear bar first, so that when openFindBar() calls setCaseSensitivity()
-            // it doesn't get confused by a lingering value
-            this._findField.value = "";
+      // Clear bar first, so that when openFindBar() calls setCaseSensitivity()
+      // it doesn't get confused by a lingering value
+      this._findField.value = "";
+
+      this.open(mode);
+      this._setFindCloseTimeout();
+      this._findField.select();
+      this._findField.focus();
 
-            this.open(mode);
-            this._setFindCloseTimeout();
-            this._findField.select();
-            this._findField.focus();
+      if (autostartFAYT)
+        this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
+      else
+        this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
+    }
+  }
 
-            if (autostartFAYT)
-              this._dispatchKeypressEvent(this._findField.inputField, aFakeEvent);
-            else
-              this._updateStatusUI(this.nsITypeAheadFind.FIND_FOUND);
-          }
-        ]]></body>
-      </method>
+  /**
+   * See MessageListener
+   */
+  receiveMessage(aMessage) {
+    if (aMessage.target != this._browser) {
+      return undefined;
+    }
+    switch (aMessage.name) {
+      case "Findbar:Mouseup":
+        if (!this.hidden && this._findMode != this.FIND_NORMAL)
+          this.close();
+        break;
+      case "Findbar:Keypress":
+        this._onBrowserKeypress(aMessage.data);
+        break;
+    }
+    return undefined;
+  }
 
-      <!-- See MessageListener -->
-      <method name="receiveMessage">
-        <parameter name="aMessage"/>
-        <body><![CDATA[
-          if (aMessage.target != this._browser) {
-            return undefined;
-          }
-          switch (aMessage.name) {
-            case "Findbar:Mouseup":
-              if (!this.hidden && this._findMode != this.FIND_NORMAL)
-                this.close();
-              break;
-            case "Findbar:Keypress":
-              this._onBrowserKeypress(aMessage.data);
-              break;
-          }
-          return undefined;
-        ]]></body>
-      </method>
+  _updateBrowserWithState() {
+    if (this._browser && this._browser.messageManager) {
+      this._browser.messageManager.sendAsyncMessage("Findbar:UpdateState", {
+        findMode: this._findMode,
+        isOpenAndFocused: !this.hidden && document.activeElement == this._findField.inputField,
+        hasQuickFindTimeout: !!this._quickFindTimeout,
+      });
+    }
+  }
+
+  _enableFindButtons(aEnable) {
+    this.getElement("find-next").disabled =
+      this.getElement("find-previous").disabled = !aEnable;
+  }
+
+  /**
+   * - Determines whether minimalist or general-purpose search UI is to be
+   * - displayed when the find bar is activated.
+   */
+  _updateFindUI() {
+    let showMinimalUI = this._findMode != this.FIND_NORMAL;
 
-      <method name="_updateBrowserWithState">
-        <body><![CDATA[
-          if (this._browser && this._browser.messageManager) {
-            this._browser.messageManager.sendAsyncMessage("Findbar:UpdateState", {
-              findMode: this._findMode,
-              isOpenAndFocused: !this.hidden && document.activeElement == this._findField.inputField,
-              hasQuickFindTimeout: !!this._quickFindTimeout,
-            });
-          }
-        ]]></body>
-      </method>
+    let nodes = this.getElement("findbar-container").children;
+    let wrapper = this.getElement("findbar-textbox-wrapper");
+    let foundMatches = this._foundMatches;
+    for (let node of nodes) {
+      if (node == wrapper || node == foundMatches)
+        continue;
+      node.hidden = showMinimalUI;
+    }
+    this.getElement("find-next").hidden =
+      this.getElement("find-previous").hidden = showMinimalUI;
+    foundMatches.hidden = showMinimalUI || !foundMatches.value;
+    this._updateCaseSensitivity();
+    this._setEntireWord();
+    this._setHighlightAll();
 
-      <method name="_enableFindButtons">
-        <parameter name="aEnable"/>
-        <body><![CDATA[
-          this.getElement("find-next").disabled =
-            this.getElement("find-previous").disabled = !aEnable;
-        ]]></body>
-      </method>
+    if (showMinimalUI)
+      this._findField.classList.add("minimal");
+    else
+      this._findField.classList.remove("minimal");
 
-      <!--
-        - Determines whether minimalist or general-purpose search UI is to be
-        - displayed when the find bar is activated.
-        -->
-      <method name="_updateFindUI">
-        <body><![CDATA[
-          let showMinimalUI = this._findMode != this.FIND_NORMAL;
+    if (this._findMode == this.FIND_TYPEAHEAD)
+      this._findField.placeholder = this._fastFindStr;
+    else if (this._findMode == this.FIND_LINKS)
+      this._findField.placeholder = this._fastFindLinksStr;
+    else
+      this._findField.placeholder = this._normalFindStr;
+  }
+
+  _find(aValue) {
+    if (!this._dispatchFindEvent(""))
+      return;
+
+    let val = aValue || this._findField.value;
 
-          let nodes = this.getElement("findbar-container").children;
-          let wrapper = this.getElement("findbar-textbox-wrapper");
-          let foundMatches = this._foundMatches;
-          for (let node of nodes) {
-            if (node == wrapper || node == foundMatches)
-               continue;
-            node.hidden = showMinimalUI;
-          }
-          this.getElement("find-next").hidden =
-            this.getElement("find-previous").hidden = showMinimalUI;
-          foundMatches.hidden = showMinimalUI || !foundMatches.value;
-          this._updateCaseSensitivity();
-          this._setEntireWord();
-          this._setHighlightAll();
+    // We have to carry around an explicit version of this,
+    // because finder.searchString doesn't update on failed
+    // searches.
+    this.browser._lastSearchString = val;
 
-          if (showMinimalUI)
-            this._findField.classList.add("minimal");
-          else
-            this._findField.classList.remove("minimal");
+    // Only search on input if we don't have a last-failed string,
+    // or if the current search string doesn't start with it.
+    // In entire-word mode we always attemp a find; since sequential matching
+    // is not guaranteed, the first character typed may not be a word (no
+    // match), but the with the second character it may well be a word,
+    // thus a match.
+    if (!this._findFailedString ||
+      !val.startsWith(this._findFailedString) ||
+      this._entireWord) {
+      // Getting here means the user commanded a find op. Make sure any
+      // initial prefilling is ignored if it hasn't happened yet.
+      if (this._startFindDeferred) {
+        this._startFindDeferred.resolve();
+        this._startFindDeferred = null;
+      }
 
-          if (this._findMode == this.FIND_TYPEAHEAD)
-            this._findField.placeholder = this._fastFindStr;
-          else if (this._findMode == this.FIND_LINKS)
-            this._findField.placeholder = this._fastFindLinksStr;
-          else
-            this._findField.placeholder = this._normalFindStr;
-        ]]></body>
-      </method>
+      this._enableFindButtons(val);
+      this._updateCaseSensitivity(val);
+      this._setEntireWord();
+
+      this.browser.finder.fastFind(val, this._findMode == this.FIND_LINKS,
+        this._findMode != this.FIND_NORMAL);
+    }
+
+    if (this._findMode != this.FIND_NORMAL)
+      this._setFindCloseTimeout();
 
-      <method name="_find">
-        <parameter name="aValue"/>
-        <body><![CDATA[
-          if (!this._dispatchFindEvent(""))
-            return;
+    if (this._findResetTimeout != -1)
+      clearTimeout(this._findResetTimeout);
 
-          let val = aValue || this._findField.value;
+    // allow a search to happen on input again after a second has
+    // expired since the previous input, to allow for dynamic
+    // content and/or page loading
+    this._findResetTimeout = setTimeout(() => {
+      this._findFailedString = null;
+      this._findResetTimeout = -1;
+    }, 1000);
+  }
 
-          // We have to carry around an explicit version of this,
-          // because finder.searchString doesn't update on failed
-          // searches.
-          this.browser._lastSearchString = val;
+  _flash() {
+    if (this._flashFindBarCount === undefined)
+      this._flashFindBarCount = this._initialFlashFindBarCount;
 
-          // Only search on input if we don't have a last-failed string,
-          // or if the current search string doesn't start with it.
-          // In entire-word mode we always attemp a find; since sequential matching
-          // is not guaranteed, the first character typed may not be a word (no
-          // match), but the with the second character it may well be a word,
-          // thus a match.
-          if (!this._findFailedString ||
-              !val.startsWith(this._findFailedString) ||
-              this._entireWord) {
-            // Getting here means the user commanded a find op. Make sure any
-            // initial prefilling is ignored if it hasn't happened yet.
-            if (this._startFindDeferred) {
-              this._startFindDeferred.resolve();
-              this._startFindDeferred = null;
-            }
+    if (this._flashFindBarCount-- == 0) {
+      clearInterval(this._flashFindBarTimeout);
+      this._findField.removeAttribute("flash");
+      this._flashFindBarCount = 6;
+      return;
+    }
 
-            this._enableFindButtons(val);
-            this._updateCaseSensitivity(val);
-            this._setEntireWord();
+    this._findField.setAttribute("flash",
+      (this._flashFindBarCount % 2 == 0) ?
+      "false" : "true");
+  }
 
-            this.browser.finder.fastFind(val, this._findMode == this.FIND_LINKS,
-                                         this._findMode != this.FIND_NORMAL);
-          }
-
-          if (this._findMode != this.FIND_NORMAL)
-            this._setFindCloseTimeout();
+  _findAgain(aFindPrevious) {
+    this.browser.finder.findAgain(aFindPrevious,
+      this._findMode == this.FIND_LINKS,
+      this._findMode != this.FIND_NORMAL);
+  }
 
-          if (this._findResetTimeout != -1)
-            clearTimeout(this._findResetTimeout);
-
-          // allow a search to happen on input again after a second has
-          // expired since the previous input, to allow for dynamic
-          // content and/or page loading
-          this._findResetTimeout = setTimeout(() => {
-            this._findFailedString = null;
-            this._findResetTimeout = -1;
-          }, 1000);
-        ]]></body>
-      </method>
+  _updateStatusUI(res, aFindPrevious) {
+    switch (res) {
+      case this.nsITypeAheadFind.FIND_WRAPPED:
+        this._findStatusIcon.setAttribute("status", "wrapped");
+        this._findStatusDesc.textContent =
+          aFindPrevious ? this._wrappedToBottomStr : this._wrappedToTopStr;
+        this._findField.removeAttribute("status");
+        break;
+      case this.nsITypeAheadFind.FIND_NOTFOUND:
+        this._findStatusIcon.setAttribute("status", "notfound");
+        this._findStatusDesc.textContent = this._notFoundStr;
+        this._findField.setAttribute("status", "notfound");
+        break;
+      case this.nsITypeAheadFind.FIND_PENDING:
+        this._findStatusIcon.setAttribute("status", "pending");
+        this._findStatusDesc.textContent = "";
+        this._findField.removeAttribute("status");
+        break;
+      case this.nsITypeAheadFind.FIND_FOUND:
+      default:
+        this._findStatusIcon.removeAttribute("status");
+        this._findStatusDesc.textContent = "";
+        this._findField.removeAttribute("status");
+        break;
+    }
+  }
 
-      <method name="_flash">
-        <body><![CDATA[
-          if (this._flashFindBarCount === undefined)
-            this._flashFindBarCount = this._initialFlashFindBarCount;
-
-          if (this._flashFindBarCount-- == 0) {
-            clearInterval(this._flashFindBarTimeout);
-            this.removeAttribute("flash");
-            this._flashFindBarCount = 6;
-            return;
-          }
-
-          this.setAttribute("flash",
-                            (this._flashFindBarCount % 2 == 0) ?
-                            "false" : "true");
-        ]]></body>
-      </method>
+  updateControlState(aResult, aFindPrevious) {
+    this._updateStatusUI(aResult, aFindPrevious);
+    this._enableFindButtons(aResult !== this.nsITypeAheadFind.FIND_NOTFOUND &&
+      !!this._findField.value);
+  }
 
-      <method name="_findAgain">
-        <parameter name="aFindPrevious"/>
-        <body><![CDATA[
-          this.browser.finder.findAgain(aFindPrevious,
-                                        this._findMode == this.FIND_LINKS,
-                                        this._findMode != this.FIND_NORMAL);
-        ]]></body>
-      </method>
+  _dispatchFindEvent(aType, aFindPrevious) {
+    let event = document.createEvent("CustomEvent");
+    event.initCustomEvent("find" + aType, true, true, {
+      query: this._findField.value,
+      caseSensitive: !!this._typeAheadCaseSensitive,
+      entireWord: this._entireWord,
+      highlightAll: this._highlightAll,
+      findPrevious: aFindPrevious,
+    });
+    return this.dispatchEvent(event);
+  }
 
-      <method name="_updateStatusUI">
-        <parameter name="res"/>
-        <parameter name="aFindPrevious"/>
-        <body><![CDATA[
-          switch (res) {
-            case this.nsITypeAheadFind.FIND_WRAPPED:
-              this._findStatusIcon.setAttribute("status", "wrapped");
-              this._findStatusDesc.textContent =
-                aFindPrevious ? this._wrappedToBottomStr : this._wrappedToTopStr;
-              this._findField.removeAttribute("status");
-              break;
-            case this.nsITypeAheadFind.FIND_NOTFOUND:
-              this._findStatusIcon.setAttribute("status", "notfound");
-              this._findStatusDesc.textContent = this._notFoundStr;
-              this._findField.setAttribute("status", "notfound");
-              break;
-            case this.nsITypeAheadFind.FIND_PENDING:
-              this._findStatusIcon.setAttribute("status", "pending");
-              this._findStatusDesc.textContent = "";
-              this._findField.removeAttribute("status");
-              break;
-            case this.nsITypeAheadFind.FIND_FOUND:
-            default:
-              this._findStatusIcon.removeAttribute("status");
-              this._findStatusDesc.textContent = "";
-              this._findField.removeAttribute("status");
-              break;
-          }
-        ]]></body>
-      </method>
+  /**
+   * - Opens the findbar, focuses the findfield and selects its contents.
+   * - Also flashes the findbar the first time it's used.
+   * - @param aMode
+   * -        the find mode to be used, which is either FIND_NORMAL,
+   * -        FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
+   * -        find mode if any or FIND_NORMAL.
+   */
+  startFind(aMode) {
+    let prefsvc = this._prefsvc;
+    let userWantsPrefill = true;
+    this.open(aMode);
 
-      <method name="updateControlState">
-        <parameter name="aResult"/>
-        <parameter name="aFindPrevious"/>
-        <body><![CDATA[
-          this._updateStatusUI(aResult, aFindPrevious);
-          this._enableFindButtons(aResult !== this.nsITypeAheadFind.FIND_NOTFOUND &&
-            !!this._findField.value);
-        ]]></body>
-      </method>
+    if (this._flashFindBar) {
+      this._flashFindBarTimeout = setInterval(() => this._flash(), 500);
+      prefsvc.setIntPref("accessibility.typeaheadfind.flashBar",
+        --this._flashFindBar);
+    }
+
+    let { PromiseUtils } =
+      ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm", {});
+    this._startFindDeferred = PromiseUtils.defer();
+    let startFindPromise = this._startFindDeferred.promise;
+
+    if (this.prefillWithSelection)
+      userWantsPrefill =
+      prefsvc.getBoolPref("accessibility.typeaheadfind.prefillwithselection");
 
-      <method name="_dispatchFindEvent">
-        <parameter name="aType"/>
-        <parameter name="aFindPrevious"/>
-        <body><![CDATA[
-          let event = document.createEvent("CustomEvent");
-          event.initCustomEvent("find" + aType, true, true, {
-            query: this._findField.value,
-            caseSensitive: !!this._typeAheadCaseSensitive,
-            entireWord: this._entireWord,
-            highlightAll: this._highlightAll,
-            findPrevious: aFindPrevious,
-          });
-          return this.dispatchEvent(event);
-        ]]></body>
-      </method>
+    if (this.prefillWithSelection && userWantsPrefill) {
+      // NB: We have to focus this._findField here so tests that send
+      // key events can open and close the find bar synchronously.
+      this._findField.focus();
 
+      // (e10s) since we focus lets also select it, otherwise that would
+      // only happen in this.onCurrentSelection and, because it is async,
+      // there's a chance keypresses could come inbetween, leading to
+      // jumbled up queries.
+      this._findField.select();
 
-      <!--
-        - Opens the findbar, focuses the findfield and selects its contents.
-        - Also flashes the findbar the first time it's used.
-        - @param aMode
-        -        the find mode to be used, which is either FIND_NORMAL,
-        -        FIND_TYPEAHEAD or FIND_LINKS. If not passed, the last
-        -        find mode if any or FIND_NORMAL.
-        -->
-      <method name="startFind">
-        <parameter name="aMode"/>
-        <body><![CDATA[
-          let prefsvc = this._prefsvc;
-          let userWantsPrefill = true;
-          this.open(aMode);
+      this.browser.finder.getInitialSelection();
+      return startFindPromise;
+    }
 
-          if (this._flashFindBar) {
-            this._flashFindBarTimeout = setInterval(() => this._flash(), 500);
-            prefsvc.setIntPref("accessibility.typeaheadfind.flashBar",
-                               --this._flashFindBar);
-          }
-
-          let {PromiseUtils} =
-            ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm", {});
-          this._startFindDeferred = PromiseUtils.defer();
-          let startFindPromise = this._startFindDeferred.promise;
-
-          if (this.prefillWithSelection)
-            userWantsPrefill =
-              prefsvc.getBoolPref("accessibility.typeaheadfind.prefillwithselection");
+    // If userWantsPrefill is false but prefillWithSelection is true,
+    // then we might need to check the selection clipboard. Call
+    // onCurrentSelection to do so.
+    // Note: this.onCurrentSelection clears this._startFindDeferred.
+    this.onCurrentSelection("", true);
+    return startFindPromise;
+  }
 
-          if (this.prefillWithSelection && userWantsPrefill) {
-            // NB: We have to focus this._findField here so tests that send
-            // key events can open and close the find bar synchronously.
-            this._findField.focus();
-
-            // (e10s) since we focus lets also select it, otherwise that would
-            // only happen in this.onCurrentSelection and, because it is async,
-            // there's a chance keypresses could come inbetween, leading to
-            // jumbled up queries.
-            this._findField.select();
+  /**
+   * - Convenient alias to startFind(gFindBar.FIND_NORMAL);
+   * -
+   * - You should generally map the window's find command to this method.
+   * -   e.g. <command name="cmd_find" oncommand="gFindBar.onFindCommand();"/>
+   */
+  onFindCommand() {
+    return this.startFind(this.FIND_NORMAL);
+  }
 
-            this.browser.finder.getInitialSelection();
-            return startFindPromise;
-          }
-
-          // If userWantsPrefill is false but prefillWithSelection is true,
-          // then we might need to check the selection clipboard. Call
-          // onCurrentSelection to do so.
-          // Note: this.onCurrentSelection clears this._startFindDeferred.
-          this.onCurrentSelection("", true);
-          return startFindPromise;
-        ]]></body>
-      </method>
+  /**
+   * - Stub for find-next and find-previous commands
+   * - @param aFindPrevious
+   * -        true for find-previous, false otherwise.
+   */
+  onFindAgainCommand(aFindPrevious) {
+    let findString = this._browser.finder.searchString || this._findField.value;
+    if (!findString)
+      return this.startFind();
 
-      <!--
-        - Convenient alias to startFind(gFindBar.FIND_NORMAL);
-        -
-        - You should generally map the window's find command to this method.
-        -   e.g. <command name="cmd_find" oncommand="gFindBar.onFindCommand();"/>
-        -->
-      <method name="onFindCommand">
-        <body><![CDATA[
-          return this.startFind(this.FIND_NORMAL);
-        ]]></body>
-      </method>
+    // We dispatch the findAgain event here instead of in _findAgain since
+    // if there is a find event handler that prevents the default then
+    // finder.searchString will never get updated which in turn means
+    // there would never be findAgain events because of the logic below.
+    if (!this._dispatchFindEvent("again", aFindPrevious))
+      return undefined;
 
-      <!--
-        - Stub for find-next and find-previous commands
-        - @param aFindPrevious
-        -        true for find-previous, false otherwise.
-        -->
-      <method name="onFindAgainCommand">
-        <parameter name="aFindPrevious"/>
-        <body><![CDATA[
-          let findString = this._browser.finder.searchString || this._findField.value;
-          if (!findString)
-            return this.startFind();
+    // user explicitly requested another search, so do it even if we think it'll fail
+    this._findFailedString = null;
 
-          // We dispatch the findAgain event here instead of in _findAgain since
-          // if there is a find event handler that prevents the default then
-          // finder.searchString will never get updated which in turn means
-          // there would never be findAgain events because of the logic below.
-          if (!this._dispatchFindEvent("again", aFindPrevious))
-            return undefined;
+    // Ensure the stored SearchString is in sync with what we want to find
+    if (this._findField.value != this._browser.finder.searchString) {
+      this._find(this._findField.value);
+    } else {
+      this._findAgain(aFindPrevious);
+      if (this._useModalHighlight) {
+        this.open();
+        this._findField.focus();
+      }
+    }
 
-          // user explicitly requested another search, so do it even if we think it'll fail
-          this._findFailedString = null;
+    return undefined;
+  }
 
-          // Ensure the stored SearchString is in sync with what we want to find
-          if (this._findField.value != this._browser.finder.searchString) {
-            this._find(this._findField.value);
-          } else {
-            this._findAgain(aFindPrevious);
-            if (this._useModalHighlight) {
-              this.open();
-              this._findField.focus();
-            }
-          }
+  /* Fetches the currently selected text and sets that as the text to search
+     next. This is a MacOS specific feature. */
+  onFindSelectionCommand() {
+    let searchString = this.browser.finder.setSearchStringToSelection();
+    if (searchString)
+      this._findField.value = searchString;
 
-          return undefined;
-        ]]></body>
-      </method>
+  }
+
+  _onFindFieldFocus() {
+    let prefsvc = this._prefsvc;
+    const kPref = "accessibility.typeaheadfind.prefillwithselection";
+    if (this.prefillWithSelection && prefsvc.getBoolPref(kPref))
+      return;
 
-#ifdef XP_MACOSX
-      <!--
-        - Fetches the currently selected text and sets that as the text to search
-        - next. This is a MacOS specific feature.
-      -->
-      <method name="onFindSelectionCommand">
-        <body><![CDATA[
-          let searchString = this.browser.finder.setSearchStringToSelection();
-          if (searchString)
-            this._findField.value = searchString;
-        ]]></body>
-      </method>
-
-      <method name="_onFindFieldFocus">
-        <body><![CDATA[
-          let prefsvc = this._prefsvc;
-          const kPref = "accessibility.typeaheadfind.prefillwithselection";
-          if (this.prefillWithSelection && prefsvc.getBoolPref(kPref))
-            return;
-
-          let clipboardSearchString = this._browser.finder.clipboardSearchString;
-          if (clipboardSearchString && this._findField.value != clipboardSearchString &&
-              !this._findField._willfullyDeleted) {
-            this._findField.value = clipboardSearchString;
-            this._findField._hadValue = true;
-            // Changing the search string makes the previous status invalid, so
-            // we better clear it here.
-            this._updateStatusUI();
-          }
-        ]]></body>
-      </method>
-#endif
+    let clipboardSearchString = this._browser.finder.clipboardSearchString;
+    if (clipboardSearchString && this._findField.value != clipboardSearchString &&
+        !this._findField._willfullyDeleted) {
+      this._findField.value = clipboardSearchString;
+      this._findField._hadValue = true;
+      // Changing the search string makes the previous status invalid, so
+      // we better clear it here.
+      this._updateStatusUI();
+    }
+  }
 
-      <!--
-        - This handles all the result changes for both
-        - type-ahead-find and highlighting.
-        - @param aResult
-        -   One of the nsITypeAheadFind.FIND_* constants
-        -   indicating the result of a search operation.
-        - @param aFindBackwards
-        -   If the search was done from the bottom to
-        -   the top. This is used for right error messages
-        -   when reaching "the end of the page".
-        - @param aLinkURL
-        -   When a link matched then its URK. Always null
-        -   when not in FIND_LINKS mode.
-        -->
-      <method name="onFindResult">
-        <parameter name="aData"/>
-        <body><![CDATA[
-          if (aData.result == this.nsITypeAheadFind.FIND_NOTFOUND) {
-            // If an explicit Find Again command fails, re-open the toolbar.
-            if (aData.storeResult && this.open()) {
-              this._findField.select();
-              this._findField.focus();
-            }
-            this._findFailedString = aData.searchString;
-          } else {
-            this._findFailedString = null;
-          }
+  /**
+   * - This handles all the result changes for both
+   * - type-ahead-find and highlighting.
+   * - @param aResult
+   * -   One of the nsITypeAheadFind.FIND_* constants
+   * -   indicating the result of a search operation.
+   * - @param aFindBackwards
+   * -   If the search was done from the bottom to
+   * -   the top. This is used for right error messages
+   * -   when reaching "the end of the page".
+   * - @param aLinkURL
+   * -   When a link matched then its URK. Always null
+   * -   when not in FIND_LINKS mode.
+   */
+  onFindResult(aData) {
+    if (aData.result == this.nsITypeAheadFind.FIND_NOTFOUND) {
+      // If an explicit Find Again command fails, re-open the toolbar.
+      if (aData.storeResult && this.open()) {
+        this._findField.select();
+        this._findField.focus();
+      }
+      this._findFailedString = aData.searchString;
+    } else {
+      this._findFailedString = null;
+    }
 
-          this._updateStatusUI(aData.result, aData.findBackwards);
-          this._updateStatusUIBar(aData.linkURL);
+    this._updateStatusUI(aData.result, aData.findBackwards);
+    this._updateStatusUIBar(aData.linkURL);
 
-          if (this._findMode != this.FIND_NORMAL)
-            this._setFindCloseTimeout();
-        ]]></body>
-      </method>
+    if (this._findMode != this.FIND_NORMAL)
+      this._setFindCloseTimeout();
+  }
 
-      <!--
-        - This handles all the result changes for matches counts.
-        - @param aResult
-        -   Result Object, containing the total amount of matches and a vector
-        -   of the current result.
-        -->
-      <method name="onMatchesCountResult">
-        <parameter name="aResult"/>
-        <body><![CDATA[
-          if (aResult.total !== 0) {
-            if (aResult.total == -1) {
-              this._foundMatches.value = this.pluralForm.get(
-                aResult.limit,
-                this.strBundle.GetStringFromName("FoundMatchesCountLimit")
-              ).replace("#1", aResult.limit);
-            } else {
-              this._foundMatches.value = this.pluralForm.get(
-                aResult.total,
-                this.strBundle.GetStringFromName("FoundMatches")
-              ).replace("#1", aResult.current)
-               .replace("#2", aResult.total);
-            }
-            this._foundMatches.hidden = false;
-          } else {
-            this._foundMatches.hidden = true;
-            this._foundMatches.value = "";
-          }
-        ]]></body>
-      </method>
+  /**
+   * - This handles all the result changes for matches counts.
+   * - @param aResult
+   * -   Result Object, containing the total amount of matches and a vector
+   * -   of the current result.
+   */
+  onMatchesCountResult(aResult) {
+    if (aResult.total !== 0) {
+      if (aResult.total == -1) {
+        this._foundMatches.value = this.pluralForm.get(
+          aResult.limit,
+          this.strBundle.GetStringFromName("FoundMatchesCountLimit")
+        ).replace("#1", aResult.limit);
+      } else {
+        this._foundMatches.value = this.pluralForm.get(
+            aResult.total,
+            this.strBundle.GetStringFromName("FoundMatches")
+          ).replace("#1", aResult.current)
+          .replace("#2", aResult.total);
+      }
+      this._foundMatches.hidden = false;
+    } else {
+      this._foundMatches.hidden = true;
+      this._foundMatches.value = "";
+    }
+  }
 
-      <method name="onHighlightFinished">
-        <parameter name="result"/>
-        <body><![CDATA[
-          // Noop.
-        ]]></body>
-      </method>
+  onHighlightFinished(result) {
+    // Noop.
+  }
 
-      <method name="onCurrentSelection">
-        <parameter name="aSelectionString" />
-        <parameter name="aIsInitialSelection" />
-        <body><![CDATA[
-          // Ignore the prefill if the user has already typed in the findbar,
-          // it would have been overwritten anyway. See bug 1198465.
-          if (aIsInitialSelection && !this._startFindDeferred)
-            return;
+  onCurrentSelection(aSelectionString, aIsInitialSelection) {
+    // Ignore the prefill if the user has already typed in the findbar,
+    // it would have been overwritten anyway. See bug 1198465.
+    if (aIsInitialSelection && !this._startFindDeferred)
+      return;
+
+    if (/Mac/.test(window.navigator.platform) && aIsInitialSelection && !aSelectionString) {
+      let clipboardSearchString = this.browser.finder.clipboardSearchString;
+      if (clipboardSearchString)
+        aSelectionString = clipboardSearchString;
+    }
 
-          if (/Mac/.test(navigator.platform) && aIsInitialSelection && !aSelectionString) {
-            let clipboardSearchString = this.browser.finder.clipboardSearchString;
-            if (clipboardSearchString)
-              aSelectionString = clipboardSearchString;
-          }
+    if (aSelectionString)
+      this._findField.value = aSelectionString;
 
-          if (aSelectionString)
-            this._findField.value = aSelectionString;
+    if (aIsInitialSelection) {
+      this._enableFindButtons(!!this._findField.value);
+      this._findField.select();
+      this._findField.focus();
 
-          if (aIsInitialSelection) {
-            this._enableFindButtons(!!this._findField.value);
-            this._findField.select();
-            this._findField.focus();
+      this._startFindDeferred.resolve();
+      this._startFindDeferred = null;
+    }
+  }
 
-            this._startFindDeferred.resolve();
-            this._startFindDeferred = null;
-          }
-        ]]></body>
-      </method>
+  /**
+   * - This handler may cancel a request to focus content by returning |false|
+   * - explicitly.
+   */
+  shouldFocusContent() {
+    const fm = Services.focus;
+    if (fm.focusedWindow != window)
+      return false;
 
-      <!--
-        - This handler may cancel a request to focus content by returning |false|
-        - explicitly.
-        -->
-      <method name="shouldFocusContent">
-        <body><![CDATA[
-          const fm = Cc["@mozilla.org/focus-manager;1"]
-                       .getService(Ci.nsIFocusManager);
-          if (fm.focusedWindow != window)
-            return false;
+    let focusedElement = fm.focusedElement;
+    if (!focusedElement)
+      return false;
 
-          let focusedElement = fm.focusedElement;
-          if (!focusedElement)
-            return false;
+    let bindingParent = document.getBindingParent(focusedElement);
+    if (bindingParent != this && bindingParent != this._findField)
+      return false;
+
+    return true;
+  }
 
-          let bindingParent = document.getBindingParent(focusedElement);
-          if (bindingParent != this && bindingParent != this._findField)
-            return false;
-
-          return true;
-        ]]></body>
-      </method>
-
-    </implementation>
+  disconnectedCallback() {
+    // Empty the DOM. We will rebuild if reconnected.
+    while (this.lastChild) {
+      this.removeChild(this.lastChild);
+    }
+    this.destroy();
+  }
+}
 
-    <handlers>
-      <!--
-        - We have to guard against `this.close` being |null| due to an unknown
-        - issue, which is tracked in bug 957999.
-        -->
-      <handler event="keypress" keycode="VK_ESCAPE" phase="capturing"
-               action="if (this.close) this.close();" preventdefault="true"/>
-    </handlers>
-  </binding>
-</bindings>
+customElements.define("findbar", MozFindbar);
+
+}
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -817,24 +817,19 @@ richlistbox {
 
 richlistitem {
   -moz-binding: url('chrome://global/content/bindings/richlistbox.xml#richlistitem');
 }
 
 
 /*********** findbar ************/
 findbar {
-  -moz-binding: url('chrome://global/content/bindings/findbar.xml#findbar');
   overflow-x: hidden;
 }
 
-.findbar-textbox {
-  -moz-binding: url("chrome://global/content/bindings/findbar.xml#findbar-textbox");
-}
-
 /*********** tabmodalprompt ************/
 tabmodalprompt {
   -moz-binding: url("chrome://global/content/tabprompts.xml#tabmodalprompt");
   overflow: hidden;
   text-shadow: none;
 }
 
 .button-highlightable-text:not([highlightable="true"]),
deleted file mode 100644
--- a/toolkit/locales/en-US/chrome/global/findbar.dtd
+++ /dev/null
@@ -1,19 +0,0 @@
-<!-- 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/. -->
-
-<!-- LOCALIZATION NOTE : FILE This file contains the entities needed to -->
-<!-- LOCALIZATION NOTE : FILE use the Find Bar. --> 
-
-<!ENTITY next.tooltip "Find the next occurrence of the phrase">
-<!ENTITY previous.tooltip "Find the previous occurrence of the phrase">
-<!ENTITY findCloseButton.tooltip "Close find bar">
-<!ENTITY highlightAll.label "Highlight All">
-<!ENTITY highlightAll.accesskey "l">
-<!ENTITY highlightAll.tooltiptext "Highlight all occurrences of the phrase">
-<!ENTITY caseSensitive.label "Match Case">
-<!ENTITY caseSensitive.accesskey "c">
-<!ENTITY caseSensitive.tooltiptext "Search with case sensitivity">
-<!ENTITY entireWord.label "Whole Words">
-<!ENTITY entireWord.accesskey "w">
-<!ENTITY entireWord.tooltiptext "Search whole words only">
--- a/toolkit/locales/en-US/toolkit/main-window/editmenu.ftl
+++ b/toolkit/locales/en-US/toolkit/main-window/editmenu.ftl
@@ -1,8 +1,12 @@
+# 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/.
+
 ### This file contains the entities needed for the 'edit' menu
 ### It's currently only used for the Browser Console
 
 editmenu-undo =
     .label = Undo
     .accesskey = U
 
 editmenu-redo =
new file mode 100644
--- /dev/null
+++ b/toolkit/locales/en-US/toolkit/main-window/findbar.ftl
@@ -0,0 +1,28 @@
+# 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/.
+
+### This file contains the entities needed to use the Find Bar.
+
+findbar-next =
+    .tooltiptext = Find the next occurrence of the phrase
+findbar-previous =
+    .tooltiptext = Find the previous occurrence of the phrase
+
+findbar-find-button-close =
+    .tooltiptext = Close find bar
+
+findbar-highlight-all =
+    .label = Highlight All
+    .accesskey = l
+    .tooltiptext = Highlight all occurrences of the phrase
+
+findbar-case-sensitive =
+    .label = Match Case
+    .accesskey = C
+    .tooltiptext = Search with case sensitivity
+
+findbar-entire-word =
+    .label = Whole Words
+    .accesskey = W
+    .tooltiptext = Search whole words only
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -41,17 +41,16 @@
   locale/@AB_CD@/global/dialogOverlay.dtd               (%chrome/global/dialogOverlay.dtd)
 #ifndef MOZ_FENNEC
   locale/@AB_CD@/global/editMenuOverlay.dtd             (%chrome/global/editMenuOverlay.dtd)
 #endif
   locale/@AB_CD@/global/extensions.properties           (%chrome/global/extensions.properties)
   locale/@AB_CD@/global/fallbackMenubar.properties      (%chrome/global/fallbackMenubar.properties)
   locale/@AB_CD@/global/filepicker.properties           (%chrome/global/filepicker.properties)
 #ifndef MOZ_FENNEC
-  locale/@AB_CD@/global/findbar.dtd                     (%chrome/global/findbar.dtd)
   locale/@AB_CD@/global/findbar.properties              (%chrome/global/findbar.properties)
 #endif
   locale/@AB_CD@/global/globalKeys.dtd                  (%chrome/global/globalKeys.dtd)
   locale/@AB_CD@/global/intl.css                        (%chrome/global/intl.css)
   locale/@AB_CD@/global/intl.properties                 (%chrome/global/intl.properties)
   locale/@AB_CD@/global/keys.properties                 (%chrome/global/keys.properties)
   locale/@AB_CD@/global/languageNames.properties        (%chrome/global/languageNames.properties)
   locale/@AB_CD@/global/mozilla.dtd                     (%chrome/global/mozilla.dtd)