diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index 41c55a696777..db5f63de615e 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -6490,7 +6490,8 @@ Nullable Document::GetDefaultView() const { return WindowProxyHolder(win->GetBrowsingContext()); } -nsIContent* Document::GetUnretargetedFocusedContent() const { +nsIContent* Document::GetUnretargetedFocusedContent( + IncludeChromeOnly aIncludeChromeOnly) const { nsCOMPtr window = GetWindow(); if (!window) { return nullptr; @@ -6506,8 +6507,8 @@ nsIContent* Document::GetUnretargetedFocusedContent() const { if (focusedContent->OwnerDoc() != this) { return nullptr; } - - if (focusedContent->ChromeOnlyAccess()) { + if (focusedContent->ChromeOnlyAccess() && + aIncludeChromeOnly == IncludeChromeOnly::No) { return focusedContent->FindFirstNonChromeOnlyAccessContent(); } return focusedContent; diff --git a/dom/base/Document.h b/dom/base/Document.h index c491cd26bad5..401e3da6296a 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -3357,7 +3357,11 @@ class Document : public nsINode, mozilla::ErrorResult& rv); Nullable GetDefaultView() const; Element* GetActiveElement(); - nsIContent* GetUnretargetedFocusedContent() const; + enum class IncludeChromeOnly : bool { No, Yes }; + // TODO(emilio): Audit callers and remove the default argument, some seem like + // they could want the IncludeChromeOnly::Yes version. + nsIContent* GetUnretargetedFocusedContent( + IncludeChromeOnly = IncludeChromeOnly::No) const; bool HasFocus(ErrorResult& rv) const; void GetDesignMode(nsAString& aDesignMode); void SetDesignMode(const nsAString& aDesignMode, diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index b2c4af08b3a2..536184ec7db6 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -6654,49 +6654,6 @@ bool nsContentUtils::IsFocusedContent(const nsIContent* aContent) { return fm && fm->GetFocusedElement() == aContent; } -bool nsContentUtils::IsSubDocumentTabbable(nsIContent* aContent) { - Document* doc = aContent->GetComposedDoc(); - if (!doc) { - return false; - } - - // If the subdocument lives in another process, the frame is - // tabbable. - if (EventStateManager::IsRemoteTarget(aContent)) { - return true; - } - - // XXXbz should this use OwnerDoc() for GetSubDocumentFor? - // sXBL/XBL2 issue! - Document* subDoc = doc->GetSubDocumentFor(aContent); - if (!subDoc) { - return false; - } - - nsCOMPtr docShell = subDoc->GetDocShell(); - if (!docShell) { - return false; - } - - nsCOMPtr contentViewer; - docShell->GetContentViewer(getter_AddRefs(contentViewer)); - if (!contentViewer) { - return false; - } - - // If there are 2 viewers for the current docshell, that - // means the current document may be a zombie document. - // While load and pageshow events are dispatched, zombie viewer is the old, - // to be hidden document. - if (contentViewer->GetPreviousViewer()) { - bool inOnLoad = false; - docShell->GetIsExecutingOnLoadHandler(&inOnLoad); - return inOnLoad; - } - - return true; -} - bool nsContentUtils::HasScrollgrab(nsIContent* aContent) { // If we ever standardize this feature we'll want to hook this up properly // again. For now we're removing all the DOM-side code related to it but diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index af36ade88cc3..4b0ca1ed2d4d 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -2487,16 +2487,6 @@ class nsContentUtils { static void GetAltText(nsAString& text); static void GetModifierSeparatorText(nsAString& text); - /** - * Returns if aContent has a tabbable subdocument. - * A sub document isn't tabbable when it's a zombie document. - * - * @param aElement element to test. - * - * @return Whether the subdocument is tabbable. - */ - static bool IsSubDocumentTabbable(nsIContent* aContent); - /** * Returns if aContent has the 'scrollgrab' property. * aContent may be null (in this case false is returned). diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp index 25c06cd90d55..00dc711ffa6e 100644 --- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp @@ -2096,34 +2096,21 @@ Element* nsFocusManager::FlushAndCheckIfFocusable(Element* aElement, // If this is an iframe that doesn't have an in-process subdocument, it is // either an OOP iframe or an in-process iframe without lazy about:blank - // creation having taken place. In the OOP case, treat the frame as - // focusable for consistency with Chrome. In the in-process case, create - // the initial about:blank for in-process BrowsingContexts in order to - // have the `GetSubDocumentFor` call after this block return something. + // creation having taken place. In the OOP case, iframe is always focusable. + // In the in-process case, create the initial about:blank for in-process + // BrowsingContexts in order to have the `GetSubDocumentFor` call after this + // block return something. + // + // TODO(emilio): This block can probably go after bug 543435 lands. if (RefPtr flo = do_QueryObject(aElement)) { - // dom/webauthn/tests/browser/browser_abort_visibility.js fails without - // the exclusion of XUL. - if (aElement->NodeInfo()->NamespaceID() != kNameSpaceID_XUL) { + if (!aElement->IsXULElement()) { // Only look at pre-existing browsing contexts. If this function is // called during reflow, calling GetBrowsingContext() could cause frame // loader initialization at a time when it isn't safe. if (BrowsingContext* bc = flo->GetExtantBrowsingContext()) { // This call may create a contentViewer-created about:blank. // That's intentional, so we can move focus there. - Document* subdoc = bc->GetDocument(); - if (!subdoc) { - return aElement; - } - nsIPrincipal* framerPrincipal = doc->GetPrincipal(); - nsIPrincipal* frameePrincipal = subdoc->GetPrincipal(); - if (framerPrincipal && frameePrincipal && - !framerPrincipal->Equals(frameePrincipal)) { - // Assume focusability of different-origin iframes even in the - // in-process case for consistency with the OOP case. - // This is likely already the case anyway, but in case not, - // this makes it explicitly so. - return aElement; - } + Unused << bc->GetDocument(); } } } diff --git a/dom/events/test/window_bug1369072.html b/dom/events/test/window_bug1369072.html index ffba23392856..94b147d38f2a 100644 --- a/dom/events/test/window_bug1369072.html +++ b/dom/events/test/window_bug1369072.html @@ -44,11 +44,18 @@ async function runTests() async function resetScroll() { + var oldFocus = document.activeElement; + var oldFrameFocus = iframe.contentDocument.activeElement; + // Cancel any scroll animation on the target scroll elements to make sure // setting scrollTop or scrolLeft works as expected. + // cancelScrollAnimation clears focus, so make sure to restore it. await cancelScrollAnimation(document.documentElement); await cancelScrollAnimation(iframe.contentDocument.documentElement); + oldFocus.focus(); + oldFrameFocus.focus(); + return new Promise(resolve => { var scrollParent = document.documentElement.scrollTop || document.documentElement.scrollLeft; var scrollChild = iframe.contentDocument.documentElement.scrollTop || iframe.contentDocument.documentElement.scrollLeft; diff --git a/dom/html/HTMLObjectElement.cpp b/dom/html/HTMLObjectElement.cpp index 6d48cc0dba4f..aae6ea73a1d6 100644 --- a/dom/html/HTMLObjectElement.cpp +++ b/dom/html/HTMLObjectElement.cpp @@ -188,9 +188,8 @@ bool HTMLObjectElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, // This method doesn't call nsGenericHTMLFormControlElement intentionally. // TODO: It should probably be changed when bug 597242 will be fixed. - if (IsEditableRoot() || - ((Type() == eType_Document || Type() == eType_FakePlugin) && - nsContentUtils::IsSubDocumentTabbable(this))) { + if (IsEditableRoot() || Type() == eType_Document || + Type() == eType_FakePlugin) { if (aTabIndex) { *aTabIndex = isFocusable ? attrVal->GetIntegerValue() : 0; } diff --git a/dom/html/nsGenericHTMLFrameElement.cpp b/dom/html/nsGenericHTMLFrameElement.cpp index 2b446514688f..cc9de8947995 100644 --- a/dom/html/nsGenericHTMLFrameElement.cpp +++ b/dom/html/nsGenericHTMLFrameElement.cpp @@ -329,12 +329,7 @@ bool nsGenericHTMLFrameElement::IsHTMLFocusable(bool aWithMouse, return true; } - *aIsFocusable = nsContentUtils::IsSubDocumentTabbable(this); - - if (!*aIsFocusable && aTabIndex) { - *aTabIndex = -1; - } - + *aIsFocusable = true; return false; } diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index c0cdadbc5e42..9694cad96ae4 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -1595,6 +1595,39 @@ void PresShell::AddAuthorSheet(StyleSheet* aSheet) { mDocument->ApplicableStylesChanged(); } +bool PresShell::FixUpFocus() { + if (!StaticPrefs::dom_focus_fixup()) { + return false; + } + if (NS_WARN_IF(!mDocument)) { + return false; + } + + nsIContent* currentFocus = mDocument->GetUnretargetedFocusedContent( + Document::IncludeChromeOnly::Yes); + if (!currentFocus) { + return false; + } + + nsIFrame* f = currentFocus->GetPrimaryFrame(); + if (f && f->IsFocusable()) { + return false; + } + + if (currentFocus == mDocument->GetBody() || + currentFocus == mDocument->GetRootElement()) { + return false; + } + + RefPtr fm = nsFocusManager::GetFocusManager(); + nsCOMPtr window = mDocument->GetWindow(); + if (NS_WARN_IF(!window)) { + return false; + } + fm->ClearFocus(window); + return true; +} + void PresShell::SelectionWillTakeFocus() { if (mSelection) { FrameSelectionWillTakeFocus(*mSelection); diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h index 08afafe94398..49d2eda3e20b 100644 --- a/layout/base/PresShell.h +++ b/layout/base/PresShell.h @@ -1269,6 +1269,11 @@ class PresShell final : public nsStubDocumentObserver, void SelectionWillTakeFocus() override; void SelectionWillLoseFocus() override; + // Implements the "focus fix-up rule". Returns true if the focus moved (in + // which case we might need to update layout again). + // See https://github.com/whatwg/html/issues/8225 + MOZ_CAN_RUN_SCRIPT bool FixUpFocus(); + /** * Set a "resolution" for the document, which if not 1.0 will * allocate more or fewer pixels for rescalable content by a factor diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index e7f5f5e6d65b..ec23efba4dd0 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -2580,10 +2580,15 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime, RefPtr presShell = rawPresShell; presShell->mObservingLayoutFlushes = false; presShell->mWasLastReflowInterrupted = false; - FlushType flushType = HasPendingAnimations(presShell) - ? FlushType::Layout - : FlushType::InterruptibleLayout; - presShell->FlushPendingNotifications(ChangesToFlush(flushType, false)); + const auto flushType = HasPendingAnimations(presShell) + ? FlushType::Layout + : FlushType::InterruptibleLayout; + const ChangesToFlush ctf(flushType, false); + presShell->FlushPendingNotifications(ctf); + if (presShell->FixUpFocus()) { + presShell->FlushPendingNotifications(ctf); + } + // Inform the FontFaceSet that we ticked, so that it can resolve its // ready promise if it needs to. presShell->NotifyFontFaceSetOnRefresh(); diff --git a/layout/reftests/forms/select/focusring-3-ref.html b/layout/reftests/forms/select/focusring-3-ref.html deleted file mode 100644 index 76757cdaf1ad..000000000000 --- a/layout/reftests/forms/select/focusring-3-ref.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - Testcase #3 for bug 1253977 - - - - - - - - - - diff --git a/layout/reftests/forms/select/focusring-3.html b/layout/reftests/forms/select/focusring-3.html deleted file mode 100644 index a76668f2b5f9..000000000000 --- a/layout/reftests/forms/select/focusring-3.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - Testcase #3 for bug 1253977 - - - - - - - - - - diff --git a/layout/reftests/forms/select/reftest.list b/layout/reftests/forms/select/reftest.list index cd4f0febf2d5..64212b94290c 100644 --- a/layout/reftests/forms/select/reftest.list +++ b/layout/reftests/forms/select/reftest.list @@ -8,7 +8,6 @@ fuzzy(0-1,0-4) == padding-button-placement.html padding-button-placement-ref.htm == 997709-2.html 997709-2-ref.html fuzzy(0-4,0-1) needs-focus == focusring-1.html focusring-1-ref.html needs-focus == focusring-2.html focusring-2-ref.html -needs-focus == focusring-3.html focusring-3-ref.html == dynamic-text-indent-1.html dynamic-text-indent-1-ref.html == dynamic-text-overflow-1.html dynamic-text-overflow-1-ref.html == listbox-zero-row-initial.html listbox-zero-row-initial-ref.html diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 79ab2d5bb008..a9ad08bce5e1 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -2265,6 +2265,13 @@ value: false mirror: always +# Controls whether the "focus fixup rule" is enabled. Nightly-only pending +# resolution + edits in https://github.com/whatwg/html/issues/8225 +- name: dom.focus.fixup + type: bool + value: @IS_NIGHTLY_BUILD@ + mirror: always + - name: dom.mouse_capture.enabled type: bool value: true diff --git a/testing/web-platform/meta/css/css-contain/container-queries/layout-dependent-focus.html.ini b/testing/web-platform/meta/css/css-contain/container-queries/layout-dependent-focus.html.ini deleted file mode 100644 index 06618e1c4128..000000000000 --- a/testing/web-platform/meta/css/css-contain/container-queries/layout-dependent-focus.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[layout-dependent-focus.html] - [Verify that onblur is called on hidden input] - expected: FAIL diff --git a/testing/web-platform/meta/css/selectors/focus-display-none-001.html.ini b/testing/web-platform/meta/css/selectors/focus-display-none-001.html.ini deleted file mode 100644 index 9e079af738a9..000000000000 --- a/testing/web-platform/meta/css/selectors/focus-display-none-001.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[focus-display-none-001.html] - [Test ':focus' after 'display:none' on input] - expected: FAIL - - [Test ':focus' after 'display:none' on input's parent] - expected: FAIL - diff --git a/testing/web-platform/meta/css/selectors/focus-within-display-none-001.html.ini b/testing/web-platform/meta/css/selectors/focus-within-display-none-001.html.ini deleted file mode 100644 index a8a7a320d614..000000000000 --- a/testing/web-platform/meta/css/selectors/focus-within-display-none-001.html.ini +++ /dev/null @@ -1,7 +0,0 @@ -[focus-within-display-none-001.html] - [Test ':focus-within' after 'display:none' on input] - expected: FAIL - - [Test ':focus-within' after 'display:none' on input's parent] - expected: FAIL - diff --git a/testing/web-platform/meta/inert/dynamic-inert-on-focused-element.html.ini b/testing/web-platform/meta/inert/dynamic-inert-on-focused-element.html.ini deleted file mode 100644 index 2a95a8c072d5..000000000000 --- a/testing/web-platform/meta/inert/dynamic-inert-on-focused-element.html.ini +++ /dev/null @@ -1,19 +0,0 @@ -[dynamic-inert-on-focused-element.html] - bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1740989 - [ that gets 'inert' attribute] - expected: FAIL - - [ whose parent gets 'inert' attribute] - expected: FAIL - - [