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)
|
||||
, mDOMInteractiveSet(false)
|
||||
, mDOMCompleteSet(false)
|
||||
, mAutoFocusFired(false)
|
||||
{
|
||||
SetContentTypeInternal(nsDependentCString(aContentType));
|
||||
|
||||
@@ -9444,6 +9445,133 @@ nsDocument::GetTemplateContentsOwner()
|
||||
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
|
||||
nsDocument::SetScrollToRef(nsIURI *aDocumentURI)
|
||||
{
|
||||
|
||||
@@ -804,6 +804,9 @@ public:
|
||||
// Only BlockOnload should call this!
|
||||
void AsyncBlockOnload();
|
||||
|
||||
virtual void SetAutoFocusElement(Element* aAutoFocusElement) override;
|
||||
virtual void TriggerAutoFocus() override;
|
||||
|
||||
virtual void SetScrollToRef(nsIURI *aDocumentURI) override;
|
||||
virtual void ScrollToRef() override;
|
||||
virtual void ResetScrolledToRefAlready() override;
|
||||
@@ -1306,6 +1309,8 @@ private:
|
||||
|
||||
RefPtr<nsContentList> mImageMaps;
|
||||
|
||||
nsWeakPtr mAutoFocusElement;
|
||||
|
||||
nsCString mScrollToRef;
|
||||
uint8_t mScrolledToRefAlready : 1;
|
||||
uint8_t mChangeScrollPosWhenScrollingToRef : 1;
|
||||
@@ -1356,6 +1361,7 @@ private:
|
||||
bool mDOMLoadingSet : 1;
|
||||
bool mDOMInteractiveSet : 1;
|
||||
bool mDOMCompleteSet : 1;
|
||||
bool mAutoFocusFired : 1;
|
||||
};
|
||||
|
||||
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
|
||||
// also initialized in case we come from an autofocus event.
|
||||
// also initialized in case we come from a script calling focus() early.
|
||||
mEventHandlingNeedsFlush = false;
|
||||
doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
|
||||
|
||||
|
||||
@@ -2637,6 +2637,9 @@ public:
|
||||
|
||||
virtual nsISupports* GetCurrentContentSink() = 0;
|
||||
|
||||
virtual void SetAutoFocusElement(Element* aAutoFocusElement) = 0;
|
||||
virtual void TriggerAutoFocus() = 0;
|
||||
|
||||
virtual void SetScrollToRef(nsIURI *aDocumentURI) = 0;
|
||||
virtual void ScrollToRef() = 0;
|
||||
virtual void ResetScrolledToRefAlready() = 0;
|
||||
|
||||
@@ -111,64 +111,6 @@
|
||||
using namespace mozilla;
|
||||
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_RELEASE_INHERITED(nsGenericHTMLElement, nsGenericHTMLElementBase)
|
||||
|
||||
@@ -1882,10 +1824,8 @@ nsGenericHTMLFormElement::BindToTree(nsIDocument* aDocument,
|
||||
// the document should not be already loaded and the "browser.autofocus"
|
||||
// preference should be 'true'.
|
||||
if (IsAutofocusable() && HasAttr(kNameSpaceID_None, nsGkAtoms::autofocus) &&
|
||||
nsContentUtils::AutoFocusEnabled()) {
|
||||
nsCOMPtr<nsIRunnable> event = new nsAutoFocusEvent(this);
|
||||
rv = NS_DispatchToCurrentThread(event);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsContentUtils::AutoFocusEnabled() && aDocument) {
|
||||
aDocument->SetAutoFocusElement(this);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
mDocument->TriggerAutoFocus();
|
||||
|
||||
NS_ASSERTION(rootFrame, "How did that happen?");
|
||||
|
||||
// Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit
|
||||
|
||||
Reference in New Issue
Block a user