Bug 1640280 - Extend autofocus to SVGGraphicsElement r=emilio
authorlongsonr <longsonr@gmail.com>
Wed, 27 May 2020 07:59:07 +0000
changeset 532326 9734a05d25053a0c6510606980c1aeb3cdb3aadc
parent 532325 97c567ece28bde503d566429d64904f30c1763a9
child 532327 2348767fd355b47baf252d0e42187c15995d453b
push id37454
push userccoroiu@mozilla.com
push dateWed, 27 May 2020 16:14:31 +0000
treeherdermozilla-central@a1dd9afbfdf5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs1640280, 662496
milestone78.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 1640280 - Extend autofocus to SVGGraphicsElement r=emilio Note we're still limited to markup only because of bug 662496 so we don't pass the web platform tests for this feature. Chrome has implemented support per https://www.chromestatus.com/feature/5654905853313024 In SVG only SVGGraphicsElements are focusable. Differential Revision: https://phabricator.services.mozilla.com/D76546
dom/base/BindContext.cpp
dom/base/BindContext.h
dom/base/ResponsiveImageSelector.cpp
dom/base/moz.build
dom/html/nsGenericHTMLElement.cpp
dom/svg/SVGGraphicsElement.cpp
dom/svg/SVGGraphicsElement.h
dom/webidl/SVGGraphicsElement.webidl
layout/reftests/svg/autofocus-01-ref.svg
layout/reftests/svg/autofocus-01a.svg
layout/reftests/svg/autofocus-01b.svg
layout/reftests/svg/autofocus-02a.svg
layout/reftests/svg/autofocus-02b.svg
layout/reftests/svg/reftest.list
new file mode 100644
--- /dev/null
+++ b/dom/base/BindContext.cpp
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BindContext.h"
+
+#include "mozilla/StaticPrefs_browser.h"
+
+namespace mozilla {
+namespace dom {
+
+bool BindContext::AllowsAutoFocus() const {
+  if (!StaticPrefs::browser_autofocus()) {
+    return false;
+  }
+  if (!InUncomposedDoc()) {
+    return false;
+  }
+  if (mDoc.IsBeingUsedAsImage()) {
+    return false;
+  }
+  return IsSameOriginAsTop();
+}
+
+bool BindContext::IsSameOriginAsTop() const {
+  BrowsingContext* browsingContext = mDoc.GetBrowsingContext();
+  if (!browsingContext) {
+    return false;
+  }
+
+  nsPIDOMWindowOuter* topWindow = browsingContext->Top()->GetDOMWindow();
+  if (!topWindow) {
+    // If we don't have a DOMWindow, We are not in same origin.
+    return false;
+  }
+
+  Document* topLevelDocument = topWindow->GetExtantDoc();
+  if (!topLevelDocument) {
+    return false;
+  }
+
+  return NS_SUCCEEDED(nsContentUtils::CheckSameOrigin(topLevelDocument, &mDoc));
+}
+
+}  // namespace dom
+}  // namespace mozilla
--- a/dom/base/BindContext.h
+++ b/dom/base/BindContext.h
@@ -38,16 +38,23 @@ struct MOZ_STACK_CLASS BindContext final
 
   Document* GetUncomposedDoc() const {
     return mInUncomposedDoc ? &mDoc : nullptr;
   }
 
   // Whether our subtree root is changing as a result of this operation.
   bool SubtreeRootChanges() const { return mSubtreeRootChanges; }
 
+  // Autofocus is allowed only if the is in the same origin as the top level
+  // document.
+  // https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute:same-origin
+  // In addition, the document should not be already loaded and the
+  // "browser.autofocus" preference should be 'true'.
+  bool AllowsAutoFocus() const;
+
   // This constructor should be used for regular appends to content.
   explicit BindContext(nsINode& aParent)
       : mDoc(*aParent.OwnerDoc()),
         mInComposedDoc(aParent.IsInComposedDoc()),
         mInUncomposedDoc(aParent.IsInUncomposedDoc()),
         mSubtreeRootChanges(true) {}
 
   // When re-binding a shadow host into a tree, we re-bind all the shadow tree
@@ -69,16 +76,20 @@ struct MOZ_STACK_CLASS BindContext final
       : mDoc(*aParentElement.OwnerDoc()),
         mInComposedDoc(aParentElement.IsInComposedDoc()),
         mInUncomposedDoc(aParentElement.IsInUncomposedDoc()),
         mSubtreeRootChanges(true) {
     MOZ_ASSERT(mInComposedDoc, "Binding NAC in a disconnected subtree?");
   }
 
  private:
+  // Returns true iff the document is in the same origin as the top level
+  // document.
+  bool IsSameOriginAsTop() const;
+
   Document& mDoc;
 
   const bool mInComposedDoc;
   const bool mInUncomposedDoc;
 
   // Whether the bind operation will change the subtree root of the content
   // we're binding.
   const bool mSubtreeRootChanges;
