Bug 1132518, make document navigation with F6/Shift+F6 work in e10s. This combines the document and tab navigation mechanisms together, r=smaug
authorNeil Deakin <neil@mozilla.com>
Mon, 13 Jul 2015 06:07:49 -0400
changeset 252504 cf5cb1d5802ea1f3ec6a2af1178c8c2a95f0f400
parent 252503 c4753659cf72fac4c4abf1a6980d0ed587c26c61
child 252505 87f44cd6e7f0cc030a7a86f47a84cb3b5222ee72
push id62174
push userneil@mozilla.com
push dateMon, 13 Jul 2015 10:08:16 +0000
treeherdermozilla-inbound@dc9d58b43abf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1132518
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1132518, make document navigation with F6/Shift+F6 work in e10s. This combines the document and tab navigation mechanisms together, r=smaug
docshell/base/nsDocShell.cpp
docshell/base/nsIDocShell.idl
dom/base/nsFocusManager.cpp
dom/base/nsFocusManager.h
dom/interfaces/base/nsIFocusManager.idl
dom/interfaces/base/nsITabParent.idl
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
embedding/browser/nsIWebBrowserChromeFocus.idl
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2799,26 +2799,26 @@ nsDocShell::GetBusyFlags(uint32_t* aBusy
 {
   NS_ENSURE_ARG_POINTER(aBusyFlags);
 
   *aBusyFlags = mBusyFlags;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDocShell::TabToTreeOwner(bool aForward, bool* aTookFocus)
+nsDocShell::TabToTreeOwner(bool aForward, bool aForDocumentNavigation, bool* aTookFocus)
 {
   NS_ENSURE_ARG_POINTER(aTookFocus);
 
   nsCOMPtr<nsIWebBrowserChromeFocus> chromeFocus = do_GetInterface(mTreeOwner);
   if (chromeFocus) {
     if (aForward) {
-      *aTookFocus = NS_SUCCEEDED(chromeFocus->FocusNextElement());
+      *aTookFocus = NS_SUCCEEDED(chromeFocus->FocusNextElement(aForDocumentNavigation));
     } else {
-      *aTookFocus = NS_SUCCEEDED(chromeFocus->FocusPrevElement());
+      *aTookFocus = NS_SUCCEEDED(chromeFocus->FocusPrevElement(aForDocumentNavigation));
     }
   } else {
     *aTookFocus = false;
   }
 
   return NS_OK;
 }
 
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -41,17 +41,17 @@ interface nsIWebBrowserPrint;
 interface nsIVariant;
 interface nsIPrivacyTransitionObserver;
 interface nsIReflowObserver;
 interface nsIScrollObserver;
 interface nsITabParent;
  
 typedef unsigned long nsLoadFlags;
 
-[scriptable, builtinclass, uuid(b3137b7c-d589-48aa-b89b-e02aa451d42c)]
+[scriptable, builtinclass, uuid(97471054-0BC8-4A57-BBFE-6C255BCBBB67)]
 interface nsIDocShell : nsIDocShellTreeItem
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -342,19 +342,22 @@ interface nsIDocShell : nsIDocShellTreeI
    * HTML <frame>/<iframe> elements.  A value smaller than zero indicates that
    * the attribute was not set.
    */
   attribute long marginHeight;
 
   /*
    * Tells the docshell to offer focus to its tree owner.
    * This is currently only necessary for embedding chrome.
+   * If forDocumentNavigation is true, then document navigation should be
+   * performed, where only the root of documents are selected. Otherwise, the
+   * next element in the parent should be returned. Returns true if focus was
+   * successfully taken by the tree owner.
    */
-  void tabToTreeOwner(in boolean forward,
-                      out boolean tookFocus);
+  bool tabToTreeOwner(in boolean forward, in boolean forDocumentNavigation);
 
   /**
    * Current busy state for DocShell
    */
   const unsigned long BUSY_FLAGS_NONE             = 0;
   const unsigned long BUSY_FLAGS_BUSY             = 1;
   const unsigned long BUSY_FLAGS_BEFORE_PAGE_LOAD = 2;
   const unsigned long BUSY_FLAGS_PAGE_LOADING     = 4;
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -11,16 +11,17 @@
 #include "AccessibleCaretEventHub.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsGkAtoms.h"
 #include "nsContentUtils.h"
 #include "nsIDocument.h"
 #include "nsIDOMWindow.h"
 #include "nsIEditor.h"
 #include "nsPIDOMWindow.h"
+#include "nsIDOMChromeWindow.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMRange.h"
 #include "nsIHTMLDocument.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsLayoutUtils.h"
 #include "nsIPresShell.h"
@@ -28,24 +29,26 @@
 #include "nsIWebNavigation.h"
 #include "nsCaret.h"
 #include "nsIBaseWindow.h"
 #include "nsIXULWindow.h"
 #include "nsViewManager.h"
 #include "nsFrameSelection.h"
 #include "mozilla/dom/Selection.h"
 #include "nsXULPopupManager.h"
+#include "nsMenuPopupFrame.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIPrincipal.h"
 #include "nsIObserverService.h"
 #include "nsIObjectFrame.h"
 #include "nsBindingManager.h"
 #include "nsStyleCoord.h"
 #include "SelectionCarets.h"
 #include "TabChild.h"
+#include "nsFrameLoader.h"
 
 #include "mozilla/ContentEvents.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/LookAndFeel.h"
@@ -516,16 +519,20 @@ nsFocusManager::MoveFocus(nsIDOMWindow* 
   }
 
   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 
   bool noParentTraversal = aFlags & FLAG_NOPARENTFRAME;
   nsCOMPtr<nsIContent> newFocus;
   nsresult rv = DetermineElementToMoveFocus(window, startContent, aType, noParentTraversal,
                                             getter_AddRefs(newFocus));
+  if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
+    return NS_OK;
+  }
+
   NS_ENSURE_SUCCESS(rv, rv);
 
   LOGCONTENTNAVIGATION("Element to be focused: %s", newFocus.get());
 
   if (newFocus) {
     // for caret movement, pass false for the aFocusChanged argument,
     // otherwise the caret will end up moving to the focus position. This
     // would be a problem because the caret would move to the beginning of the
@@ -2417,19 +2424,29 @@ nsFocusManager::GetSelectionLocation(nsI
 nsresult
 nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindow* aWindow,
                                             nsIContent* aStartContent,
                                             int32_t aType, bool aNoParentTraversal,
                                             nsIContent** aNextContent)
 {
   *aNextContent = nullptr;
 
-  nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
-  if (!docShell)
-    return NS_OK;
+  // True if we are navigating by document (F6/Shift+F6) or false if we are
+  // navigating by element (Tab/Shift+Tab).
+  bool forDocumentNavigation = false;
+
+  // This is used for document navigation only. It will be set to true if we
+  // start navigating from a starting point. If this starting point is near the
+  // end of the document (for example, an element on a statusbar), and there
+  // are no child documents or panels before the end of the document, then we
+  // will need to ensure that we don't consider the root chrome window when we
+  // loop around and instead find the next child document/panel, as focus is
+  // already in that window. This flag will be cleared once we navigate into
+  // another document.
+  bool mayFocusRoot = (aStartContent != nullptr);
 
   nsCOMPtr<nsIContent> startContent = aStartContent;
   if (!startContent && aType != MOVEFOCUS_CARET) {
     if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC) {
       // When moving between documents, make sure to get the right
       // starting content in a descendant.
       nsCOMPtr<nsPIDOMWindow> focusedWindow;
       startContent = GetFocusedDescendant(aWindow, true, getter_AddRefs(focusedWindow));
@@ -2445,51 +2462,58 @@ nsFocusManager::DetermineElementToMoveFo
   else
     doc = aWindow->GetExtantDoc();
   if (!doc)
     return NS_OK;
 
   LookAndFeel::GetInt(LookAndFeel::eIntID_TabFocusModel,
                       &nsIContent::sTabFocusModel);
 
-  if (aType == MOVEFOCUS_ROOT) {
-    NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false));
-    return NS_OK;
+  // These types are for document navigation using F6.
+  if (aType == MOVEFOCUS_FORWARDDOC || aType == MOVEFOCUS_BACKWARDDOC ||
+      aType == MOVEFOCUS_FIRSTDOC || aType == MOVEFOCUS_LASTDOC) {
+    forDocumentNavigation = true;
   }
-  if (aType == MOVEFOCUS_FORWARDDOC) {
-    NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(startContent, true));
-    return NS_OK;
+
+  // If moving to the root or first document, find the root element and return.
+  if (aType == MOVEFOCUS_ROOT || aType == MOVEFOCUS_FIRSTDOC) {
+    NS_IF_ADDREF(*aNextContent = GetRootForFocus(aWindow, doc, false, false));
+    if (!*aNextContent && aType == MOVEFOCUS_FIRSTDOC) {
+      // When looking for the first document, if the root wasn't focusable,
+      // find the next focusable document.
+      aType = MOVEFOCUS_FORWARDDOC;
+    } else {
+      return NS_OK;
+    }
   }
-  if (aType == MOVEFOCUS_BACKWARDDOC) {
-    NS_IF_ADDREF(*aNextContent = GetNextTabbableDocument(startContent, false));
-    return NS_OK;
-  }
-  
+
   nsIContent* rootContent = doc->GetRootElement();
   NS_ENSURE_TRUE(rootContent, NS_OK);
 
   nsIPresShell *presShell = doc->GetShell();
   NS_ENSURE_TRUE(presShell, NS_OK);
 
   if (aType == MOVEFOCUS_FIRST) {
     if (!aStartContent)
       startContent = rootContent;
     return GetNextTabbableContent(presShell, startContent,
                                   nullptr, startContent,
-                                  true, 1, false, aNextContent);
+                                  true, 1, false, false, aNextContent);
   }
   if (aType == MOVEFOCUS_LAST) {
     if (!aStartContent)
       startContent = rootContent;
     return GetNextTabbableContent(presShell, startContent,
                                   nullptr, startContent,
-                                  false, 0, false, aNextContent);
+                                  false, 0, false, false, aNextContent);
   }
 
-  bool forward = (aType == MOVEFOCUS_FORWARD || aType == MOVEFOCUS_CARET);
+  bool forward = (aType == MOVEFOCUS_FORWARD ||
+                  aType == MOVEFOCUS_FORWARDDOC ||
+                  aType == MOVEFOCUS_CARET);
   bool doNavigation = true;
   bool ignoreTabIndex = false;
   // when a popup is open, we want to ensure that tab navigation occurs only
   // within the most recently opened panel. If a popup is open, its frame will
   // be stored in popupFrame.
   nsIFrame* popupFrame = nullptr;
 
   int32_t tabIndex = forward ? 1 : 0;
@@ -2514,17 +2538,17 @@ nsFocusManager::DetermineElementToMoveFo
     // check if the focus is currently inside a popup. Elements such as the
     // autocomplete widget use the noautofocus attribute to allow the focus to
     // remain outside the popup when it is opened.
     if (frame) {
       popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
                                                         nsGkAtoms::menuPopupFrame);
     }
 
