Bug 712130 - Defer autofocus until after frame construction. r=bz
The autofocus attribute on form elements forces layout in CheckIfFocusable. To avoid unpleasant FOUCs, defer autofocus processing until frames are constructed in PresShell::Initialize. Resolve the race between nsAutoFocusEvent running and page load by checking the readystate at time of event posting. Skip autofocus if the element moved to a different window in the meantime. MozReview-Commit-ID: 90jiJYJWmRg
This commit is contained in:
@@ -1582,6 +1582,7 @@ nsDocument::nsDocument(const char* aContentType)
|
|||||||
, mDOMLoadingSet(false)
|
, mDOMLoadingSet(false)
|
||||||
, mDOMInteractiveSet(false)
|
, mDOMInteractiveSet(false)
|
||||||
, mDOMCompleteSet(false)
|
, mDOMCompleteSet(false)
|
||||||
|
, mAutoFocusFired(false)
|
||||||
{
|
{
|
||||||
SetContentTypeInternal(nsDependentCString(aContentType));
|
SetContentTypeInternal(nsDependentCString(aContentType));
|
||||||
|
|
||||||
@@ -9444,6 +9445,133 @@ nsDocument::GetTemplateContentsOwner()
|
|||||||
return mTemplateContentsOwner;
|
return mTemplateContentsOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static already_AddRefed<nsPIDOMWindowOuter>
|
||||||
|
FindTopWindowForElement(Element* element)
|
||||||
|
{
|
||||||
|
nsIDocument* document = element->OwnerDoc();
|
||||||
|
if (!document) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsPIDOMWindowOuter> window = document->GetWindow();
|
||||||
|
if (!window) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trying to find the top window (equivalent to window.top).
|
||||||
|
if (nsCOMPtr<nsPIDOMWindowOuter> top = window->GetTop()) {
|
||||||
|
window = top.forget();
|
||||||
|
}
|
||||||
|
return window.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nsAutoFocusEvent is used to dispatch a focus event for an
|
||||||
|
* nsGenericHTMLFormElement with the autofocus attribute enabled.
|
||||||
|
*/
|
||||||
|
class nsAutoFocusEvent : public Runnable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit nsAutoFocusEvent(already_AddRefed<Element>&& aElement,
|
||||||
|
already_AddRefed<nsPIDOMWindowOuter>&& aTopWindow)
|
||||||
|
: mozilla::Runnable("nsAutoFocusEvent")
|
||||||
|
, mElement(aElement)
|
||||||
|
, mTopWindow(aTopWindow)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHOD Run() override
|
||||||
|
{
|
||||||
|
nsCOMPtr<nsPIDOMWindowOuter> currentTopWindow =
|
||||||
|
FindTopWindowForElement(mElement);
|
||||||
|
if (currentTopWindow != mTopWindow) {
|
||||||
|
// The element's top window changed from when the event was queued.
|
||||||
|
// Don't take away focus from an unrelated window.
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
||||||
|
if (!fm) {
|
||||||
|
return NS_ERROR_NULL_POINTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsIDocument* document = mElement->OwnerDoc();
|
||||||
|
|
||||||
|
// Don't steal focus from the user.
|
||||||
|
if (mTopWindow->GetFocusedNode()) {
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If something is focused in the same document, ignore autofocus.
|
||||||
|
if (!fm->GetFocusedContent() ||
|
||||||
|
fm->GetFocusedContent()->OwnerDoc() != document) {
|
||||||
|
mozilla::ErrorResult rv;
|
||||||
|
mElement->Focus(rv);
|
||||||
|
return rv.StealNSResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
nsCOMPtr<Element> mElement;
|
||||||
|
nsCOMPtr<nsPIDOMWindowOuter> mTopWindow;
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
nsDocument::SetAutoFocusElement(Element* aAutoFocusElement)
|
||||||
|
{
|
||||||
|
if (mAutoFocusFired) {
|
||||||
|
// Too late.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mAutoFocusElement) {
|
||||||
|
// The spec disallows multiple autofocus elements, so we consider only the
|
||||||
|
// first one to preserve the old behavior.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mAutoFocusElement = do_GetWeakReference(aAutoFocusElement);
|
||||||
|
TriggerAutoFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
nsDocument::TriggerAutoFocus()
|
||||||
|
{
|
||||||
|
if (mAutoFocusFired) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mPresShell || !mPresShell->DidInitialize()) {
|
||||||
|
// Delay autofocus until frames are constructed so that we don't thrash
|
||||||
|
// style and layout calculations.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<Element> autoFocusElement = do_QueryReferent(mAutoFocusElement);
|
||||||
|
if (autoFocusElement && autoFocusElement->OwnerDoc() == this) {
|
||||||
|
mAutoFocusFired = true;
|
||||||
|
|
||||||
|
nsCOMPtr<nsPIDOMWindowOuter> topWindow =
|
||||||
|
FindTopWindowForElement(autoFocusElement);
|
||||||
|
if (!topWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: This may be removed in the future since the spec technically
|
||||||
|
// allows autofocus after load.
|
||||||
|
nsCOMPtr<nsIDocument> topDoc = topWindow->GetExtantDoc();
|
||||||
|
if (topDoc && topDoc->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsIRunnable> event =
|
||||||
|
new nsAutoFocusEvent(autoFocusElement.forget(), topWindow.forget());
|
||||||
|
nsresult rv = NS_DispatchToCurrentThread(event.forget());
|
||||||
|
NS_ENSURE_SUCCESS_VOID(rv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
nsDocument::SetScrollToRef(nsIURI *aDocumentURI)
|
nsDocument::SetScrollToRef(nsIURI *aDocumentURI)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -804,6 +804,9 @@ public:
|
|||||||
// Only BlockOnload should call this!
|
// Only BlockOnload should call this!
|
||||||
void AsyncBlockOnload();
|
void AsyncBlockOnload();
|
||||||
|
|
||||||
|
virtual void SetAutoFocusElement(Element* aAutoFocusElement) override;
|
||||||
|
virtual void TriggerAutoFocus() override;
|
||||||
|
|
||||||
virtual void SetScrollToRef(nsIURI *aDocumentURI) override;
|
virtual void SetScrollToRef(nsIURI *aDocumentURI) override;
|
||||||
virtual void ScrollToRef() override;
|
virtual void ScrollToRef() override;
|
||||||
virtual void ResetScrolledToRefAlready() override;
|
virtual void ResetScrolledToRefAlready() override;
|
||||||
@@ -1306,6 +1309,8 @@ private:
|
|||||||
|
|
||||||
RefPtr<nsContentList> mImageMaps;
|
RefPtr<nsContentList> mImageMaps;
|
||||||
|
|
||||||
|
nsWeakPtr mAutoFocusElement;
|
||||||
|
|
||||||
nsCString mScrollToRef;
|
nsCString mScrollToRef;
|
||||||
uint8_t mScrolledToRefAlready : 1;
|
uint8_t mScrolledToRefAlready : 1;
|
||||||
uint8_t mChangeScrollPosWhenScrollingToRef : 1;
|
uint8_t mChangeScrollPosWhenScrollingToRef : 1;
|
||||||
@@ -1356,6 +1361,7 @@ private:
|
|||||||
bool mDOMLoadingSet : 1;
|
bool mDOMLoadingSet : 1;
|
||||||
bool mDOMInteractiveSet : 1;
|
bool mDOMInteractiveSet : 1;
|
||||||
bool mDOMCompleteSet : 1;
|
bool mDOMCompleteSet : 1;
|
||||||
|
bool mAutoFocusFired : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
class nsDocumentOnStack
|
class nsDocumentOnStack
|
||||||
|
|||||||
@@ -1603,7 +1603,7 @@ nsFocusManager::CheckIfFocusable(nsIContent* aContent, uint32_t aFlags)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure that our frames are up to date while ensuring the presshell is
|
// Make sure that our frames are up to date while ensuring the presshell is
|
||||||
// also initialized in case we come from an autofocus event.
|
// also initialized in case we come from a script calling focus() early.
|
||||||
mEventHandlingNeedsFlush = false;
|
mEventHandlingNeedsFlush = false;
|
||||||
doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
|
doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
|
||||||
|
|
||||||
|
|||||||
@@ -2637,6 +2637,9 @@ public:
|
|||||||
|
|
||||||
virtual nsISupports* GetCurrentContentSink() = 0;
|
virtual nsISupports* GetCurrentContentSink() = 0;
|
||||||
|
|
||||||
|
virtual void SetAutoFocusElement(Element* aAutoFocusElement) = 0;
|
||||||
|
virtual void TriggerAutoFocus() = 0;
|
||||||
|
|
||||||
virtual void SetScrollToRef(nsIURI *aDocumentURI) = 0;
|
virtual void SetScrollToRef(nsIURI *aDocumentURI) = 0;
|
||||||
virtual void ScrollToRef() = 0;
|
virtual void ScrollToRef() = 0;
|
||||||
virtual void ResetScrolledToRefAlready() = 0;
|
virtual void ResetScrolledToRefAlready() = 0;
|
||||||
|
|||||||
@@ -111,64 +111,6 @@
|
|||||||
using namespace mozilla;
|
using namespace mozilla;
|
||||||
using namespace mozilla::dom;
|
using namespace mozilla::dom;
|
||||||
|
|
||||||
/**
|
|
||||||
* nsAutoFocusEvent is used to dispatch a focus event when a
|
|
||||||
* nsGenericHTMLFormElement is binded to the tree with the autofocus attribute
|
|
||||||
* enabled.
|
|
||||||
*/
|
|
||||||
class nsAutoFocusEvent : public Runnable
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit nsAutoFocusEvent(nsGenericHTMLFormElement* aElement)
|
|
||||||
: mozilla::Runnable("nsAutoFocusEvent")
|
|
||||||
, mElement(aElement)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHOD Run() override {
|
|
||||||
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
|
||||||
if (!fm) {
|
|
||||||
return NS_ERROR_NULL_POINTER;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsIDocument* document = mElement->OwnerDoc();
|
|
||||||
|
|
||||||
nsPIDOMWindowOuter* window = document->GetWindow();
|
|
||||||
if (!window) {
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trying to found the top window (equivalent to window.top).
|
|
||||||
if (nsCOMPtr<nsPIDOMWindowOuter> top = window->GetTop()) {
|
|
||||||
window = top;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window->GetFocusedNode()) {
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsCOMPtr<nsIDocument> topDoc = window->GetExtantDoc();
|
|
||||||
if (topDoc && topDoc->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE) {
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If something is focused in the same document, ignore autofocus.
|
|
||||||
if (!fm->GetFocusedContent() ||
|
|
||||||
fm->GetFocusedContent()->OwnerDoc() != document) {
|
|
||||||
mozilla::ErrorResult rv;
|
|
||||||
mElement->Focus(rv);
|
|
||||||
return rv.StealNSResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
// NOTE: nsGenericHTMLFormElement is saved as a nsGenericHTMLElement
|
|
||||||
// because AddRef/Release are ambiguous with nsGenericHTMLFormElement
|
|
||||||
// and Focus() is declared (and defined) in nsGenericHTMLElement class.
|
|
||||||
RefPtr<nsGenericHTMLElement> mElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
NS_IMPL_ADDREF_INHERITED(nsGenericHTMLElement, nsGenericHTMLElementBase)
|
NS_IMPL_ADDREF_INHERITED(nsGenericHTMLElement, nsGenericHTMLElementBase)
|
||||||
NS_IMPL_RELEASE_INHERITED(nsGenericHTMLElement, nsGenericHTMLElementBase)
|
NS_IMPL_RELEASE_INHERITED(nsGenericHTMLElement, nsGenericHTMLElementBase)
|
||||||
|
|
||||||
@@ -1882,10 +1824,8 @@ nsGenericHTMLFormElement::BindToTree(nsIDocument* aDocument,
|
|||||||
// the document should not be already loaded and the "browser.autofocus"
|
// the document should not be already loaded and the "browser.autofocus"
|
||||||
// preference should be 'true'.
|
// preference should be 'true'.
|
||||||
if (IsAutofocusable() && HasAttr(kNameSpaceID_None, nsGkAtoms::autofocus) &&
|
if (IsAutofocusable() && HasAttr(kNameSpaceID_None, nsGkAtoms::autofocus) &&
|
||||||
nsContentUtils::AutoFocusEnabled()) {
|
nsContentUtils::AutoFocusEnabled() && aDocument) {
|
||||||
nsCOMPtr<nsIRunnable> event = new nsAutoFocusEvent(this);
|
aDocument->SetAutoFocusElement(this);
|
||||||
rv = NS_DispatchToCurrentThread(event);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If @form is set, the element *has* to be in a document, otherwise it
|
// If @form is set, the element *has* to be in a document, otherwise it
|
||||||
|
|||||||
@@ -1817,6 +1817,8 @@ PresShell::Initialize(nscoord aWidth, nscoord aHeight)
|
|||||||
NS_ENSURE_STATE(!mHaveShutDown);
|
NS_ENSURE_STATE(!mHaveShutDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mDocument->TriggerAutoFocus();
|
||||||
|
|
||||||
NS_ASSERTION(rootFrame, "How did that happen?");
|
NS_ASSERTION(rootFrame, "How did that happen?");
|
||||||
|
|
||||||
// Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit
|
// Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit
|
||||||
|
|||||||
Reference in New Issue
Block a user