#409, #411: M1371620 M1367906 M1366218+backbugs M1358453 M1366250

This commit is contained in:
Cameron Kaiser 2017-07-01 12:15:04 -07:00
parent 467573125f
commit d22246efad
10 changed files with 155 additions and 19 deletions

View File

@ -173,6 +173,7 @@ static const char* kObservedPrefs[] = {
};
nsFocusManager::nsFocusManager()
: mEventHandlingNeedsFlush(false)
{ }
nsFocusManager::~nsFocusManager()
@ -1522,6 +1523,7 @@ nsFocusManager::CheckIfFocusable(nsIContent* aContent, uint32_t aFlags)
}
// Make sure that our frames are up to date
mEventHandlingNeedsFlush = false;
doc->FlushPendingNotifications(Flush_Layout);
nsIPresShell *shell = doc->GetShell();

View File

@ -8,6 +8,7 @@
#define nsFocusManager_h___
#include "nsCycleCollectionParticipant.h"
#include "nsIContent.h"
#include "nsIDocument.h"
#include "nsIFocusManager.h"
#include "nsIObserver.h"
@ -93,6 +94,29 @@ public:
return handlingDocument.forget();
}
void NeedsFlushBeforeEventHandling(nsIContent* aContent)
{
if (mFocusedContent == aContent) {
mEventHandlingNeedsFlush = true;
}
}
bool CanSkipFocus(nsIContent* aContent)
{
return mFocusedContent == aContent;
}
void FlushBeforeEventHandlingIfNeeded(nsIContent* aContent)
{
if (mEventHandlingNeedsFlush) {
nsCOMPtr<nsIDocument> doc = aContent->GetComposedDoc();
if (doc) {
mEventHandlingNeedsFlush = false;
doc->FlushPendingNotifications(Flush_Layout);
}
}
}
/**
* Update the caret with current mode (whether in caret browsing mode or not).
*/
@ -513,6 +537,10 @@ private:
// moving focus.
nsCOMPtr<nsIDocument> mMouseButtonEventHandlingDocument;
// If set to true, layout of the document of the event target should be
// flushed before handling focus depending events.
bool mEventHandlingNeedsFlush;
static bool sTestMode;
// the single focus manager

View File

@ -1465,6 +1465,10 @@ HTMLInputElement::GetValueInternal(nsAString& aValue) const
bool
HTMLInputElement::IsValueEmpty() const
{
if (GetValueMode() == VALUE_MODE_VALUE && IsSingleLineTextControl(false)) {
return !mInputData.mState->HasNonEmptyValue();
}
nsAutoString value;
GetValueInternal(value);
@ -4277,6 +4281,14 @@ HTMLInputElement::UnbindFromTree(bool aDeep, bool aNullParent)
void
HTMLInputElement::HandleTypeChange(uint8_t aNewType)
{
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
// Input element can represent very different kinds of UIs, and we may
// need to flush styling even when focusing the already focused input
// element.
fm->NeedsFlushBeforeEventHandling(this);
}
if (mType == NS_FORM_INPUT_RANGE && mIsDraggingRange) {
CancelRangeThumbDrag(false);
}

View File

@ -2668,9 +2668,12 @@ nsGenericHTMLElement::Blur(mozilla::ErrorResult& aError)
void
nsGenericHTMLElement::Focus(ErrorResult& aError)
{
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
aError = fm->SetFocus(this, 0);
if (fm->CanSkipFocus(this))
fm->NeedsFlushBeforeEventHandling(this);
else
aError = fm->SetFocus(this, 0);
}
}

View File

@ -2174,6 +2174,23 @@ nsTextEditorState::SetValue(const nsAString& aValue, uint32_t aFlags)
return true;
}
bool
nsTextEditorState::HasNonEmptyValue()
{
if (mEditor && mBoundFrame && mEditorInitialized &&
!mIsCommittingComposition) {
bool empty;
nsresult rv = mEditor->GetDocumentIsEmpty(&empty);
if (NS_SUCCEEDED(rv)) {
return !empty;
}
}
nsAutoString value;
GetValue(value, true);
return !value.IsEmpty();
}
void
nsTextEditorState::InitializeKeyboardEventListeners()
{

View File

@ -158,6 +158,11 @@ public:
MOZ_WARN_UNUSED_RESULT bool SetValue(const nsAString& aValue,
uint32_t aFlags);
void GetValue(nsAString& aValue, bool aIgnoreWrap) const;
bool HasNonEmptyValue();
// The following methods are for textarea element to use whether default
// value or not.
// XXX We might have to add assertion when it is into editable,
// or reconsider fixing bug 597525 to remove these.
void EmptyValue() { if (mValue) mValue->Truncate(); }
bool IsEmpty() const { return mValue ? mValue->IsEmpty() : true; }

View File

@ -7815,6 +7815,13 @@ PresShell::HandleEventInternal(WidgetEvent* aEvent,
bool touchIsNew = false;
bool isHandlingUserInput = false;
if (mCurrentEventContent && aEvent->IsTargetedAtFocusedWindow()) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
fm->FlushBeforeEventHandlingIfNeeded(mCurrentEventContent);
}
}
// XXX How about IME events and input events for plugins?
if (aEvent->mFlags.mIsTrusted) {
switch (aEvent->mMessage) {

View File

@ -396,6 +396,7 @@ nsFrame::nsFrame(nsStyleContext* aContext)
MOZ_COUNT_CTOR(nsFrame);
mState = NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY;
mMayHaveRoundedCorners = false;
mStyleContext = aContext;
mStyleContext->AddRef();
#ifdef DEBUG
@ -876,6 +877,7 @@ nsFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
NS_FRAME_SIMPLE_DISPLAYLIST);
*/
RemoveStateBits(NS_FRAME_SIMPLE_DISPLAYLIST);
mMayHaveRoundedCorners = true;
}
// MSVC fails with link error "one or more multiply defined symbols found",
@ -1289,6 +1291,11 @@ nsIFrame::OutsetBorderRadii(nscoord aRadii[8], const nsMargin &aOffsets)
nsIFrame::GetBorderRadii(const nsSize& aFrameSize, const nsSize& aBorderArea,
Sides aSkipSides, nscoord aRadii[8]) const
{
if (!mMayHaveRoundedCorners) {
memset(aRadii, 0, sizeof(nscoord) * 8);
return false;
}
if (IsThemed()) {
// When we're themed, the native theme code draws the border and
// background, and therefore it doesn't make sense to tell other
@ -1302,9 +1309,12 @@ nsIFrame::GetBorderRadii(const nsSize& aFrameSize, const nsSize& aBorderArea,
}
return false;
}
return ComputeBorderRadii(StyleBorder()->mBorderRadius,
aFrameSize, aBorderArea,
aSkipSides, aRadii);
const_cast<nsIFrame*>(this)->mMayHaveRoundedCorners =
ComputeBorderRadii(StyleBorder()->mBorderRadius,
aFrameSize, aBorderArea,
aSkipSides, aRadii);
return mMayHaveRoundedCorners;
}
bool