-    if (popupFrame) {
+    if (popupFrame && !forDocumentNavigation) {
       // Don't navigate outside of a popup, so pretend that the
       // root content is the popup itself
       rootContent = popupFrame->GetContent();
       NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
     }
     else if (!forward) {
       // If focus moves backward and when current focused node is root
       // content or <body> element which is editable by contenteditable
@@ -2546,23 +2570,34 @@ nsFocusManager::DetermineElementToMoveFo
       // if there is no focus, yet a panel is open, focus the first item in
       // the panel
       nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
       if (pm)
         popupFrame = pm->GetTopPopup(ePopupTypePanel);
     }
 #endif
     if (popupFrame) {
-      rootContent = popupFrame->GetContent();
-      NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
-      startContent = rootContent;
+      // When there is a popup open, and no starting content, start the search
+      // at the topmost popup.
+      startContent = popupFrame->GetContent();
+      NS_ASSERTION(startContent, "Popup frame doesn't have a content node");
+      // Unless we are searching for documents, set the root content to the
+      // popup as well, so that we don't tab-navigate outside the popup.
+      // When navigating by documents, we start at the popup but can navigate
+      // outside of it to look for other panels and documents.
+      if (!forDocumentNavigation) {
+        rootContent = startContent;
+      }
+
+      doc = startContent ? startContent->GetComposedDoc() : nullptr;
     }
     else {
       // Otherwise, for content shells, start from the location of the caret.
-      if (docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
+      nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+      if (docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
         nsCOMPtr<nsIContent> endSelectionContent;
         GetSelectionLocation(doc, presShell,
                              getter_AddRefs(startContent),
                              getter_AddRefs(endSelectionContent));
         // If the selection is on the rootContent, then there is no selection
         if (startContent == rootContent) {
           startContent = nullptr;
         }
@@ -2606,133 +2641,153 @@ nsFocusManager::DetermineElementToMoveFo
   // forward. Without skipOriginalContentCheck set to true, we'd end up
   // returning right away and focusing nothing. Luckily, GetNextTabbableContent
   // will never wrap around on its own, and can only return the original
   // content when it is called a second time or later.
   bool skipOriginalContentCheck = true;
   nsIContent* originalStartContent = startContent;
 
   LOGCONTENTNAVIGATION("Focus Navigation Start Content %s", startContent.get());
-  LOGFOCUSNAVIGATION(("  Tabindex: %d Ignore: %d", tabIndex, ignoreTabIndex));
+  LOGFOCUSNAVIGATION(("  Forward: %d Tabindex: %d Ignore: %d DocNav: %d",
+                      forward, tabIndex, ignoreTabIndex, forDocumentNavigation));
 
   while (doc) {
     if (doNavigation) {
       nsCOMPtr<nsIContent> nextFocus;
       nsresult rv = GetNextTabbableContent(presShell, rootContent,
                                            skipOriginalContentCheck ? nullptr : originalStartContent,
                                            startContent, forward,
                                            tabIndex, ignoreTabIndex,
+                                           forDocumentNavigation,
                                            getter_AddRefs(nextFocus));
       NS_ENSURE_SUCCESS(rv, rv);
+      if (rv == NS_SUCCESS_DOM_NO_OPERATION) {
+        // Navigation was redirected to a child process, so just return.
+        return NS_OK;
+      }
 
       // found a content node to focus.
       if (nextFocus) {
         LOGCONTENTNAVIGATION("Next Content: %s", nextFocus.get());
 
         // as long as the found node was not the same as the starting node,
-        // set it as the return value.
-        if (nextFocus != originalStartContent) {
+        // set it as the return value. For document navigation, we can return
+        // the same element in case there is only one content node that could
+        // be returned, for example, in a child process document. 
+        if (nextFocus != originalStartContent || forDocumentNavigation) {
           nextFocus.forget(aNextContent);
         }
         return NS_OK;
       }
 
-      if (popupFrame) {
+      if (popupFrame && !forDocumentNavigation) {
         // in a popup, so start again from the beginning of the popup. However,
         // if we already started at the beginning, then there isn't anything to
         // focus, so just return
         if (startContent != rootContent) {
           startContent = rootContent;
           tabIndex = forward ? 1 : 0;
           continue;
         }
         return NS_OK;
       }
     }
 
     doNavigation = true;
-    skipOriginalContentCheck = false;
+    skipOriginalContentCheck = forDocumentNavigation;
     ignoreTabIndex = false;
 
     if (aNoParentTraversal) {
       if (startContent == rootContent)
         return NS_OK;
 
       startContent = rootContent;
       tabIndex = forward ? 1 : 0;
       continue;
     }
 
-    // reached the beginning or end of the document. Traverse up to the parent
-    // document and try again.
-    nsCOMPtr<nsIDocShellTreeItem> docShellParent;
-    docShell->GetParent(getter_AddRefs(docShellParent));
-    if (docShellParent) {
-      // move up to the parent shell and try again from there.
-
-      // first, get the frame element this window is inside.
-      nsCOMPtr<nsPIDOMWindow> piWindow = docShell->GetWindow();
-      NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
-
-      // Next, retrieve the parent docshell, document and presshell.
-      docShell = do_QueryInterface(docShellParent);
-      NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
-
-      nsCOMPtr<nsPIDOMWindow> piParentWindow = docShellParent->GetWindow();
-      NS_ENSURE_TRUE(piParentWindow, NS_ERROR_FAILURE);
-      doc = piParentWindow->GetExtantDoc();
+    // Reached the beginning or end of the document. Next, navigate up to the
+    // parent document and try again.
+    nsCOMPtr<nsPIDOMWindow> piWindow = doc->GetWindow();
+    NS_ENSURE_TRUE(piWindow, NS_ERROR_FAILURE);
+
+    nsCOMPtr<nsIDocShell> docShell = piWindow->GetDocShell();
+    NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+    // Get the frame element this window is inside and, from that, get the
+    // parent document and presshell. If there is no enclosing frame element,
+    // then this is a top-level, embedded or remote window.
+    startContent = piWindow->GetFrameElementInternal();
+    if (startContent) {
+      doc = startContent->GetComposedDoc();
       NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
 
-      presShell = doc->GetShell();
-
       rootContent = doc->GetRootElement();
-      startContent = piWindow->GetFrameElementInternal();
-      if (startContent) {
-        nsIFrame* frame = startContent->GetPrimaryFrame();
-        if (!frame)
-          return NS_OK;
-
-        frame->IsFocusable(&tabIndex, 0);
-        if (tabIndex < 0) {
-          tabIndex = 1;
-          ignoreTabIndex = true;
-        }
-
-        // if the frame is inside a popup, make sure to scan only within the
-        // popup. This handles the situation of tabbing amongst elements
-        // inside an iframe which is itself inside a popup. Otherwise,
-        // navigation would move outside the popup when tabbing outside the
-        // iframe.
+      presShell = doc->GetShell();
+
+      // We can focus the root element now that we have moved to another document.
+      mayFocusRoot = true;
+
+      nsIFrame* frame = startContent->GetPrimaryFrame();
+      if (!frame) {
+        return NS_OK;
+      }
+
+      frame->IsFocusable(&tabIndex, 0);
+      if (tabIndex < 0) {
+        tabIndex = 1;
+        ignoreTabIndex = true;
+      }
+
+      // if the frame is inside a popup, make sure to scan only within the
+      // popup. This handles the situation of tabbing amongst elements
+      // inside an iframe which is itself inside a popup. Otherwise,
+      // navigation would move outside the popup when tabbing outside the
+      // iframe.
+      if (!forDocumentNavigation) {
         popupFrame = nsLayoutUtils::GetClosestFrameOfType(frame,
                                                           nsGkAtoms::menuPopupFrame);
         if (popupFrame) {
           rootContent = popupFrame->GetContent();
           NS_ASSERTION(rootContent, "Popup frame doesn't have a content node");
         }
       }
-      else {
-        startContent = rootContent;
-        tabIndex = forward ? 1 : 0;
-      }
     }
     else {
-      // no parent, so call the tree owner. This will tell the embedder that
-      // it should take the focus.
+      // There is no parent, so call the tree owner. This will tell the
+      // embedder or parent process that it should take the focus.
       bool tookFocus;
-      docShell->TabToTreeOwner(forward, &tookFocus);
-      // if the tree owner, took the focus, blur the current content
+      docShell->TabToTreeOwner(forward, forDocumentNavigation, &tookFocus);
+      // If the tree owner took the focus, blur the current content.
       if (tookFocus) {
         nsCOMPtr<nsPIDOMWindow> window = docShell->GetWindow();
         if (window->GetFocusedNode() == mFocusedContent)
           Blur(mFocusedWindow, nullptr, true, true);
         else
           window->SetFocusedNode(nullptr);
         return NS_OK;
       }
 
+      // If we have reached the end of the top-level document, focus the
+      // first element in the top-level document. This should always happen
+      // when navigating by document forwards but when navigating backwards,
+      // only do this if we started in another document or within a popup frame.
+      // If the focus started in this window outside a popup however, we should
+      // continue by looping around to the end again.
+      if (forDocumentNavigation && (forward || mayFocusRoot || popupFrame)) {
+        // HTML content documents can have their root element focused (a focus
+        // ring appears around the entire content area frame). This root
+        // appears in the tab order before all of the elements in the document.
+        // Chrome documents however cannot be focused directly, so instead we
+        // focus the first focusable element within the window.
+        // For example, the urlbar.
+        nsIContent* root = GetRootForFocus(piWindow, doc, true, true);
+        return FocusFirst(root, aNextContent);
+      }
+
       // reset the tab index and start again from the beginning or end
       startContent = rootContent;
       tabIndex = forward ? 1 : 0;
     }
 
     // wrapped all the way around and didn't find anything to move the focus
     // to, so just break out
     if (startContent == originalStartContent)
@@ -2745,16 +2800,17 @@ nsFocusManager::DetermineElementToMoveFo
 nsresult
 nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell,
                                        nsIContent* aRootContent,
                                        nsIContent* aOriginalStartContent,
                                        nsIContent* aStartContent,
                                        bool aForward,
                                        int32_t aCurrentTabIndex,
                                        bool aIgnoreTabIndex,
+                                       bool aForDocumentNavigation,
                                        nsIContent** aResultContent)
 {
   *aResultContent = nullptr;
 
   nsCOMPtr<nsIContent> startContent = aStartContent;
   if (!startContent)
     return NS_OK;
 
@@ -2781,24 +2837,27 @@ nsFocusManager::GetNextTabbableContent(n
       if (iterStartContent)
         continue;
 
       // otherwise, as a last attempt, just look at the root content
       iterStartContent = aRootContent;
       continue;
     }
 
+    // For tab navigation, pass false for aSkipPopupChecks so that we don't
+    // iterate into or out of a popup. For document naviation pass true to
+    // ignore these boundaries.
     nsCOMPtr<nsIFrameEnumerator> frameTraversal;
     nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal),
                                        presContext, startFrame,
                                        ePreOrder,
                                        false, // aVisual
                                        false, // aLockInScrollView
                                        true,  // aFollowOOFs
-                                       false  // aSkipPopupChecks
+                                       aForDocumentNavigation  // aSkipPopupChecks
                                        );
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (iterStartContent == aRootContent) {
       if (!aForward) {
         frameTraversal->Last();
       } else if (aRootContent->IsFocusable()) {
         frameTraversal->Next();
@@ -2813,34 +2872,78 @@ nsFocusManager::GetNextTabbableContent(n
         frameTraversal->Next();
       else
         frameTraversal->Prev();
     }
 
     // Walk frames to find something tabbable matching mCurrentTabIndex
     nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
     while (frame) {
+      nsIContent* currentContent = frame->GetContent();
+
+      // For document navigation, check if this element is an open panel. Since
+      // panels aren't focusable (tabIndex would be -1), we'll just assume that
+      // for document navigation, the tabIndex is 0.
+      if (aForDocumentNavigation && currentContent && (aCurrentTabIndex == 0) &&
+          currentContent->IsXULElement(nsGkAtoms::panel)) {
+        nsMenuPopupFrame* popupFrame = do_QueryFrame(frame);
+        // Check if the panel is open. Closed panels are ignored since you can't
+        // focus anything in them.
+        if (popupFrame && popupFrame->IsOpen()) {
+          // When moving backward, skip the popup we started in otherwise it
+          // will be selected again.
+          bool validPopup = true;
+          if (!aForward) {
+            nsIContent* content = aStartContent;
+            while (content) {
+              if (content == currentContent) {
+                validPopup = false;
+                break;
+              }
+
+              content = content->GetParent();
+            }
+          }
+
+          if (validPopup) {
+            // Since a panel isn't focusable itself, find the first focusable
+            // content within the popup. If there isn't any focusable content
+            // in the popup, skip this popup and continue iterating through the
+            // frames. We pass the panel itself (currentContent) as the starting
+            // and root content, so that we only find content within the panel.
+            // Note also that we pass false for aForDocumentNavigation since we
+            // want to locate the first content, not the first document.
+            rv = GetNextTabbableContent(aPresShell, currentContent,
+                                        nullptr, currentContent,
+                                        true, 1, false, false,
+                                        aResultContent);
+            if (NS_SUCCEEDED(rv) && *aResultContent) {
+              return rv;
+            }
+          }
+        }
+      }
+
       // TabIndex not set defaults to 0 for form elements, anchors and other
       // elements that are normally focusable. Tabindex defaults to -1
       // for elements that are not normally focusable.
       // The returned computed tabindex from IsFocusable() is as follows:
       //          < 0 not tabbable at all
       //          == 0 in normal tab order (last after positive tabindexed items)
       //          > 0 can be tabbed to in the order specified by this value
-
       int32_t tabIndex;
       frame->IsFocusable(&tabIndex, 0);
 
       LOGCONTENTNAVIGATION("Next Tabbable %s:", frame->GetContent());
       LOGFOCUSNAVIGATION(("  with tabindex: %d expected: %d", tabIndex, aCurrentTabIndex));
 
-      nsIContent* currentContent = frame->GetContent();
       if (tabIndex >= 0) {
         NS_ASSERTION(currentContent, "IsFocusable set a tabindex for a frame with no content");
-        if (currentContent->IsHTMLElement(nsGkAtoms::img) &&
+        if (!aForDocumentNavigation &&
+            currentContent->IsHTMLElement(nsGkAtoms::img) &&
             currentContent->HasAttr(kNameSpaceID_None, nsGkAtoms::usemap)) {
           // This is an image with a map. Image map areas are not traversed by
           // nsIFrameTraversal so look for the next or previous area element.
           nsIContent *areaContent =
             GetNextTabbableMapArea(aForward, aCurrentTabIndex,
                                    currentContent, iterStartContent);
           if (areaContent) {
             NS_ADDREF(*aResultContent = areaContent);
@@ -2849,74 +2952,95 @@ nsFocusManager::GetNextTabbableContent(n
         }
         else if (aIgnoreTabIndex || aCurrentTabIndex == tabIndex) {
           // break out if we've wrapped around to the start again.
           if (aOriginalStartContent && currentContent == aOriginalStartContent) {
             NS_ADDREF(*aResultContent = currentContent);
             return NS_OK;
           }
 
-          // found a node with a matching tab index. Check if it is a child
-          // frame. If so, navigate into the child frame instead.
-          nsIDocument* doc = currentContent->GetComposedDoc();
-          NS_ASSERTION(doc, "content not in document");
-          nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
-          if (subdoc) {
-            if (!subdoc->EventHandlingSuppressed()) {
+          bool checkSubDocument = true;
+          if (aForDocumentNavigation) {
+            // If this is a remote child browser, call NavigateDocument to have
+            // the child process continue the navigation. Return a special error
+            // code to have the caller return early. If the child ends up not
+            // being focusable in some way, the child process will call back
+            // into document navigation again by calling MoveFocus.
+            TabParent* remote = TabParent::GetFrom(currentContent);
+            if (remote) {
+              remote->NavigateDocument(aForward);
+              return NS_SUCCESS_DOM_NO_OPERATION;
+            }
+
+            // Next, check if this a non-remote child document.
+            nsIContent* docRoot = GetRootForChildDocument(currentContent);
+            if (docRoot) {
+              // If GetRootForChildDocument returned something then call
+              // FocusFirst to find the root or first element to focus within
+              // the child document. If this is a frameset though, skip this and
+              // fall through to the checkSubDocument block below to iterate into
+              // the frameset's frames and locate the first focusable frame.
+              if (!docRoot->IsHTMLElement(nsGkAtoms::frameset)) {
+                return FocusFirst(docRoot, aResultContent);
+              }
+            } else {
+              // Set checkSubDocument to false, as this was neither a frame
+              // type element or a child document that was focusable.
+              checkSubDocument = false;
+            }
+          }
+
+          if (checkSubDocument) {
+            // found a node with a matching tab index. Check if it is a child
+            // frame. If so, navigate into the child frame instead.
+            nsIDocument* doc = currentContent->GetComposedDoc();
+            NS_ASSERTION(doc, "content not in document");
+            nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent);
+            if (subdoc && !subdoc->EventHandlingSuppressed()) {
               if (aForward) {
                 // when tabbing forward into a frame, return the root
                 // frame so that the canvas becomes focused.
                 nsCOMPtr<nsPIDOMWindow> subframe = subdoc->GetWindow();
                 if (subframe) {
-                  // If the subframe body is editable by contenteditable,
-                  // we should set the editor's root element rather than the
-                  // actual root element.  Otherwise, we should set the focus
-                  // to the root content.
-                  *aResultContent =
-                    nsLayoutUtils::GetEditableRootContentByContentEditable(subdoc);
-                  if (!*aResultContent ||
-                      !((*aResultContent)->GetPrimaryFrame())) {
-                    *aResultContent =
-                      GetRootForFocus(subframe, subdoc, false, true);
-                  }
+                  *aResultContent = GetRootForFocus(subframe, subdoc, false, true);
                   if (*aResultContent) {
                     NS_ADDREF(*aResultContent);
                     return NS_OK;
                   }
                 }
               }
               Element* rootElement = subdoc->GetRootElement();
               nsIPresShell* subShell = subdoc->GetShell();
               if (rootElement && subShell) {
                 rv = GetNextTabbableContent(subShell, rootElement,
                                             aOriginalStartContent, rootElement,
                                             aForward, (aForward ? 1 : 0),
-                                            false, aResultContent);
+                                            false, aForDocumentNavigation, aResultContent);
                 NS_ENSURE_SUCCESS(rv, rv);
                 if (*aResultContent)
                   return NS_OK;
               }
             }
-          }
-          // otherwise, use this as the next content node to tab to, unless
-          // this was the element we started on. This would happen for
-          // instance on an element with child frames, where frame navigation
-          // could return the original element again. In that case, just skip
-          // it. Also, if the next content node is the root content, then
-          // return it. This latter case would happen only if someone made a
-          // popup focusable.
-          // Also, when going backwards, check to ensure that the focus
-          // wouldn't be redirected. Otherwise, for example, when an input in
-          // a textbox is focused, the enclosing textbox would be found and
-          // the same inner input would be returned again.
-          else if (currentContent == aRootContent ||
-                   (currentContent != startContent &&
-                    (aForward || !GetRedirectedFocus(currentContent)))) {
-            NS_ADDREF(*aResultContent = currentContent);
-            return NS_OK;
+            // otherwise, use this as the next content node to tab to, unless
+            // this was the element we started on. This would happen for
+            // instance on an element with child frames, where frame navigation
+            // could return the original element again. In that case, just skip
+            // it. Also, if the next content node is the root content, then
+            // return it. This latter case would happen only if someone made a
+            // popup focusable.
+            // Also, when going backwards, check to ensure that the focus
+            // wouldn't be redirected. Otherwise, for example, when an input in
+            // a textbox is focused, the enclosing textbox would be found and
+            // the same inner input would be returned again.
+            else if (currentContent == aRootContent ||
+                     (currentContent != startContent &&
+                      (aForward || !GetRedirectedFocus(currentContent)))) {
+              NS_ADDREF(*aResultContent = currentContent);
+              return NS_OK;
+            }
           }
         }
       }
       else if (aOriginalStartContent && currentContent == aOriginalStartContent) {
         // not focusable, so return if we have wrapped around to the original
         // content. This is necessary in case the original starting content was
         // not focusable.
         NS_ADDREF(*aResultContent = currentContent);
@@ -2939,23 +3063,24 @@ nsFocusManager::GetNextTabbableContent(n
         frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem());
       } while (frame && frame->GetPrevContinuation());
     }
 
     // If already at lowest priority tab (0), end search completely.
     // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0
     if (aCurrentTabIndex == (aForward ? 0 : 1)) {
       // if going backwards, the canvas should be focused once the beginning
-      // has been reached.
+      // has been reached, so get the root element.
       if (!aForward) {
         nsCOMPtr<nsPIDOMWindow> window = GetCurrentWindow(aRootContent);
         NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
-        NS_IF_ADDREF(*aResultContent =
-                     GetRootForFocus(window, aRootContent->GetComposedDoc(),
-                                     false, true));
+
+        nsCOMPtr<nsIContent> docRoot =
+          GetRootForFocus(window, aRootContent->GetComposedDoc(), false, true);
+        FocusFirst(docRoot, aResultContent);
       }
       break;
     }
 
     // continue looking for next highest priority tabindex
     aCurrentTabIndex = GetNextTabIndex(aRootContent, aCurrentTabIndex, aForward);
     startContent = iterStartContent = aRootContent;
   }
@@ -3051,341 +3176,114 @@ nsFocusManager::GetNextTabIndex(nsIConte
         }
       }
     }
   }
 
   return tabIndex;
 }
 
+nsresult
+nsFocusManager::FocusFirst(nsIContent* aRootContent, nsIContent** aNextContent)
+{
+  if (!aRootContent) {
+    return NS_OK;
+  }
+
+  nsIDocument* doc = aRootContent->GetComposedDoc();
+  if (doc) {
+    nsCOMPtr<nsIDocShell> docShell = doc->GetDocShell();
+    if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
+      // If the found content is in a chrome shell, navigate forward one
+      // tabbable item so that the first item is focused. Note that we
+      // always go forward and not back here.
+      nsIPresShell* presShell = doc->GetShell();
+      if (presShell) {
+        return GetNextTabbableContent(presShell, aRootContent,
+                                      nullptr, aRootContent,
+                                      true, 1, false, false,
+                                      aNextContent);
+      }
+    }
+  }
+
+  NS_ADDREF(*aNextContent = aRootContent);
+  return NS_OK;
+}
+
 nsIContent*
 nsFocusManager::GetRootForFocus(nsPIDOMWindow* aWindow,
                                 nsIDocument* aDocument,
-                                bool aIsForDocNavigation,
+                                bool aForDocumentNavigation,
                                 bool aCheckVisibility)
 {
-  // the root element's canvas may be focused as long as the document is in a
-  // a non-chrome shell and does not contain a frameset.
-  if (aIsForDocNavigation) {
-    nsCOMPtr<Element> docElement = aWindow->GetFrameElementInternal();
-    // document navigation skips iframes and frames that are specifically non-focusable
-    if (docElement) {
-      if (docElement->NodeInfo()->NameAtom() == nsGkAtoms::iframe)
-        return nullptr;
-
-      nsIFrame* frame = docElement->GetPrimaryFrame();
-      if (!frame || !frame->IsFocusable(nullptr, 0))
-        return nullptr;
-    }
-  } else {
+  if (!aForDocumentNavigation) {
     nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
     if (docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
       return nullptr;
     }
   }
 
   if (aCheckVisibility && !IsWindowVisible(aWindow))
     return nullptr;
 
-  Element *rootElement = aDocument->GetRootElement();
-  if (!rootElement) {
-    return nullptr;
+  // If the body is contenteditable, use the editor's root element rather than
+  // the actual root element.
+  nsCOMPtr<nsIContent> rootElement =
+    nsLayoutUtils::GetEditableRootContentByContentEditable(aDocument);
+  if (!rootElement || !rootElement->GetPrimaryFrame()) {
+    rootElement = aDocument->GetRootElement();
+    if (!rootElement) {
+      return nullptr;
+    }
   }
 
   if (aCheckVisibility && !rootElement->GetPrimaryFrame()) {
     return nullptr;
   }
 
   // Finally, check if this is a frameset
   nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(aDocument);
-  if (htmlDoc && aDocument->GetHtmlChildElement(nsGkAtoms::frameset)) {
-    return nullptr;
+  if (htmlDoc) {
+    nsIContent* htmlChild = aDocument->GetHtmlChildElement(nsGkAtoms::frameset);
+    if (htmlChild) {
+      // In document navigation mode, return the frameset so that navigation
+      // descends into the child frames.
+      return aForDocumentNavigation ? htmlChild : nullptr;
+    }
   }
 
   return rootElement;
 }
 
-void
-nsFocusManager::GetLastDocShell(nsIDocShellTreeItem* aItem,
-                                nsIDocShellTreeItem** aResult)
-{
-  *aResult = nullptr;
-
-  nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
-  while (curItem) {
-    int32_t childCount = 0;
-    curItem->GetChildCount(&childCount);
-    if (!childCount) {
-      curItem.forget(aResult);
-      return;
-    }
-
-    
-    curItem->GetChildAt(childCount - 1, getter_AddRefs(curItem));
-  }
-}
-
-void
-nsFocusManager::GetNextDocShell(nsIDocShellTreeItem* aItem,
-                                nsIDocShellTreeItem** aResult)
-{
-  *aResult = nullptr;
-
-  int32_t childCount = 0;
-  aItem->GetChildCount(&childCount);
-  if (childCount) {
-    aItem->GetChildAt(0, aResult);
-    if (*aResult)
-      return;
-  }
-
-  nsCOMPtr<nsIDocShellTreeItem> curItem = aItem;
-  while (curItem) {
-    nsCOMPtr<nsIDocShellTreeItem> parentItem;
-    curItem->GetParent(getter_AddRefs(parentItem));
-    if (!parentItem)
-      return;
-
-    // Note that we avoid using GetChildOffset() here because docshell
-    // child offsets can't be trusted to be correct. bug 162283.
-    nsCOMPtr<nsIDocShellTreeItem> iterItem;
-    childCount = 0;
-    parentItem->GetChildCount(&childCount);
-    for (int32_t index = 0; index < childCount; ++index) {
-      parentItem->GetChildAt(index, getter_AddRefs(iterItem));
-      if (iterItem == curItem) {
-        ++index;
-        if (index < childCount) {
-          parentItem->GetChildAt(index, aResult);
-          if (*aResult)
-            return;
-        }
-        break;
-      }
-    }
-
-    curItem = parentItem;
-  }
-}
-
-void
-nsFocusManager::GetPreviousDocShell(nsIDocShellTreeItem* aItem,
-                                    nsIDocShellTreeItem** aResult)
+nsIContent*
+nsFocusManager::GetRootForChildDocument(nsIContent* aContent)
 {
-  *aResult = nullptr;
-
-  nsCOMPtr<nsIDocShellTreeItem> parentItem;
-  aItem->GetParent(getter_AddRefs(parentItem));
-  if (!parentItem)
-    return;
-
-  // Note that we avoid using GetChildOffset() here because docshell
-  // child offsets can't be trusted to be correct. bug 162283.
-  int32_t childCount = 0;
-  parentItem->GetChildCount(&childCount);
-  nsCOMPtr<nsIDocShellTreeItem> prevItem, iterItem;
-  for (int32_t index = 0; index < childCount; ++index) {
-    parentItem->GetChildAt(index, getter_AddRefs(iterItem));
-    if (iterItem == aItem)
-      break;
-    prevItem = iterItem;
-  }
-
-  if (prevItem)
-    GetLastDocShell(prevItem, aResult);
-  else
-    parentItem.forget(aResult);
-}
-
-nsIContent*
-nsFocusManager::GetNextTabbablePanel(nsIDocument* aDocument, nsIFrame* aCurrentPopup, bool aForward)
-{
-  nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
-  if (!pm)
+  // Check for elements that represent child documents, that is, browsers,
+  // editors or frames from a frameset. We don't include iframes since we
+  // consider them to be an integral part of the same window or page.
+  if (!aContent ||
+      !(aContent->IsXULElement(nsGkAtoms::browser) ||
+        aContent->IsXULElement(nsGkAtoms::editor) ||
+        aContent->IsHTMLElement(nsGkAtoms::frame))) {
     return nullptr;
-
-  // Iterate through the array backwards if aForward is false.
-  nsTArray<nsIFrame *> popups;
-  pm->GetVisiblePopups(popups);
-  int32_t i = aForward ? 0 : popups.Length() - 1;
-  int32_t end = aForward ? popups.Length() : -1;
-
-  for (; i != end; aForward ? i++ : i--) {
-    nsIFrame* popupFrame = popups[i];
-    if (aCurrentPopup) {
-      // If the current popup is set, then we need to skip over this popup and
-      // wait until the currently focused popup is found. Once found, the
-      // current popup will be cleared so that the next popup is used.
-      if (aCurrentPopup == popupFrame)
-        aCurrentPopup = nullptr;
-      continue;
-    }
-
-    // Skip over non-panels
-    if (!popupFrame->GetContent()->IsXULElement(nsGkAtoms::panel) ||
-        (aDocument && popupFrame->GetContent()->GetComposedDoc() != aDocument)) {
-      continue;
-    }
-
-    // Find the first focusable content within the popup. If there isn't any
-    // focusable content in the popup, skip to the next popup.
-    nsIPresShell* presShell = popupFrame->PresContext()->GetPresShell();
-    if (presShell) {
-      nsCOMPtr<nsIContent> nextFocus;
-      nsIContent* popup = popupFrame->GetContent();
-      nsresult rv = GetNextTabbableContent(presShell, popup,
-                                           nullptr, popup,
-                                           true, 1, false,
-                                           getter_AddRefs(nextFocus));
-      if (NS_SUCCEEDED(rv) && nextFocus) {
-        return nextFocus.get();
-      }
-    }
   }
 
-  return nullptr;
-}
-
-nsIContent*
-nsFocusManager::GetNextTabbableDocument(nsIContent* aStartContent, bool aForward)
-{
-  // If currentPopup is set, then the starting content is in a panel.
-  nsIFrame* currentPopup = nullptr;
-  nsCOMPtr<nsIDocument> doc;
-  nsCOMPtr<nsIDocShell> startDocShell;
-
-  if (aStartContent) {
-    doc = aStartContent->GetComposedDoc();
-    if (doc) {
-      startDocShell = doc->GetWindow()->GetDocShell();
-    }
-
-    // Check if the starting content is inside a panel. Document navigation
-    // must start from this panel instead of the document root.
-    nsIContent* content = aStartContent;
-    while (content) {
-      if (content->NodeInfo()->Equals(nsGkAtoms::panel, kNameSpaceID_XUL)) {
-        currentPopup = content->GetPrimaryFrame();
-        break;
-      }
-      content = content->GetParent();
-    }
-  }
-  else if (mFocusedWindow) {
-    startDocShell = mFocusedWindow->GetDocShell();
-    doc = mFocusedWindow->GetExtantDoc();
-  } else if (mActiveWindow) {
-    startDocShell = mActiveWindow->GetDocShell();
-    doc = mActiveWindow->GetExtantDoc();
-  }
-
-  if (!startDocShell)
+  nsIDocument* doc = aContent->GetComposedDoc();
+  if (!doc) {
     return nullptr;
-
-  // perform a depth first search (preorder) of the docshell tree
-  // looking for an HTML Frame or a chrome document
-  nsIContent* content = aStartContent;
-  nsCOMPtr<nsIDocShellTreeItem> curItem = startDocShell.get();
-  nsCOMPtr<nsIDocShellTreeItem> nextItem;
-  do {
-    // If moving forward, check for a panel in the starting document. If one
-    // exists with focusable content, return that content instead of the next
-    // document. If currentPopup is set, then, another panel may exist. If no
-    // such panel exists, then continue on to check the next document.
-    // When moving backwards, and the starting content is in a panel, then
-    // check for additional panels in the starting document. If the starting
-    // content is not in a panel, move back to the previous document and check
-    // for panels there.
-
-    bool checkPopups = false;
-    nsCOMPtr<nsPIDOMWindow> nextFrame = nullptr;
-
-    if (doc && (aForward || currentPopup)) {
-      nsIContent* popupContent = GetNextTabbablePanel(doc, currentPopup, aForward);
-      if (popupContent)
-        return popupContent;
-
-      if (!aForward && currentPopup) {
-        // The starting content was in a popup, yet no other popups were
-        // found. Move onto the starting content's document.
-        nextFrame = doc->GetWindow();
-      }
-    }
-
-    // Look for the next or previous document.
-    if (!nextFrame) {
-      if (aForward) {
-        GetNextDocShell(curItem, getter_AddRefs(nextItem));
-        if (!nextItem) {
-          // wrap around to the beginning, which is the top of the tree
-          startDocShell->GetRootTreeItem(getter_AddRefs(nextItem));
-        }
-      }
-      else {
-        GetPreviousDocShell(curItem, getter_AddRefs(nextItem));
-        if (!nextItem) {
-          // wrap around to the end, which is the last item in the tree
-          nsCOMPtr<nsIDocShellTreeItem> rootItem;
-          startDocShell->GetRootTreeItem(getter_AddRefs(rootItem));
-          GetLastDocShell(rootItem, getter_AddRefs(nextItem));
-        }
-
-        // When going back to the previous document, check for any focusable
-        // popups in that previous document first.
-        checkPopups = true;
-      }
-
-      curItem = nextItem;
-      nextFrame = nextItem ? nextItem->GetWindow() : nullptr;
-    }
-
-    if (!nextFrame)
-      return nullptr;
-
-    // Clear currentPopup for the next iteration
-    currentPopup = nullptr;
-
-    // If event handling is suppressed, move on to the next document. Set
-    // content to null so that the popup check will be skipped on the next
-    // loop iteration.
-    doc = nextFrame->GetExtantDoc();
-    if (!doc || doc->EventHandlingSuppressed()) {
-      content = nullptr;
-      continue;
-    }
-
-    if (checkPopups) {
-      // When iterating backwards, check the panels of the previous document
-      // first. If a panel exists that has focusable content, focus that.
-      // Otherwise, continue on to focus the document.
-      nsIContent* popupContent = GetNextTabbablePanel(doc, nullptr, false);
-      if (popupContent)
-        return popupContent;
-    }
-
-    content = GetRootForFocus(nextFrame, doc, true, true);
-    if (content && !GetRootForFocus(nextFrame, doc, false, false)) {
-      // if the found content is in a chrome shell or a frameset, navigate
-      // forward one tabbable item so that the first item is focused. Note
-      // that we always go forward and not back here.
-      nsCOMPtr<nsIContent> nextFocus;
-      Element* rootElement = doc->GetRootElement();
-      nsIPresShell* presShell = doc->GetShell();
-      if (presShell) {
-        nsresult rv = GetNextTabbableContent(presShell, rootElement,
-                                             nullptr, rootElement,
-                                             true, 1, false,
-                                             getter_AddRefs(nextFocus));
-        return NS_SUCCEEDED(rv) ? nextFocus.get() : nullptr;
-      }
-    }
-
-  } while (!content);
-
-  return content;
+  }
+
+  nsIDocument* subdoc = doc->GetSubDocumentFor(aContent);
+  if (!subdoc || subdoc->EventHandlingSuppressed()) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsPIDOMWindow> window = subdoc->GetWindow();
+  return GetRootForFocus(window, subdoc, true, true);
 }
 
 void
 nsFocusManager::GetFocusInSelection(nsPIDOMWindow* aWindow,
                                     nsIContent* aStartSelection,
                                     nsIContent* aEndSelection,
                                     nsIContent** aFocusedContent)
 {
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -20,16 +20,22 @@
 
 #define FOCUSMANAGER_CONTRACTID "@mozilla.org/focus-manager;1"
 
 class nsIContent;
 class nsIDocShellTreeItem;
 class nsPIDOMWindow;
 class nsIMessageBroadcaster;
 
+namespace mozilla {
+namespace dom {
+class TabParent;
+}
+}
+
 struct nsDelayedBlurOrFocusEvent;
 
 /**
  * The focus manager keeps track of where the focus is, that is, the node
  * which receives key events.
  */
 
 class nsFocusManager final : public nsIFocusManager,
@@ -382,16 +388,17 @@ protected:
    */
   nsresult GetNextTabbableContent(nsIPresShell* aPresShell,
                                   nsIContent* aRootContent,
                                   nsIContent* aOriginalStartContent,
                                   nsIContent* aStartContent,
                                   bool aForward,
                                   int32_t aCurrentTabIndex,
                                   bool aIgnoreTabIndex,
+                                  bool aForDocumentNavigation,
                                   nsIContent** aResultContent);
 
   /**
    * Get the next tabbable image map area and returns it.
    *
    * aForward should be true for forward navigation or false for backward
    * navigation.
    *
@@ -411,74 +418,41 @@ protected:
    * is true, or the previous tabindex value if aForward is false. aParent is
    * the node from which to start looking for tab indicies.
    */
   int32_t GetNextTabIndex(nsIContent* aParent,
                           int32_t aCurrentTabIndex,
                           bool aForward);
 
   /**
+   * Focus the first focusable content within the document with a root node of
+   * aRootContent. For content documents, this will be aRootContent itself, but
+   * for chrome documents, this will locate the next focusable content.
+   */
+  nsresult FocusFirst(nsIContent* aRootContent, nsIContent** aNextContent);
+
+  /**
    * Retrieves and returns the root node from aDocument to be focused. Will
    * return null if the root node cannot be focused. There are several reasons
    * for this:
    *
-   * - if aIsForDocNavigation is true, and aWindow is in an <iframe>.
-   * - if aIsForDocNavigation is false, and aWindow is a chrome shell.
+   * - if aForDocumentNavigation is false and aWindow is a chrome shell.
    * - if aCheckVisibility is true and the aWindow is not visible.
    * - if aDocument is a frameset document.
    */
   nsIContent* GetRootForFocus(nsPIDOMWindow* aWindow,
                               nsIDocument* aDocument,
-                              bool aIsForDocNavigation,
+                              bool aForDocumentNavigation,
                               bool aCheckVisibility);
 
   /**
-   * Get the last docshell child of aItem and return it in aResult.
-   */
-  void GetLastDocShell(nsIDocShellTreeItem* aItem,
-                       nsIDocShellTreeItem** aResult);
-
-  /**
-   * Get the next docshell child of aItem and return it in aResult.
-   */
-  void GetNextDocShell(nsIDocShellTreeItem* aItem,
-                       nsIDocShellTreeItem** aResult);
-
-  /**
-   * Get the previous docshell child of aItem and return it in aResult.
+   * Retrieves and returns the root node as with GetRootForFocus but only if
+   * aContent is a frame with a valid child document.
    */
-  void GetPreviousDocShell(nsIDocShellTreeItem* aItem,
-                           nsIDocShellTreeItem** aResult);
-
-  /**
-   * Determine the first panel with focusable content in document tab order
-   * from the given document. aForward indicates the direction to scan. If
-   * aCurrentPopup is set to a panel, the next or previous popup after
-   * aCurrentPopup after it is used. If aCurrentPopup is null, then the first
-   * or last popup is used. If a panel has no focusable content, it is skipped.
-   * Null is returned if no panel is open or no open panel contains a focusable
-   * element.
-   */
-  nsIContent* GetNextTabbablePanel(nsIDocument* aDocument, nsIFrame* aCurrentPopup, bool aForward);
-
-  /**
-   * Get the tabbable next document from aStartContent or, if null, the
-   * currently focused frame if aForward is true, or the previously tabbable
-   * document if aForward is false. If this document is a chrome or frameset
-   * document, returns the first focusable element within this document,
-   * otherwise, returns the root node of the document.
-   *
-   *
-   * Panels with focusable content are also placed in the cycling order, just
-   * after the document containing that panel.
-   *
-   * This method would be used for document navigation, which is typically
-   * invoked by pressing F6.
-   */
-  nsIContent* GetNextTabbableDocument(nsIContent* aStartContent, bool aForward);
+  nsIContent* GetRootForChildDocument(nsIContent* aContent);
 
   /**
    * Retreives a focusable element within the current selection of aWindow.
    * Currently, this only detects links.
    *  
    * This is used when MoveFocus is called with a type of MOVEFOCUS_CARET,
    * which is used, for example, to focus links as the caret is moved over
    * them.
--- a/dom/interfaces/base/nsIFocusManager.idl
+++ b/dom/interfaces/base/nsIFocusManager.idl
@@ -3,17 +3,17 @@
  * 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 "domstubs.idl"
 
 interface nsIDocument;
 interface nsIContent;
 
-[scriptable, uuid(c0716002-5602-4002-a0de-cc69b924b2c6)]
+[scriptable, uuid(2487F9CA-D05F-4BD1-8F43-5964E746C482)]
 /**
  * The focus manager deals with all focus related behaviour. Only one element
  * in the entire application may have the focus at a time; this element
  * receives any keyboard events. While there is only one application-wide
  * focused element, each nsIDOMWindow maintains a reference to the element
  * that would be focused if the window was active.
  *
  * If the window's reference is to a frame element (iframe, browser,
@@ -92,17 +92,18 @@ interface nsIFocusManager : nsISupports
    *
    * Specifying aStartElement and using MOVEFOCUS_LAST is not currently
    * implemented.
    *
    * If no element is found, and aType is either MOVEFOCUS_ROOT or
    * MOVEFOCUS_CARET, then the focus is cleared. If aType is any other value,
    * the focus is not changed.
    *
-   * Returns the element that was focused.
+   * Returns the element that was focused. The return value may be null if focus
+   * was moved into a child process.
    */
   nsIDOMElement moveFocus(in nsIDOMWindow aWindow, in nsIDOMElement aStartElement,
                           in unsigned long aType, in unsigned long aFlags);
 
   /**
    * Clears the focused element within aWindow. If the current focusedWindow
    * is a descendant of aWindow, sets the current focusedWindow to aWindow.
    *
@@ -210,16 +211,21 @@ interface nsIFocusManager : nsISupports
   const unsigned long MOVEFOCUS_LAST = 6;
   /** move focus to the root element in the document */
   const unsigned long MOVEFOCUS_ROOT = 7;
   /** move focus to a link at the position of the caret. This is a special value used to
    *  focus links as the caret moves over them in caret browsing mode.
    */
   const unsigned long MOVEFOCUS_CARET = 8;
 
+  /** move focus to the first focusable document */
+  const unsigned long MOVEFOCUS_FIRSTDOC = 9;
+  /** move focus to the last focusable document */
+  const unsigned long MOVEFOCUS_LASTDOC = 10;
+
   /**
    * Called when a window has been raised.
    */
   [noscript] void windowRaised(in nsIDOMWindow aWindow);
 
   /**
    * Called when a window has been lowered.
    */
--- a/dom/interfaces/base/nsITabParent.idl
+++ b/dom/interfaces/base/nsITabParent.idl
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 #include "domstubs.idl"
 
-[scriptable, uuid(4d4576eb-ecfe-47b9-93b8-4121518621ad)]
+[scriptable, uuid(A10D887D-7FC2-48A2-ABC6-A2027423860C)]
 interface nsITabParent : nsISupports
 {
   void injectTouchEvent(in AString aType,
                         [array, size_is(count)] in uint32_t aIdentifiers,
                         [array, size_is(count)] in int32_t aXs,
                         [array, size_is(count)] in int32_t aYs,
                         [array, size_is(count)] in uint32_t aRxs,
                         [array, size_is(count)] in uint32_t aRys,
@@ -22,10 +22,16 @@ interface nsITabParent : nsISupports
   void getChildProcessOffset(out int32_t aCssX, out int32_t aCssY);
 
   readonly attribute boolean useAsyncPanZoom;
 
   void setIsDocShellActive(in bool aIsActive);
 
   readonly attribute uint64_t tabId;
 
+  /**
+   * If aForward is true, navigate to the first focusable document.
+   * If aForward is false, navigate to the last focusable document.
+   */
+  void navigateDocument(in bool aForward);
+
   readonly attribute boolean hasContentOpener;
 };
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -145,19 +145,19 @@ parent:
      * of the native browser window we need to move native focus to the
      * browser. Otherwise the plugin window will never relinquish focus.
      */
     sync DispatchFocusToTopLevelWindow();
 
 parent:
     /**
      * When child sends this message, parent should move focus to
-     * the next or previous focusable element.
+     * the next or previous focusable element or document.
      */
-    MoveFocus(bool forward);
+    MoveFocus(bool forward, bool forDocumentNavigation);
 
     Event(RemoteDOMEvent aEvent);
 
     sync CreateWindow(PBrowser aNewTab,
                       uint32_t aChromeFlags,
                       bool aCalledFromJS,
                       bool aPositionSpecified,
                       bool aSizeSpecified,
@@ -699,16 +699,21 @@ child:
     SetUpdateHitRegion(bool aEnabled);
 
     /**
      * Tell the child to update its docShell's active state.
      */
     SetIsDocShellActive(bool aIsActive);
 
     /**
+     * Navigate by document.
+     */
+    NavigateDocument(bool aForward);
+
+    /**
      * The parent (chrome thread) requests that the child inform it when
      * the graphics objects are ready to display.
      * @see PCompositor
      * @see RemotePaintIsReady
      */
     async RequestNotifyAfterRemotePaint();
 
     /**
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1431,26 +1431,26 @@ NS_IMETHODIMP
 TabChild::Blur()
 {
   NS_WARNING("TabChild::Blur not supported in TabChild");
 
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
-TabChild::FocusNextElement()
+TabChild::FocusNextElement(bool aForDocumentNavigation)
 {
-  SendMoveFocus(true);
+  SendMoveFocus(true, aForDocumentNavigation);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TabChild::FocusPrevElement()
+TabChild::FocusPrevElement(bool aForDocumentNavigation)
 {
-  SendMoveFocus(false);
+  SendMoveFocus(false, aForDocumentNavigation);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabChild::GetInterface(const nsIID & aIID, void **aSink)
 {
     if (aIID.Equals(NS_GET_IID(nsIWebBrowserChrome3))) {
       NS_IF_ADDREF(((nsISupports *) (*aSink = mWebBrowserChrome)));
@@ -2935,16 +2935,34 @@ TabChild::RecvSetIsDocShellActive(const 
 {
     nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
     if (docShell) {
       docShell->SetIsActive(aIsActive);
     }
     return true;
 }
 
+bool
+TabChild::RecvNavigateDocument(const bool& aForward)
+{
+  nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+  if (fm) {
+    nsCOMPtr<nsIDOMElement> result;
+    nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(WebNavigation());
+
+    // Move to the first or last document.
+    fm->MoveFocus(window, nullptr, aForward ? nsIFocusManager::MOVEFOCUS_FIRSTDOC :
+                                              nsIFocusManager::MOVEFOCUS_LASTDOC,
+                  nsIFocusManager::FLAG_BYKEY, getter_AddRefs(result));
+    SendRequestFocus(false);
+  }
+
+  return true;
+}
+
 PRenderFrameChild*
 TabChild::AllocPRenderFrameChild()
 {
     return new RenderFrameChild();
 }
 
 bool
 TabChild::DeallocPRenderFrameChild(PRenderFrameChild* aFrame)
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -520,16 +520,17 @@ public:
 protected:
     virtual ~TabChild();
 
     virtual PRenderFrameChild* AllocPRenderFrameChild() override;
     virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
     virtual bool RecvDestroy() override;
     virtual bool RecvSetUpdateHitRegion(const bool& aEnabled) override;
     virtual bool RecvSetIsDocShellActive(const bool& aIsActive) override;
+    virtual bool RecvNavigateDocument(const bool& aForward) override;
 
     virtual bool RecvRequestNotifyAfterRemotePaint() override;
 
     virtual bool RecvParentActivated(const bool& aActivated) override;
 
     virtual bool RecvStopIMEStateManagement() override;
     virtual bool RecvMenuKeyboardListenerInstalled(
                    const bool& aInstalled) override;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -504,23 +504,27 @@ TabParent::ActorDestroy(ActorDestroyReas
   }
 
   if (os) {
     os->NotifyObservers(NS_ISUPPORTS_CAST(nsITabParent*, this), "ipc:browser-destroyed", nullptr);
   }
 }
 
 bool
-TabParent::RecvMoveFocus(const bool& aForward)
+TabParent::RecvMoveFocus(const bool& aForward, const bool& aForDocumentNavigation)
 {
   nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID);
   if (fm) {
     nsCOMPtr<nsIDOMElement> dummy;
-    uint32_t type = aForward ? uint32_t(nsIFocusManager::MOVEFOCUS_FORWARD)
-                             : uint32_t(nsIFocusManager::MOVEFOCUS_BACKWARD);
+
+    uint32_t type = aForward ?
+      (aForDocumentNavigation ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FORWARDDOC) :
+                                static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_FORWARD)) :
+      (aForDocumentNavigation ? static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_BACKWARDDOC) :
+                                static_cast<uint32_t>(nsIFocusManager::MOVEFOCUS_BACKWARD));
     nsCOMPtr<nsIDOMElement> frame = do_QueryInterface(mFrameElement);
     fm->MoveFocus(nullptr, frame, type, nsIFocusManager::FLAG_BYKEY,
                   getter_AddRefs(dummy));
   }
   return true;
 }
 
 bool
@@ -2927,16 +2931,23 @@ TabParent::GetHasContentOpener(bool* aRe
 }
 
 void
 TabParent::SetHasContentOpener(bool aHasContentOpener)
 {
   mHasContentOpener = aHasContentOpener;
 }
 
+NS_IMETHODIMP
+TabParent::NavigateDocument(bool aForward)
+{
+  unused << SendNavigateDocument(aForward);
+  return NS_OK;
+}
+
 class LayerTreeUpdateRunnable final
   : public nsRunnable
 {
   uint64_t mLayersId;
   bool mActive;
 
 public:
   explicit LayerTreeUpdateRunnable(uint64_t aLayersId, bool aActive)
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -120,17 +120,18 @@ public:
     nsIXULBrowserWindow* GetXULBrowserWindow();
 
     void Destroy();
 
     void RemoveWindowListeners();
     void AddWindowListeners();
     void DidRefresh() override;
 
-    virtual bool RecvMoveFocus(const bool& aForward) override;
+    virtual bool RecvMoveFocus(const bool& aForward,
+                               const bool& aForDocumentNavigation) override;
     virtual bool RecvEvent(const RemoteDOMEvent& aEvent) override;
     virtual bool RecvReplyKeyEvent(const WidgetKeyboardEvent& aEvent) override;
     virtual bool RecvDispatchAfterKeyboardEvent(const WidgetKeyboardEvent& aEvent) override;
     virtual bool RecvBrowserFrameOpenWindow(PBrowserParent* aOpener,
                                             const nsString& aURL,
                                             const nsString& aName,
                                             const nsString& aFeatures,
                                             bool* aOutWindowOpened) override;
--- a/embedding/browser/nsIWebBrowserChromeFocus.idl
+++ b/embedding/browser/nsIWebBrowserChromeFocus.idl
@@ -7,24 +7,26 @@
 #include "nsISupports.idl"
 
 /**
  * The nsIWebBrowserChromeFocus is implemented by the same object as the
  * nsIEmbeddingSiteWindow. It represents the focus up-calls from mozilla
  * to the embedding chrome. See mozilla bug #70224 for gratuitous info.
  */
 
-[scriptable, uuid(d2206418-1dd1-11b2-8e55-acddcd2bcfb8)]
+[scriptable, uuid(947B2EE6-51ED-4C2B-9F45-426C27CA84C6)]
 interface nsIWebBrowserChromeFocus : nsISupports
 {
     /**
-     * Set the focus at the next focusable element in the chrome.
+     * Set the focus at the next focusable element in the chrome. If
+     * aForDocumentNavigation is true, this was a document navigation, so
+     * focus the parent window. 
      */
 
-    void focusNextElement();
+    void focusNextElement(in bool aForDocumentNavigation);
 
     /**
      * Set the focus at the previous focusable element in the chrome.
      */
 
-    void focusPrevElement();
+    void focusPrevElement(in bool aForDocumentNavigation);
 
 };