Bug 1379280 - Only do async keyboard scrolling for a selection, not a focused element. r=smaug

This commit changes async keyboard scrolling to be enabled only if the content to
scroll is from a selection. This works around the problem of detecting whether
an arbitrary element has key listeners that should prevent async key scrolling,
because when they have the focus we will have disabled async key scrolling.

MozReview-Commit-ID: 6HhSuGZNsMX

--HG--
extra : rebase_source : 98a6449dd1e913136ca66532a67df8e0bb717e52
This commit is contained in:
Ryan Hunt 2017-07-13 15:53:26 -05:00
parent 74d75aee90
commit f116107a57
7 changed files with 99 additions and 38 deletions

View File

@ -4126,6 +4126,12 @@ nsIDocument::GetRootElement() const
mCachedRootElement : GetRootElementInternal();
}
nsIContent*
nsIDocument::GetUnfocusedKeyEventTarget()
{
return GetRootElement();
}
Element*
nsDocument::GetRootElementInternal() const
{

View File

@ -938,6 +938,12 @@ public:
*/
Element* GetRootElement() const;
/**
* Gets the event target to dispatch key events to if there is no focused
* content in the document.
*/
virtual nsIContent* GetUnfocusedKeyEventTarget();
/**
* Retrieve information about the viewport as a data structure.
* This will return information in the viewport META data section

View File

@ -883,6 +883,15 @@ nsHTMLDocument::SetCompatibilityMode(nsCompatibility aMode)
}
}
nsIContent*
nsHTMLDocument::GetUnfocusedKeyEventTarget()
{
if (nsGenericHTMLElement* body = GetBody()) {
return body;
}
return nsDocument::GetUnfocusedKeyEventTarget();
}
//
// nsIDOMHTMLDocument interface implementation
//

View File

@ -74,6 +74,8 @@ public:
return mWriteLevel != uint32_t(0);
}
virtual nsIContent* GetUnfocusedKeyEventTarget() override;
virtual nsContentList* GetForms() override;
virtual nsContentList* GetFormControls() override;

View File

@ -94,8 +94,9 @@ FocusTarget::FocusTarget(nsIPresShell* aRootPresShell,
// Key events can be retargeted to a child PresShell when there is an iframe
nsCOMPtr<nsIPresShell> presShell = GetRetargetEventPresShell(aRootPresShell);
nsCOMPtr<nsIDocument> document = presShell->GetDocument();
if (!presShell) {
if (!presShell || !document) {
FT_LOG("Creating nil target with seq=%" PRIu64 " (can't find retargeted presshell)\n",
aFocusSequenceNumber);
@ -103,16 +104,31 @@ FocusTarget::FocusTarget(nsIPresShell* aRootPresShell,
return;
}
// Get the content that should be scrolled for this PresShell, which is
// the current focused element or the current DOM selection
nsCOMPtr<nsIContent> scrollTarget = presShell->GetContentForScrolling();
// Find the focused content and use it to determine whether there are key event
// listeners or whether key events will be targeted at a different process
// through a remote browser.
nsCOMPtr<nsIContent> focusedContent = presShell->GetFocusedContentInOurWindow();
// Collect event listener information so we can track what is potentially focus
// changing
mFocusHasKeyEventListeners = HasListenersForKeyEvents(scrollTarget);
// Check if there are key event listeners that could prevent default or change
// the focus or selection of the page.
mFocusHasKeyEventListeners =
HasListenersForKeyEvents(focusedContent ? focusedContent.get()
: document->GetUnfocusedKeyEventTarget());
// Check if the scroll target is a remote browser
if (TabParent* browserParent = TabParent::GetFrom(scrollTarget)) {
// Check if the focused element is content editable or if the document
// is in design mode.
if (IsEditableNode(focusedContent) ||
IsEditableNode(document)) {
FT_LOG("Creating nil target with seq=%" PRIu64 ", kl=%d (disabling for editable node)\n",
aFocusSequenceNumber,
static_cast<int>(mFocusHasKeyEventListeners));
mType = FocusTarget::eNone;
return;
}
// Check if the focused element is a remote browser
if (TabParent* browserParent = TabParent::GetFrom(focusedContent)) {
RenderFrameParent* rfp = browserParent->GetRenderFrame();
// The globally focused element for scrolling is in a remote layer tree
@ -135,24 +151,29 @@ FocusTarget::FocusTarget(nsIPresShell* aRootPresShell,
return;
}
// If the focus isn't on a remote browser then check for scrollable targets
if (IsEditableNode(scrollTarget) ||
IsEditableNode(presShell->GetDocument())) {
FT_LOG("Creating nil target with seq=%" PRIu64 ", kl=%d (disabling for editable node)\n",
aFocusSequenceNumber,
mFocusHasKeyEventListeners);
// The content to scroll is either the focused element or the focus node of
// the selection. It's difficult to determine if an element is an interactive
// element requiring async keyboard scrolling to be disabled. So we only
// allow async key scrolling based on the selection, which doesn't have
// this problem and is more common.
if (focusedContent) {
FT_LOG("Creating nil target with seq=%" PRIu64 ", kl=%d (disabling for focusing an element)\n",
mFocusHasKeyEventListeners,
aFocusSequenceNumber);
mType = FocusTarget::eNone;
return;
}
nsCOMPtr<nsIContent> selectedContent = presShell->GetSelectedContentForScrolling();
// Gather the scrollable frames that would be scrolled in each direction
// for this scroll target
nsIScrollableFrame* horizontal =
presShell->GetScrollableFrameToScrollForContent(scrollTarget.get(),
presShell->GetScrollableFrameToScrollForContent(selectedContent.get(),
nsIPresShell::eHorizontal);
nsIScrollableFrame* vertical =
presShell->GetScrollableFrameToScrollForContent(scrollTarget.get(),
presShell->GetScrollableFrameToScrollForContent(selectedContent.get(),
nsIPresShell::eVertical);
// We might have the globally focused element for scrolling. Gather a ViewID for

View File

@ -2840,24 +2840,26 @@ PresShell::FrameNeedsToContinueReflow(nsIFrame *aFrame)
already_AddRefed<nsIContent>
nsIPresShell::GetContentForScrolling() const
{
nsCOMPtr<nsIContent> focusedContent;
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm && mDocument) {
nsCOMPtr<nsIDOMElement> focusedElement;
fm->GetFocusedElementForWindow(mDocument->GetWindow(), false, nullptr,
getter_AddRefs(focusedElement));
focusedContent = do_QueryInterface(focusedElement);
if (nsCOMPtr<nsIContent> focused = GetFocusedContentInOurWindow()) {
return focused.forget();
}
if (!focusedContent && mSelection) {
return GetSelectedContentForScrolling();
}
already_AddRefed<nsIContent>
nsIPresShell::GetSelectedContentForScrolling() const
{
nsCOMPtr<nsIContent> selectedContent;
if (mSelection) {
nsISelection* domSelection =
mSelection->GetSelection(SelectionType::eNormal);
if (domSelection) {
nsCOMPtr<nsIDOMNode> focusedNode;
domSelection->GetFocusNode(getter_AddRefs(focusedNode));
focusedContent = do_QueryInterface(focusedNode);
selectedContent = do_QueryInterface(focusedNode);
}
}
return focusedContent.forget();
return selectedContent.forget();
}
nsIScrollableFrame*
@ -6785,6 +6787,20 @@ PresShell::GetFocusedDOMWindowInOurWindow()
return focusedWindow.forget();
}
already_AddRefed<nsIContent>
nsIPresShell::GetFocusedContentInOurWindow() const
{
nsCOMPtr<nsIContent> focusedContent;
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm && mDocument) {
nsCOMPtr<nsIDOMElement> focusedElement;
fm->GetFocusedElementForWindow(mDocument->GetWindow(), false, nullptr,
getter_AddRefs(focusedElement));
focusedContent = do_QueryInterface(focusedElement);
}
return focusedContent.forget();
}
already_AddRefed<nsIPresShell>
PresShell::GetParentPresShellForEventHandling()
{
@ -7764,17 +7780,7 @@ PresShell::HandleEvent(nsIFrame* aFrame,
// still get sent to the window properly if nothing is focused or if a
// frame goes away while it is focused.
if (!eventTarget || !eventTarget->GetPrimaryFrame()) {
nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(mDocument);
if (htmlDoc) {
nsCOMPtr<nsIDOMHTMLElement> body;
htmlDoc->GetBody(getter_AddRefs(body));
eventTarget = do_QueryInterface(body);
if (!eventTarget) {
eventTarget = mDocument->GetRootElement();
}
} else {
eventTarget = mDocument->GetRootElement();
}
eventTarget = mDocument->GetUnfocusedKeyEventTarget();
}
if (aEvent->mMessage == eKeyDown) {

View File

@ -434,6 +434,12 @@ public:
*/
already_AddRefed<nsIContent> GetContentForScrolling() const;
/**
* Get the DOM selection that should be the target for scrolling, if there
* is no focused content.
*/
already_AddRefed<nsIContent> GetSelectedContentForScrolling() const;
/**
* Gets nearest scrollable frame from the specified content node. The frame
* is scrollable with overflow:scroll or overflow:auto in some direction when
@ -1442,6 +1448,11 @@ public:
*/
virtual already_AddRefed<nsPIDOMWindowOuter> GetFocusedDOMWindowInOurWindow() = 0;
/**
* Get the focused content under this window.
*/
already_AddRefed<nsIContent> GetFocusedContentInOurWindow() const;
/**
* Get the layer manager for the widget of the root view, if it has
* one.