--- a/dom/base/ResponsiveImageSelector.cpp
+++ b/dom/base/ResponsiveImageSelector.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/dom/ResponsiveImageSelector.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/PresShellInlines.h"
 #include "mozilla/ServoStyleSetInlines.h"
 #include "mozilla/TextUtils.h"
 #include "nsIURI.h"
 #include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
 #include "nsContentUtils.h"
 #include "nsPresContext.h"
 
 #include "nsCSSProps.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -265,16 +265,17 @@ if CONFIG['FUZZING']:
     ]
 
 UNIFIED_SOURCES += [
     'AbstractRange.cpp',
     'AnonymousContent.cpp',
     'Attr.cpp',
     'AttrArray.cpp',
     'BarProps.cpp',
+    'BindContext.cpp',
     'BodyConsumer.cpp',
     'BodyStream.cpp',
     'BodyUtil.cpp',
     'BorrowedAttrInfo.cpp',
     'CharacterData.cpp',
     'ChildIterator.cpp',
     'ChromeMessageBroadcaster.cpp',
     'ChromeMessageSender.cpp',
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -11,17 +11,16 @@
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/MappedDeclarations.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/TextEditor.h"
-#include "mozilla/StaticPrefs_browser.h"
 #include "mozilla/StaticPrefs_layout.h"
 
 #include "nscore.h"
 #include "nsGenericHTMLElement.h"
 #include "nsAttrValueInlines.h"
 #include "nsCOMPtr.h"
 #include "nsAtom.h"
 #include "nsQueryObject.h"
@@ -1654,54 +1653,23 @@ nsIContent::IMEState nsGenericHTMLFormEl
   IMEState state;
   nsresult rv = textEditor->GetPreferredIMEState(&state);
   if (NS_FAILED(rv)) {
     return nsGenericHTMLElement::GetDesiredIMEState();
   }
   return state;
 }
 
