Bug 1374045: add 'preventScroll' option to HTMLElement's, SVGElement's and XULElement's 'focus' method r=smaug
authorMirko Brodesser <mbrodesser@mozilla.com>
Fri, 12 Apr 2019 08:16:47 +0000
changeset 469250 894487fe3fa04ee0c91aef059bcbab4005f6e3e2
parent 469249 0cee09cf4016fa15f2d5904e2e1c9efc0f5032c8
child 469251 11fb8048959d0d2a1e12027680684ff9fbebf760
push id112776
push usershindli@mozilla.com
push dateFri, 12 Apr 2019 16:20:17 +0000
treeherdermozilla-inbound@b4501ced5619 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1374045
milestone68.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 1374045: add 'preventScroll' option to HTMLElement's, SVGElement's and XULElement's 'focus' method r=smaug - Remove expectation that 'preventScroll.html' fails. - Use '[NoInterfaceObject] interface' workaround to simulate missing 'mixin' support. Differential Revision: https://phabricator.services.mozilla.com/D26922
dom/base/Document.cpp
dom/base/Element.cpp
dom/base/Element.h
dom/base/nsFocusManager.cpp
dom/base/nsFocusManager.h
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
dom/html/HTMLLabelElement.cpp
dom/html/HTMLLabelElement.h
dom/html/HTMLLegendElement.cpp
dom/html/HTMLLegendElement.h
dom/interfaces/base/nsIFocusManager.idl
dom/webidl/Element.webidl
dom/webidl/HTMLElement.webidl
dom/webidl/SVGElement.webidl
dom/webidl/XULElement.webidl
layout/forms/nsNumberControlFrame.cpp
testing/web-platform/meta/html/interaction/focus/processing-model/preventScroll.html.ini
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -8840,18 +8840,19 @@ class nsAutoFocusEvent : public Runnable
       return NS_OK;
     }
 
     // Don't steal focus from the user.
     if (mTopWindow->GetFocusedElement()) {
       return NS_OK;
     }
 
-    mozilla::ErrorResult rv;
-    mElement->Focus(rv);
+    FocusOptions options;
+    ErrorResult rv;
+    mElement->Focus(options, rv);
     return rv.StealNSResult();
   }
 
  private:
   nsCOMPtr<Element> mElement;
   nsCOMPtr<nsPIDOMWindowOuter> mTopWindow;
 };
 
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -333,25 +333,31 @@ int32_t Element::TabIndex() {
   const nsAttrValue* attrVal = mAttrs.GetAttr(nsGkAtoms::tabindex);
   if (attrVal && attrVal->Type() == nsAttrValue::eInteger) {
     return attrVal->GetIntegerValue();
   }
 
   return TabIndexDefault();
 }
 