View File

@ -3152,6 +3152,8 @@ protected:
VisualDeltas mVisualDeltas;
} mOverflow;
bool mMayHaveRoundedCorners : 1;
// Helpers
/**
* Can we stop inside this frame when we're skipping non-rendered whitespace?

View File

@ -196,10 +196,39 @@
<field name="_scrollTarget">null</field>
<method name="_boundsWithoutFlushing">
<parameter name="element"/>
<body><![CDATA[
if (!("_DOMWindowUtils" in this)) {
try {
this._DOMWindowUtils =
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
} catch (e) {
// Can't access nsIDOMWindowUtils if we're unprivileged.
this._DOMWindowUtils = null;
}
}
return this._DOMWindowUtils ?
this._DOMWindowUtils.getBoundsWithoutFlushing(element) :
element.getBoundingClientRect();
]]></body>
</method>
<method name="_canScrollToElement">
<parameter name="element"/>
<body><![CDATA[
return window.getComputedStyle(element).display != "none";
if (element.hidden) {
return false;
}
// See if the element is hidden via CSS without the hidden attribute.
// If we get only zeros for the client rect, this means the element
// is hidden. As a performance optimization, we don't flush layout
// here which means that on the fly changes aren't fully supported.
let rect = this._boundsWithoutFlushing(element);
return !!(rect.top || rect.left || rect.width || rect.height);
]]></body>
</method>
@ -458,29 +487,29 @@
</method>
<method name="_updateScrollButtonsDisabledState">
<parameter name="aScrolling"/>
<body><![CDATA[
var scrolledToStart = false;
var scrolledToEnd = false;
let scrolledToStart;
let scrolledToEnd;
// Avoid flushing layout when not overflowing or when scrolling.
if (this.hasAttribute("notoverflowing")) {
scrolledToStart = true;
scrolledToEnd = true;
}
else if (this.scrollPosition == 0) {
} else if (aScrolling) {
scrolledToStart = false;
scrolledToEnd = false;
} else if (this.scrollPosition == 0) {
// In the RTL case, this means the _last_ element in the
// scrollbox is visible
if (this._isRTLScrollbox)
scrolledToEnd = true;
else
scrolledToStart = true;
scrolledToEnd = this._isRTLScrollbox;
scrolledToStart = !this._isRTLScrollbox;
}
else if (this.scrollClientSize + this.scrollPosition == this.scrollSize) {
// In the RTL case, this means the _first_ element in the
// scrollbox is visible
if (this._isRTLScrollbox)
scrolledToStart = true;
else
scrolledToEnd = true;
scrolledToStart = this._isRTLScrollbox;
scrolledToEnd = !this._isRTLScrollbox;
}
if (scrolledToEnd)
@ -600,8 +629,29 @@
this.setAttribute("notoverflowing", "true");
}
]]></handler>
<handler event="scroll"><![CDATA[
if (!this._delayedUpdateScrollButtonsTimer) {
// This is the beginning of a scrolling animation. We need to update
// scroll buttons now in case we were scrolled to the start or to the
// end before we started scrolling.
this._updateScrollButtonsDisabledState(true);
} else {
// We're in the middle of the scrolling animation. We'll restart the
// delayed update request so that we only update the scroll buttons
// a second time once we're done scrolling.
window.clearTimeout(this._delayedUpdateScrollButtonsTimer);
}
<handler event="scroll" action="this._updateScrollButtonsDisabledState()"/>
// Try to detect the end of the scrolling animation to update the
// scroll buttons again. To avoid false positives, this timeout needs
// to be big enough to account for intermediate frames that don't move
// the scroll position in case we're scrolling slowly.
this._delayedUpdateScrollButtonsTimer = setTimeout(() => {
// Scrolling animation has finished.
this._delayedUpdateScrollButtonsTimer = 0;
this._updateScrollButtonsDisabledState();
}, 500);
]]></handler>
</handlers>
</binding>