-static bool IsSameOriginAsTop(const BindContext& aContext,
-                              const nsGenericHTMLFormElement* aElement) {
-  MOZ_ASSERT(aElement);
-
-  BrowsingContext* browsingContext = aContext.OwnerDoc().GetBrowsingContext();
-  if (!browsingContext) {
-    return false;
-  }
-
-  nsPIDOMWindowOuter* topWindow = browsingContext->Top()->GetDOMWindow();
-  if (!topWindow) {
-    // If we don't have a DOMWindow, We are not in same origin.
-    return false;
-  }
-
-  Document* topLevelDocument = topWindow->GetExtantDoc();
-  if (!topLevelDocument) {
-    return false;
-  }
-
-  return NS_SUCCEEDED(
-      nsContentUtils::CheckSameOrigin(topLevelDocument, aElement));
-}
-
 nsresult nsGenericHTMLFormElement::BindToTree(BindContext& aContext,
                                               nsINode& aParent) {
   nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // An autofocus event has to be launched if the autofocus attribute is
-  // specified and the element accepts the autofocus attribute and only if the
-  // target document is in the same origin as the top level document.
-  // https://html.spec.whatwg.org/multipage/interaction.html#the-autofocus-attribute:same-origin
-  // In addition, the document should not be already loaded and the
-  // "browser.autofocus" preference should be 'true'.
-  if (IsAutofocusable() && HasAttr(kNameSpaceID_None, nsGkAtoms::autofocus) &&
-      StaticPrefs::browser_autofocus() && IsInUncomposedDoc() &&
-      IsSameOriginAsTop(aContext, this)) {
+  if (IsAutofocusable() && HasAttr(nsGkAtoms::autofocus) &&
+      aContext.AllowsAutoFocus()) {
     aContext.OwnerDoc().SetAutoFocusElement(this);
   }
 
   // If @form is set, the element *has* to be in a composed document, otherwise
   // it wouldn't be possible to find an element with the corresponding id.
   // If @form isn't set, the element *has* to have a parent, otherwise it
   // wouldn't be possible to find a form ancestor.
   // We should not call UpdateFormOwner if none of these conditions are
--- a/dom/svg/SVGGraphicsElement.cpp
+++ b/dom/svg/SVGGraphicsElement.cpp
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/SVGGraphicsElement.h"
 
+#include "mozilla/dom/BindContext.h"
+
 namespace mozilla {
 namespace dom {
 
 //----------------------------------------------------------------------
 // nsISupports methods
 
 NS_IMPL_ADDREF_INHERITED(SVGGraphicsElement, SVGGraphicsElementBase)
 NS_IMPL_RELEASE_INHERITED(SVGGraphicsElement, SVGGraphicsElementBase)
@@ -57,10 +59,23 @@ bool SVGGraphicsElement::IsSVGFocusable(
 
 bool SVGGraphicsElement::IsFocusableInternal(int32_t* aTabIndex,
                                              bool aWithMouse) {
   bool isFocusable = false;
   IsSVGFocusable(&isFocusable, aTabIndex);
   return isFocusable;
 }
 
+nsresult SVGGraphicsElement::BindToTree(BindContext& aContext,
+                                        nsINode& aParent) {
+  nsresult rv = SVGGraphicsElementBase::BindToTree(aContext, aParent);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (IsFocusable() && HasAttr(nsGkAtoms::autofocus) &&
+      aContext.AllowsAutoFocus()) {
+    aContext.OwnerDoc().SetAutoFocusElement(this);
+  }
+
+  return NS_OK;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/svg/SVGGraphicsElement.h
+++ b/dom/svg/SVGGraphicsElement.h
@@ -20,17 +20,24 @@ class SVGGraphicsElement : public SVGGra
   explicit SVGGraphicsElement(
       already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
   ~SVGGraphicsElement() = default;
 
  public:
   // interfaces:
   NS_DECL_ISUPPORTS_INHERITED
 
+  // WebIDL
+  bool Autofocus() const { return GetBoolAttr(nsGkAtoms::autofocus); }
+  void SetAutofocus(bool aAutofocus) {
+    SetBoolAttr(nsGkAtoms::autofocus, aAutofocus);
+  }
+
   bool IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) override;
+  nsresult BindToTree(BindContext&, nsINode& aParent) override;
   SVGElement* AsSVGElement() final { return this; }
 
  protected:
   // returns true if focusability has been definitively determined otherwise
   // false
   bool IsSVGFocusable(bool* aIsFocusable, int32_t* aTabIndex);
 
   template <typename T>
--- a/dom/webidl/SVGGraphicsElement.webidl
+++ b/dom/webidl/SVGGraphicsElement.webidl
@@ -14,16 +14,19 @@ dictionary SVGBoundingBoxOptions {
   boolean fill = true;
   boolean stroke = false;
   boolean markers = false;
   boolean clipped = false;
 };
 
 [Exposed=Window]
 interface SVGGraphicsElement : SVGElement {
+  [Pure]
+  attribute boolean autofocus;
+
   readonly attribute SVGAnimatedTransformList transform;
 
   readonly attribute SVGElement? nearestViewportElement;
   readonly attribute SVGElement? farthestViewportElement;
 
   [NewObject]
   SVGRect getBBox(optional SVGBoundingBoxOptions aOptions = {});
   // Not implemented
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/autofocus-01-ref.svg
@@ -0,0 +1,8 @@
+<svg width="400" height="110" xmlns="http://www.w3.org/2000/svg" onload="document.querySelector('a').focus();">
+  <a href="#">
+    <rect x="10" y="10" width="300" height="90" fill="lime"/>
+  </a>
+  <style>
+    a:focus { outline: 2px solid blue; }
+  </style>
+</svg> 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/autofocus-01a.svg
@@ -0,0 +1,6 @@
+<svg width="400" height="110" xmlns="http://www.w3.org/2000/svg">
+  <rect x="10" y="10" width="300" height="90" fill="lime" tabindex="0" autofocus="true"/>
+  <style>
+    rect:focus { outline: 2px solid blue; }
+  </style>
+</svg> 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/autofocus-01b.svg
@@ -0,0 +1,8 @@
+<svg width="400" height="110" xmlns="http://www.w3.org/2000/svg">
+  <a href="#" autofocus="true">
+    <rect x="10" y="10" width="300" height="90" fill="lime"/>
+  </a>
+  <style>
+    a:focus { outline: 2px solid blue; }
+  </style>
+</svg> 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/autofocus-02a.svg
@@ -0,0 +1,8 @@
+<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
+  <rect width="100%" height="100%" fill="lime"/>
+  <!-- need tabindex for autofocus to work -->
+  <rect x="10" y="10" width="300" height="90" fill="lime" autofocus="true"/>
+  <style>
+    rect:focus { outline: 2px solid blue; }
+  </style>
+</svg> 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/autofocus-02b.svg
@@ -0,0 +1,10 @@
+<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
+  <rect width="100%" height="100%" fill="lime"/>
+  <!-- need href for autofocus to work -->
+  <a autofocus="true">
+    <rect x="10" y="10" width="300" height="90" fill="lime"/>
+  </a>
+  <style>
+    a:focus { outline: 2px solid blue; }
+  </style>
+</svg> 
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -20,16 +20,21 @@ include text/reftest.list
 include load-only/reftest.list
 
 # Mozilla only tests (i.e. those containing XUL/XBL/etc.)
 include moz-only/reftest.list
 
 # svg-integration tests (using svg effects in e.g. HTML)
 include svg-integration/reftest.list
 
+needs-focus == autofocus-01a.svg autofocus-01-ref.svg
+needs-focus == autofocus-01b.svg autofocus-01-ref.svg
+needs-focus == autofocus-02a.svg pass.svg
+needs-focus == autofocus-02b.svg pass.svg
+
 == background-svg-without-height.html background-ref.html
 == background-svg-without-height-width.html background-ref.html
 == background-svg-without-width.html background-ref.html
 
 == baseline-middle-01.svg pass.svg
 
 fails-if(Android&&webrender&&device) == blend-color-burn.svg blend-color-burn-ref.svg
 fails-if(Android&&webrender&&device) == blend-color-dodge.svg blend-color-dodge-ref.svg