-void Element::Focus(mozilla::ErrorResult& aError) {
+void Element::Focus(const FocusOptions& aOptions, ErrorResult& aError) {
   nsFocusManager* fm = nsFocusManager::GetFocusManager();
   // Also other browsers seem to have the hack to not re-focus (and flush) when
   // the element is already focused.
+  // Until https://github.com/whatwg/html/issues/4512 is clarified, we'll
+  // maintain interoperatibility by not re-focusing, independent of aOptions.
+  // I.e., `focus({ preventScroll: true})` followed by `focus( { preventScroll:
+  // false })` won't re-focus.
   if (fm) {
     if (fm->CanSkipFocus(this)) {
       fm->NeedsFlushBeforeEventHandling(this);
     } else {
-      aError = fm->SetFocus(this, nsIFocusManager::FLAG_BYELEMENTFOCUS);
+      aError = fm->SetFocus(
+          this, nsIFocusManager::FLAG_BYELEMENTFOCUS |
+                    nsFocusManager::FocusOptionsToFocusManagerFlags(aOptions));
     }
   }
 }
 
 void Element::SetTabIndex(int32_t aTabIndex, mozilla::ErrorResult& aError) {
   nsAutoString value;
   value.AppendInt(aTabIndex);
 
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -248,17 +248,17 @@ class Element : public FragmentOrElement
    *
    * @param aShadowRoot The ShadowRoot to be bound to this element.
    */
   void SetShadowRoot(ShadowRoot* aShadowRoot);
 
   /**
    * Make focus on this element.
    */
-  virtual void Focus(mozilla::ErrorResult& aError);
+  virtual void Focus(const FocusOptions& aOptions, ErrorResult& aError);
 
   /**
    * Show blur and clear focus.
    */
   virtual void Blur(mozilla::ErrorResult& aError);
 
   /**
    * The style state of this element. This is the real state of the element
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -42,16 +42,17 @@
 #include "nsFrameLoader.h"
 #include "nsNumberControlFrame.h"
 #include "nsNetUtil.h"
 #include "nsRange.h"
 
 #include "mozilla/AccessibleCaretEventHub.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementBinding.h"
 #include "mozilla/dom/HTMLImageElement.h"
 #include "mozilla/dom/HTMLInputElement.h"
 #include "mozilla/dom/HTMLSlotElement.h"
 #include "mozilla/dom/BrowserBridgeChild.h"
 #include "mozilla/dom/Text.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
@@ -2110,19 +2111,16 @@ void nsFocusManager::FireFocusOrBlurEven
                             currentWindow, currentFocusedContent,
                             aRelatedTarget);
     }
   }
 }
 
 void nsFocusManager::ScrollIntoView(nsIPresShell* aPresShell,
                                     nsIContent* aContent, uint32_t aFlags) {
-  MOZ_ASSERT(!(aFlags & FLAG_BYELEMENTFOCUS) ||
-                 !!(aFlags & FLAG_BYELEMENTFOCUS) == !(aFlags & FLAG_NOSCROLL),
-             "FLAG_BYELEMENTFOCUS shouldn't involve with FLAG_NOSCROLL");
   // if the noscroll flag isn't set, scroll the newly focused element into view
   if (!(aFlags & FLAG_NOSCROLL)) {
     uint32_t scrollFlags = nsIPresShell::SCROLL_OVERFLOW_HIDDEN;
     if (!(aFlags & FLAG_BYELEMENTFOCUS)) {
       scrollFlags |= nsIPresShell::SCROLL_IGNORE_SCROLL_MARGIN_AND_PADDING;
     }
     aPresShell->ScrollContentIntoView(
         aContent,
@@ -2859,16 +2857,21 @@ nsresult nsFocusManager::DetermineElemen
     // wrapped all the way around and didn't find anything to move the focus
     // to, so just break out
     if (startContent == originalStartContent) break;
   }
 
   return NS_OK;
 }
 
+uint32_t nsFocusManager::FocusOptionsToFocusManagerFlags(
+    const mozilla::dom::FocusOptions& aOptions) {
+  return aOptions.mPreventScroll ? nsIFocusManager::FLAG_NOSCROLL : 0;
+}
+
 static bool IsHostOrSlot(const nsIContent* aContent) {
   return aContent && (aContent->GetShadowRoot() ||
                       aContent->IsHTMLElement(nsGkAtoms::slot));
 }
 
 // Helper class to iterate contents in scope by traversing flattened tree
 // in tree order
 class MOZ_STACK_CLASS ScopedContentTraversal {
--- a/dom/base/nsFocusManager.h
+++ b/dom/base/nsFocusManager.h
@@ -24,16 +24,17 @@
 
 class nsIContent;
 class nsIDocShellTreeItem;
 class nsPIDOMWindowOuter;
 
 namespace mozilla {
 namespace dom {
 class Element;
+struct FocusOptions;
 class TabParent;
 }  // namespace dom
 }  // namespace mozilla
 
 struct nsDelayedBlurOrFocusEvent;
 
 /**
  * The focus manager keeps track of where the focus is, that is, the node
@@ -162,16 +163,19 @@ class nsFocusManager final : public nsIF
    * navigation is not done to parent documents and iteration returns to the
    * beginning (or end) of the starting document.
    */
   nsresult DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow,
                                        nsIContent* aStart, int32_t aType,
                                        bool aNoParentTraversal,
                                        nsIContent** aNextContent);
 
+  static uint32_t FocusOptionsToFocusManagerFlags(
+      const mozilla::dom::FocusOptions& aOptions);
+
   /**
    * Returns the content node that focus will be redirected to if aContent was
    * focused. This is used for the special case of certain XUL elements such
    * as textboxes or input number which redirect focus to an anonymous child.
    *
    * aContent must be non-null.
    *
    * XXXndeakin this should be removed eventually but I want to do that as
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -2936,42 +2936,43 @@ void HTMLInputElement::Blur(ErrorResult&
       dispatcher->RunDOMEventWhenSafe();
       return;
     }
   }
 
   nsGenericHTMLElement::Blur(aError);
 }
 
-void HTMLInputElement::Focus(ErrorResult& aError) {
+void HTMLInputElement::Focus(const FocusOptions& aOptions,
+                             ErrorResult& aError) {
   if (mType == NS_FORM_INPUT_NUMBER) {
     // Focus our anonymous text control, if we have one.
     nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
     if (numberControlFrame) {
       RefPtr<HTMLInputElement> textControl =
           numberControlFrame->GetAnonTextControl();
       if (textControl) {
-        textControl->Focus(aError);
+        textControl->Focus(aOptions, aError);
         return;
       }
     }
   }
 
   if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
       !IsExperimentalMobileType(mType)) {
     if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
       AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
           dateTimeBoxElement, NS_LITERAL_STRING("MozFocusInnerTextBox"),
           CanBubble::eNo, ChromeOnlyDispatch::eNo);
       dispatcher->RunDOMEventWhenSafe();
       return;
     }
   }
 
-  nsGenericHTMLElement::Focus(aError);
+  nsGenericHTMLElement::Focus(aOptions, aError);
 }
 
 #if !defined(ANDROID) && !defined(XP_MACOSX)
 bool HTMLInputElement::IsNodeApzAwareInternal() const {
   // Tell APZC we may handle mouse wheel event and do preventDefault when input
   // type is number.
   return (mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE) ||
          nsINode::IsNodeApzAwareInternal();
@@ -3911,18 +3912,20 @@ nsresult HTMLInputElement::PostHandleEve
                 if (container) {
                   nsAutoString name;
                   GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
                   RefPtr<HTMLInputElement> selectedRadioButton;
                   container->GetNextRadioButton(
                       name, isMovingBack, this,
                       getter_AddRefs(selectedRadioButton));
                   if (selectedRadioButton) {
+                    FocusOptions options;
                     ErrorResult error;
-                    selectedRadioButton->Focus(error);
+
+                    selectedRadioButton->Focus(options, error);
                     rv = error.StealNSResult();
                     if (NS_SUCCEEDED(rv)) {
                       rv = DispatchSimulatedClick(selectedRadioButton,
                                                   aVisitor.mEvent->IsTrusted(),
                                                   aVisitor.mPresContext);
                       if (NS_SUCCEEDED(rv)) {
                         aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
                       }
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -139,17 +139,18 @@ class HTMLInputElement final : public ns
   NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLInputElement, input)
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual int32_t TabIndexDefault() override;
   using nsGenericHTMLElement::Focus;
   virtual void Blur(ErrorResult& aError) override;
-  virtual void Focus(ErrorResult& aError) override;
+  virtual void Focus(const FocusOptions& aOptions,
+                     ErrorResult& aError) override;
 
   // nsINode
 #if !defined(ANDROID) && !defined(XP_MACOSX)
   virtual bool IsNodeApzAwareInternal() const override;
 #endif
 
   // Element
   virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
--- a/dom/html/HTMLLabelElement.cpp
+++ b/dom/html/HTMLLabelElement.cpp
@@ -45,23 +45,26 @@ HTMLFormElement* HTMLLabelElement::GetFo
   nsCOMPtr<nsIFormControl> formControl = do_QueryObject(control);
   if (!formControl) {
     return nullptr;
   }
 
   return static_cast<HTMLFormElement*>(formControl->GetFormElement());
 }
 
-void HTMLLabelElement::Focus(ErrorResult& aError) {
+void HTMLLabelElement::Focus(const FocusOptions& aOptions,
+                             ErrorResult& aError) {
   // retarget the focus method at the for content
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (fm) {
     RefPtr<Element> elem = GetLabeledElement();
     if (elem) {
-      fm->SetFocus(elem, nsIFocusManager::FLAG_BYELEMENTFOCUS);
+      fm->SetFocus(
+          elem, nsIFocusManager::FLAG_BYELEMENTFOCUS |
+                    nsFocusManager::FocusOptionsToFocusManagerFlags(aOptions));
     }
   }
 }
 
 nsresult HTMLLabelElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
   WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
   if (mHandlingEvent ||
       (!(mouseEvent && mouseEvent->IsLeftClickEvent()) &&
--- a/dom/html/HTMLLabelElement.h
+++ b/dom/html/HTMLLabelElement.h
@@ -38,17 +38,18 @@ class HTMLLabelElement final : public ns
     GetHTMLAttr(nsGkAtoms::_for, aHtmlFor);
   }
   void SetHtmlFor(const nsAString& aHtmlFor) {
     SetHTMLAttr(nsGkAtoms::_for, aHtmlFor);
   }
   nsGenericHTMLElement* GetControl() const { return GetLabeledElement(); }
 
   using nsGenericHTMLElement::Focus;
-  virtual void Focus(mozilla::ErrorResult& aError) override;
+  virtual void Focus(const FocusOptions& aOptions,
+                     ErrorResult& aError) override;
 
   // nsIContent
   virtual nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override;
   virtual bool PerformAccesskey(bool aKeyCausesActivation,
                                 bool aIsTrustedEvent) override;
   virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
 
   nsGenericHTMLElement* GetLabeledElement() const;
--- a/dom/html/HTMLLegendElement.cpp
+++ b/dom/html/HTMLLegendElement.cpp
@@ -63,47 +63,51 @@ nsresult HTMLLegendElement::BindToTree(D
                                        nsIContent* aBindingParent) {
   return nsGenericHTMLElement::BindToTree(aDocument, aParent, aBindingParent);
 }
 
 void HTMLLegendElement::UnbindFromTree(bool aDeep, bool aNullParent) {
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
-void HTMLLegendElement::Focus(ErrorResult& aError) {
+void HTMLLegendElement::Focus(const FocusOptions& aOptions,
+                              ErrorResult& aError) {
   nsIFrame* frame = GetPrimaryFrame();
   if (!frame) {
     return;
   }
 
   int32_t tabIndex;
   if (frame->IsFocusable(&tabIndex, false)) {
-    nsGenericHTMLElement::Focus(aError);
+    nsGenericHTMLElement::Focus(aOptions, aError);
     return;
   }
 
   // If the legend isn't focusable, focus whatever is focusable following
   // the legend instead, bug 81481.
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   if (!fm) {
     return;
   }
 
   RefPtr<Element> result;
-  aError = fm->MoveFocus(nullptr, this, nsIFocusManager::MOVEFOCUS_FORWARD,
-                         nsIFocusManager::FLAG_NOPARENTFRAME |
-                             nsIFocusManager::FLAG_BYELEMENTFOCUS,
-                         getter_AddRefs(result));
+  aError = fm->MoveFocus(
+      nullptr, this, nsIFocusManager::MOVEFOCUS_FORWARD,
+      nsIFocusManager::FLAG_NOPARENTFRAME |
+          nsIFocusManager::FLAG_BYELEMENTFOCUS |
+          nsFocusManager::FocusOptionsToFocusManagerFlags(aOptions),
+      getter_AddRefs(result));
 }
 
 bool HTMLLegendElement::PerformAccesskey(bool aKeyCausesActivation,
                                          bool aIsTrustedEvent) {
-  // just use the same behaviour as the focus method
+  FocusOptions options;
   ErrorResult rv;
-  Focus(rv);
+
+  Focus(options, rv);
   return NS_SUCCEEDED(rv.StealNSResult());
 }
 
 already_AddRefed<HTMLFormElement> HTMLLegendElement::GetForm() {
   Element* form = GetFormElement();
   MOZ_ASSERT_IF(form, form->IsHTMLElement(nsGkAtoms::form));
   RefPtr<HTMLFormElement> ret = static_cast<HTMLFormElement*>(form);
   return ret.forget();
--- a/dom/html/HTMLLegendElement.h
+++ b/dom/html/HTMLLegendElement.h
@@ -18,17 +18,18 @@ class HTMLLegendElement final : public n
  public:
   explicit HTMLLegendElement(
       already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
       : nsGenericHTMLElement(std::move(aNodeInfo)) {}
 
   NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLLegendElement, legend)
 
   using nsGenericHTMLElement::Focus;
-  virtual void Focus(ErrorResult& aError) override;
+  virtual void Focus(const FocusOptions& aOptions,
+                     ErrorResult& aError) override;
 
   virtual bool PerformAccesskey(bool aKeyCausesActivation,
                                 bool aIsTrustedEvent) override;
 
   // nsIContent
   virtual nsresult BindToTree(Document* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent) override;
   virtual void UnbindFromTree(bool aDeep = true,
--- a/dom/interfaces/base/nsIFocusManager.idl
+++ b/dom/interfaces/base/nsIFocusManager.idl
@@ -146,17 +146,18 @@ interface nsIFocusManager : nsISupports
   boolean elementIsFocusable(in Element aElement, in unsigned long aFlags);
 
   /*
    * Raise the window when switching focus
    */
   const unsigned long FLAG_RAISE = 1;
 
   /**
-   * Do not scroll the element to focus into view
+   * Do not scroll the element to focus into view. If FLAG_BYELEMENTFOCUS is
+   * set too, the latter is ignored.
    */
   const unsigned long FLAG_NOSCROLL = 2;
 
   /**
    * If attempting to change focus in a window that is not focused, do not
    * switch focus to that window. Instead, just update the focus within that
    * window and leave the application focus as is. This flag will have no
    * effect if a child window is focused and an attempt is made to adjust the
@@ -206,17 +207,17 @@ interface nsIFocusManager : nsISupports
   const unsigned long FLAG_BYTOUCH = 0x200000;
 
   /**
    * Focus is changing due to a Element.focus() call.
    * This is used to distinguish the Element.focus() call from other focus
    * functionalities since we need to factor scroll-margin and scroll-padding
    * values into the position where we scroll to the element by the
    * Element.focus() call.
-   * NOTE: This flag shouldn't be specified with FLAG_NOSCROLL.
+   * NOTE: This flag is ignored if FLAG_NOSCROLL is set too.
    */
   const unsigned long FLAG_BYELEMENTFOCUS = 0x400000;
 
   // these constants are used with the aType argument to MoveFocus
 
   /** move focus forward one element, used when pressing TAB */
   const unsigned long MOVEFOCUS_FORWARD = 1;
   /** move focus backward one element, used when pressing Shift+TAB */
--- a/dom/webidl/Element.webidl
+++ b/dom/webidl/Element.webidl
@@ -150,16 +150,28 @@ interface Element : Node {
   [ChromeOnly]
   DOMMatrixReadOnly getTransformToAncestor(Element ancestor);
   [ChromeOnly]
   DOMMatrixReadOnly getTransformToParent();
   [ChromeOnly]
   DOMMatrixReadOnly getTransformToViewport();
 };
 
+// https://html.spec.whatwg.org/#focus-management-apis
+dictionary FocusOptions {
+  boolean preventScroll = false;
+};
+
+// TODO(mbrodesser): once https://bugzilla.mozilla.org/show_bug.cgi?id=1414372
+// is fixed, mixin should be used.
+[NoInterfaceObject] interface HTMLOrSVGOrXULElementMixin {
+  [Throws]
+  void focus(optional FocusOptions options);
+};
+
 // http://dev.w3.org/csswg/cssom-view/
 enum ScrollLogicalPosition { "start", "center", "end", "nearest" };
 dictionary ScrollIntoViewOptions : ScrollOptions {
   ScrollLogicalPosition block = "start";
   ScrollLogicalPosition inline = "nearest";
 };
 
 // http://dev.w3.org/csswg/cssom-view/#extensions-to-the-element-interface
--- a/dom/webidl/HTMLElement.webidl
+++ b/dom/webidl/HTMLElement.webidl
@@ -31,18 +31,16 @@ interface HTMLElement : Element {
   // user interaction
   [CEReactions, SetterThrows, Pure]
            attribute boolean hidden;
   [NeedsCallerType]
   void click();
   [CEReactions, SetterThrows, Pure]
            attribute long tabIndex;
   [Throws]
-  void focus();
-  [Throws]
   void blur();
   [CEReactions, SetterThrows, Pure]
            attribute DOMString accessKey;
   [Pure]
   readonly attribute DOMString accessKeyLabel;
   [CEReactions, SetterThrows, Pure]
            attribute boolean draggable;
   //[PutForwards=value] readonly attribute DOMTokenList dropzone;
@@ -88,13 +86,14 @@ interface TouchEventHandlers {
            attribute EventHandler ontouchend;
   [Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
            attribute EventHandler ontouchmove;
   [Func="nsGenericHTMLElement::LegacyTouchAPIEnabled"]
            attribute EventHandler ontouchcancel;
 };
 
 HTMLElement implements GlobalEventHandlers;
+HTMLElement implements HTMLOrSVGOrXULElementMixin;
 HTMLElement implements DocumentAndElementEventHandlers;
 HTMLElement implements TouchEventHandlers;
 HTMLElement implements OnErrorEventHandlerForNodes;
 
 interface HTMLUnknownElement : HTMLElement {};
--- a/dom/webidl/SVGElement.webidl
+++ b/dom/webidl/SVGElement.webidl
@@ -19,16 +19,16 @@ interface SVGElement : Element {
   [PutForwards=cssText, Constant]
   readonly attribute CSSStyleDeclaration style;
 
   readonly attribute SVGSVGElement? ownerSVGElement;
   readonly attribute SVGElement? viewportElement;
 
   [SetterThrows, Pure]
         attribute long tabIndex;
-  [Throws] void focus();
   [Throws] void blur();
 };
 
 SVGElement implements GlobalEventHandlers;
+SVGElement implements HTMLOrSVGOrXULElementMixin;
 SVGElement implements DocumentAndElementEventHandlers;
 SVGElement implements TouchEventHandlers;
 SVGElement implements OnErrorEventHandlerForNodes;
--- a/dom/webidl/XULElement.webidl
+++ b/dom/webidl/XULElement.webidl
@@ -77,18 +77,16 @@ interface XULElement : Element {
   [Throws, ChromeOnly]
   readonly attribute XULControllers             controllers;
   [Throws]
   readonly attribute BoxObject?                 boxObject;
 
   [SetterThrows]
   attribute long tabIndex;
   [Throws]
-  void                      focus();
-  [Throws]
   void                      blur();
   [NeedsCallerType]
   void                      click();
   void                      doCommand();
 
   [Constant]
   readonly attribute CSSStyleDeclaration style;
 
@@ -97,10 +95,11 @@ interface XULElement : Element {
   boolean hasMenu();
 
   // If this is a menu-type element, opens or closes the menu
   // depending on the argument passed.
   void openMenu(boolean open);
 };
 
 XULElement implements GlobalEventHandlers;
+XULElement implements HTMLOrSVGOrXULElementMixin;
 XULElement implements TouchEventHandlers;
 XULElement implements OnErrorEventHandlerForNodes;
--- a/layout/forms/nsNumberControlFrame.cpp
+++ b/layout/forms/nsNumberControlFrame.cpp
@@ -284,17 +284,20 @@ class FocusTextField : public Runnable {
  public:
   FocusTextField(nsIContent* aNumber, nsIContent* aTextField)
       : mozilla::Runnable("FocusTextField"),
         mNumber(aNumber),
         mTextField(aTextField) {}
 
   NS_IMETHOD Run() override {
     if (mNumber->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS)) {
-      HTMLInputElement::FromNode(mTextField)->Focus(IgnoreErrors());
+      // This job shouldn't be triggered by a WebIDL interface, hence the
+      // default options can be used.
+      FocusOptions options;
+      HTMLInputElement::FromNode(mTextField)->Focus(options, IgnoreErrors());
     }
 
     return NS_OK;
   }
 
  private:
   nsCOMPtr<nsIContent> mNumber;
   nsCOMPtr<nsIContent> mTextField;
@@ -527,17 +530,21 @@ bool nsNumberControlFrame::IsFocused() c
   return mTextField->State().HasState(NS_EVENT_STATE_FOCUS) ||
          mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS);
 }
 
 void nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent) {
   if (aEvent->mOriginalTarget != mTextField) {
     // Move focus to our text field
     RefPtr<HTMLInputElement> textField = HTMLInputElement::FromNode(mTextField);
-    textField->Focus(IgnoreErrors());
+
+    // Use default FocusOptions, because this method isn't supposed to be called
+    // from a WebIDL interface.
+    FocusOptions options;
+    textField->Focus(options, IgnoreErrors());
   }
 }
 
 void nsNumberControlFrame::HandleSelectCall() {
   RefPtr<HTMLInputElement> textField = HTMLInputElement::FromNode(mTextField);
   textField->Select();
 }
 
deleted file mode 100644
--- a/testing/web-platform/meta/html/interaction/focus/processing-model/preventScroll.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[preventScroll.html]
-  [elm.focus({preventScroll: true})]
-    expected: FAIL
-