Bug 981248 - Rewrite <input type=number> to avoid an anonymous input. r=masayuki,surkov,jwatt,ntim,jfkthame,smaug

Instead, subclass nsTextControlFrame. This simplifies the code and avoids
correctness issues.

I kept the localization functionality though it is not spec compliant. But I
filed a bug to remove it in a followup.

Differential Revision: https://phabricator.services.mozilla.com/D57193

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Emilio Cobos Álvarez 2020-01-14 19:01:05 +00:00
Родитель 12bcd1cab8
Коммит fff6c9e1ac
45 изменённых файлов: 367 добавлений и 1169 удалений

Просмотреть файл

@ -492,7 +492,7 @@ ROLE(SPINBUTTON,
NSAccessibilityIncrementorRole, //Subroles: Increment/Decrement.
ROLE_SYSTEM_SPINBUTTON,
ROLE_SYSTEM_SPINBUTTON,
java::SessionAccessibility::CLASSNAME_VIEW, // A composite widget
java::SessionAccessibility::CLASSNAME_EDITTEXT,
eNameFromValueRule)
ROLE(DIAGRAM,

Просмотреть файл

@ -466,7 +466,7 @@ Accessible* HTMLFileInputAccessible::CurrentItem() const {
role HTMLSpinnerAccessible::NativeRole() const { return roles::SPINBUTTON; }
void HTMLSpinnerAccessible::Value(nsString& aValue) const {
AccessibleWrap::Value(aValue);
HTMLTextFieldAccessible::Value(aValue);
if (!aValue.IsEmpty()) return;
// Pass NonSystem as the caller type, to be safe. We don't expect to have a
@ -475,28 +475,28 @@ void HTMLSpinnerAccessible::Value(nsString& aValue) const {
}
double HTMLSpinnerAccessible::MaxValue() const {
double value = AccessibleWrap::MaxValue();
double value = HTMLTextFieldAccessible::MaxValue();
if (!IsNaN(value)) return value;
return HTMLInputElement::FromNode(mContent)->GetMaximum().toDouble();
}
double HTMLSpinnerAccessible::MinValue() const {
double value = AccessibleWrap::MinValue();
double value = HTMLTextFieldAccessible::MinValue();
if (!IsNaN(value)) return value;
return HTMLInputElement::FromNode(mContent)->GetMinimum().toDouble();
}
double HTMLSpinnerAccessible::Step() const {
double value = AccessibleWrap::Step();
double value = HTMLTextFieldAccessible::Step();
if (!IsNaN(value)) return value;
return HTMLInputElement::FromNode(mContent)->GetStep().toDouble();
}
double HTMLSpinnerAccessible::CurValue() const {
double value = AccessibleWrap::CurValue();
double value = HTMLTextFieldAccessible::CurValue();
if (!IsNaN(value)) return value;
return HTMLInputElement::FromNode(mContent)->GetValueAsDecimal().toDouble();

Просмотреть файл

@ -63,7 +63,7 @@ class HTMLButtonAccessible : public HyperTextAccessibleWrap {
* Accessible for HTML input@type="text", input@type="password", textarea and
* other HTML text controls.
*/
class HTMLTextFieldAccessible final : public HyperTextAccessibleWrap {
class HTMLTextFieldAccessible : public HyperTextAccessibleWrap {
public:
enum { eAction_Click = 0 };
@ -99,8 +99,7 @@ class HTMLTextFieldAccessible final : public HyperTextAccessibleWrap {
virtual ENameValueFlag NativeName(nsString& aName) const override;
/**
* Return a widget element this input is part of, for example, search-textbox
* or HTML:input@type="number".
* Return a widget element this input is part of, for example, search-textbox.
*
* FIXME: This should probably be renamed.
*/
@ -129,10 +128,10 @@ class HTMLFileInputAccessible : public HyperTextAccessibleWrap {
/**
* Used for HTML input@type="number".
*/
class HTMLSpinnerAccessible : public AccessibleWrap {
class HTMLSpinnerAccessible final : public HTMLTextFieldAccessible {
public:
HTMLSpinnerAccessible(nsIContent* aContent, DocAccessible* aDoc)
: AccessibleWrap(aContent, aDoc) {
: HTMLTextFieldAccessible(aContent, aDoc) {
mStateFlags |= eHasNumericValue;
}

Просмотреть файл

@ -124,7 +124,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=558036
testAttrs("search", {"text-input-type": "search"}, true);
testAttrs("tel", {"text-input-type": "tel"}, true);
testAttrs("url", {"text-input-type": "url"}, true);
testAttrs(getAccessible("number").firstChild, {"text-input-type": "number"}, true);
testAttrs("number", {"text-input-type": "number"}, true);
// ARIA
testAttrs("searchbox", {"text-input-type": "search"}, true);

Просмотреть файл

@ -712,15 +712,7 @@
role: ROLE_SPINBUTTON,
interfaces: [ nsIAccessibleValue ],
children: [
{
role: ROLE_ENTRY,
extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE,
actions: "activate",
interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ],
children: [
{ role: ROLE_TEXT_LEAF },
],
},
{ role: ROLE_TEXT_LEAF },
{
role: ROLE_PUSHBUTTON,
actions: "press",

Просмотреть файл

@ -64,7 +64,6 @@
// input@type="number"
accTree =
{ SPINBUTTON: [
{ ENTRY: [ ] },
{ PUSHBUTTON: [ ] },
{ PUSHBUTTON: [ ] },
] };

Просмотреть файл

@ -59,7 +59,7 @@
<html:input id="networkProxyHTTP" type="text" style="-moz-box-flex: 1;"
preference="network.proxy.http"/>
<label data-l10n-id="connection-proxy-http-port" control="networkProxyHTTP_Port" />
<html:input id="networkProxyHTTP_Port" class="proxy-port-input input-number-mozbox" hidespinbuttons="true" type="number" min="0" max="65535"
<html:input id="networkProxyHTTP_Port" class="proxy-port-input" hidespinbuttons="true" type="number" min="0" max="65535"
preference="network.proxy.http_port"/>
</hbox>
<hbox/>
@ -73,7 +73,7 @@
<hbox align="center">
<html:input id="networkProxySSL" type="text" style="-moz-box-flex: 1;" preference="network.proxy.ssl"/>
<label data-l10n-id="connection-proxy-ssl-port" control="networkProxySSL_Port" />
<html:input id="networkProxySSL_Port" class="proxy-port-input input-number-mozbox" hidespinbuttons="true" type="number" min="0" max="65535" size="5"
<html:input id="networkProxySSL_Port" class="proxy-port-input" hidespinbuttons="true" type="number" min="0" max="65535" size="5"
preference="network.proxy.ssl_port"/>
</hbox>
<hbox pack="end">
@ -82,7 +82,7 @@
<hbox align="center">
<html:input id="networkProxyFTP" type="text" style="-moz-box-flex: 1;" preference="network.proxy.ftp"/>
<label data-l10n-id="connection-proxy-ftp-port" control="networkProxyFTP_Port"/>
<html:input id="networkProxyFTP_Port" class="proxy-port-input input-number-mozbox" hidespinbuttons="true" type="number" min="0" max="65535" size="5"
<html:input id="networkProxyFTP_Port" class="proxy-port-input" hidespinbuttons="true" type="number" min="0" max="65535" size="5"
preference="network.proxy.ftp_port"/>
</hbox>
<separator class="thin"/>
@ -92,7 +92,7 @@
<hbox align="center">
<html:input id="networkProxySOCKS" type="text" style="-moz-box-flex: 1;" preference="network.proxy.socks"/>
<label data-l10n-id="connection-proxy-socks-port" control="networkProxySOCKS_Port"/>
<html:input id="networkProxySOCKS_Port" class="proxy-port-input input-number-mozbox" hidespinbuttons="true" type="number" min="0" max="65535" size="5"
<html:input id="networkProxySOCKS_Port" class="proxy-port-input" hidespinbuttons="true" type="number" min="0" max="65535" size="5"
preference="network.proxy.socks_port"/>
</hbox>
<spacer/>

Просмотреть файл

@ -60,7 +60,6 @@
#include "nsITextControlFrame.h"
#include "nsCommandManager.h"
#include "nsCommandParams.h"
#include "nsNumberControlFrame.h"
#include "nsUnicharUtils.h"
#include "nsContentList.h"
#include "nsCSSPseudoElements.h"
@ -12344,9 +12343,7 @@ already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
nsITextControlFrame* textFrame = do_QueryFrame(nonanon->GetPrimaryFrame());
nsNumberControlFrame* numberFrame =
do_QueryFrame(nonanon->GetPrimaryFrame());
if (textFrame || numberFrame) {
if (textFrame) {
// If the anonymous content node has a child, then we need to make sure
// that we get the appropriate child, as otherwise the offset may not be
// correct when we construct a range for it.

Просмотреть файл

@ -37,7 +37,6 @@
#include "BrowserChild.h"
#include "nsFrameLoader.h"
#include "nsHTMLDocument.h"
#include "nsNumberControlFrame.h"
#include "nsNetUtil.h"
#include "nsRange.h"
@ -316,24 +315,6 @@ Element* nsFocusManager::GetFocusedDescendant(
// static
Element* nsFocusManager::GetRedirectedFocus(nsIContent* aContent) {
// For input number, redirect focus to our anonymous text control.
if (aContent->IsHTMLElement(nsGkAtoms::input)) {
bool typeIsNumber =
static_cast<dom::HTMLInputElement*>(aContent)->ControlType() ==
NS_FORM_INPUT_NUMBER;
if (typeIsNumber) {
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(aContent->GetPrimaryFrame());
if (numberControlFrame) {
HTMLInputElement* textControl =
numberControlFrame->GetAnonTextControl();
return textControl;
}
}
}
#ifdef MOZ_XUL
if (aContent->IsXULElement()) {
nsCOMPtr<nsIDOMXULMenuListElement> menulist =
@ -1479,8 +1460,7 @@ Element* nsFocusManager::FlushAndCheckIfFocusable(Element* aElement,
// this is a special case for some XUL elements or input number, where an
// anonymous child is actually focusable and not the element itself.
RefPtr<Element> redirectedFocus = GetRedirectedFocus(aElement);
if (redirectedFocus) {
if (RefPtr<Element> redirectedFocus = GetRedirectedFocus(aElement)) {
return FlushAndCheckIfFocusable(redirectedFocus, aFlags);
}

Просмотреть файл

@ -1580,7 +1580,7 @@ void EventStateManager::FireContextClick() {
if (formCtrl) {
allowedToDispatch =
formCtrl->IsTextOrNumberControl(/*aExcludePassword*/ false) ||
formCtrl->IsTextControl(/*aExcludePassword*/ false) ||
formCtrl->ControlType() == NS_FORM_INPUT_FILE;
} else if (mGestureDownContent->IsAnyOfHTMLElements(
nsGkAtoms::embed, nsGkAtoms::object, nsGkAtoms::label)) {

Просмотреть файл

@ -1122,7 +1122,7 @@ void IMEStateManager::SetInputContextForChildProcess(
SetInputContext(widget, aInputContext, aAction);
}
static bool IsNextFocusableElementTextOrNumberControl(Element* aInputContent) {
static bool IsNextFocusableElementTextControl(Element* aInputContent) {
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (!fm) {
return false;
@ -1136,7 +1136,7 @@ static bool IsNextFocusableElementTextOrNumberControl(Element* aInputContent) {
}
nextContent = nextContent->FindFirstNonChromeOnlyAccessContent();
nsCOMPtr<nsIFormControl> nextControl = do_QueryInterface(nextContent);
if (!nextControl || !nextControl->IsTextOrNumberControl(false)) {
if (!nextControl || !nextControl->IsTextControl(false)) {
return false;
}
@ -1217,8 +1217,7 @@ static void GetActionHint(nsIContent& aContent, nsAString& aActionHint) {
if (!isLastElement && formElement) {
// If next tabbable content in form is text control, hint should be "next"
// even there is submit in form.
if (IsNextFocusableElementTextOrNumberControl(
inputContent->AsElement())) {
if (IsNextFocusableElementTextControl(inputContent->AsElement())) {
// This is focusable text control
// XXX What good hint for read only field?
aActionHint.AssignLiteral("next");
@ -1274,22 +1273,8 @@ void IMEStateManager::SetIMEState(const IMEState& aState,
if (aContent &&
aContent->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) {
if (!aContent->IsHTMLElement(nsGkAtoms::textarea)) {
// <input type=number> has an anonymous <input type=text> descendant
// that gets focus whenever anyone tries to focus the number control. We
// need to check if aContent is one of those anonymous text controls and,
// if so, use the number control instead:
Element* element = aContent->AsElement();
HTMLInputElement* inputElement =
HTMLInputElement::FromNodeOrNull(aContent);
if (inputElement) {
HTMLInputElement* ownerNumberControl =
inputElement->GetOwnerNumberControl();
if (ownerNumberControl) {
element = ownerNumberControl; // an <input type=number>
}
}
element->GetAttr(kNameSpaceID_None, nsGkAtoms::type,
context.mHTMLInputType);
aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::type,
context.mHTMLInputType);
} else {
context.mHTMLInputType.Assign(nsGkAtoms::textarea->GetUTF16String());
}

Просмотреть файл

@ -1754,7 +1754,7 @@ bool HTMLFormElement::ImplicitSubmissionIsDisabled() const {
uint32_t numDisablingControlsFound = 0;
uint32_t length = mControls->mElements.Length();
for (uint32_t i = 0; i < length && numDisablingControlsFound < 2; ++i) {
if (mControls->mElements[i]->IsSingleLineTextOrNumberControl(false)) {
if (mControls->mElements[i]->IsSingleLineTextControl(false)) {
numDisablingControlsFound++;
}
}
@ -1767,7 +1767,7 @@ bool HTMLFormElement::IsLastActiveElement(
for (auto* element : Reversed(mControls->mElements)) {
// XXX How about date/time control?
if (element->IsTextOrNumberControl(false) && !element->IsDisabled()) {
if (element->IsTextControl(false) && !element->IsDisabled()) {
return element == aControl;
}
}

Просмотреть файл

@ -128,12 +128,11 @@ namespace dom {
// (1 << 11 is unused)
#define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
#define NS_PRE_HANDLE_BLUR_EVENT (1 << 13)
#define NS_PRE_HANDLE_INPUT_EVENT (1 << 14)
#define NS_IN_SUBMIT_CLICK (1 << 15)
#define NS_CONTROL_TYPE(bits) \
((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | \
NS_ORIGINAL_INDETERMINATE_VALUE | NS_PRE_HANDLE_BLUR_EVENT | \
NS_PRE_HANDLE_INPUT_EVENT | NS_IN_SUBMIT_CLICK))
NS_IN_SUBMIT_CLICK))
// whether textfields should be selected once focused:
// -1: no, 1: yes, 0: uninitialized
@ -1301,15 +1300,11 @@ nsresult HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
SetDirectionFromValue(aNotify);
} else if (aName == nsGkAtoms::lang) {
// FIXME(emilio, bug 1605158): This doesn't account for lang changes on
// ancestors.
if (mType == NS_FORM_INPUT_NUMBER) {
// Update the value that is displayed to the user to the new locale:
nsAutoString value;
GetNonFileValueInternal(value);
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
numberControlFrame->SetValueOfAnonTextControl(value);
}
// The validity of our value may have changed based on the locale.
UpdateValidityState();
}
} else if (aName == nsGkAtoms::autocomplete) {
// Clear the cached @autocomplete attribute and autocompleteInfo state.
@ -1436,12 +1431,17 @@ uint32_t HTMLInputElement::Width() {
return GetWidthHeightForImage(currentRequest).width;
}
bool HTMLInputElement::SanitizesOnValueGetter() const {
// Don't return non-sanitized value for types that are experimental on mobile
// or datetime types or number.
return mType == NS_FORM_INPUT_NUMBER || IsExperimentalMobileType(mType) ||
IsDateTimeInputType(mType);
}
void HTMLInputElement::GetValue(nsAString& aValue, CallerType aCallerType) {
GetValueInternal(aValue, aCallerType);
// Don't return non-sanitized value for types that are experimental on mobile
// or datetime types
if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
if (SanitizesOnValueGetter()) {
SanitizeValue(aValue);
}
}
@ -1583,11 +1583,11 @@ void HTMLInputElement::SetValue(const nsAString& aValue, CallerType aCallerType,
// Some types sanitize value, so GetValue doesn't return pure
// previous value correctly.
//
// FIXME(emilio): Shouldn't above just use GetNonFileValueInternal() to
// get the unsanitized value?
nsresult rv = SetValueInternal(
aValue,
(IsExperimentalMobileType(mType) || IsDateTimeInputType(mType))
? nullptr
: &currentValue,
aValue, SanitizesOnValueGetter() ? nullptr : &currentValue,
TextControlState::eSetValue_ByContent |
TextControlState::eSetValue_Notify |
TextControlState::eSetValue_MoveCursorToEndIfValueChanged);
@ -2163,25 +2163,17 @@ void HTMLInputElement::UpdateValidityState() {
bool HTMLInputElement::MozIsTextField(bool aExcludePassword) {
// TODO: temporary until bug 888320 is fixed.
if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
//
// FIXME: Historically we never returned true for `number`, we should consider
// changing that now that it is similar to other inputs.
if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType) ||
mType == NS_FORM_INPUT_NUMBER) {
return false;
}
return IsSingleLineTextControl(aExcludePassword);
}
HTMLInputElement* HTMLInputElement::GetOwnerNumberControl() {
if (IsInNativeAnonymousSubtree() && mType == NS_FORM_INPUT_TEXT &&
GetParent() && GetParent()->GetParent()) {
HTMLInputElement* grandparent =
HTMLInputElement::FromNodeOrNull(GetParent()->GetParent());
if (grandparent && grandparent->mType == NS_FORM_INPUT_NUMBER) {
return grandparent;
}
}
return nullptr;
}
void HTMLInputElement::SetUserInput(const nsAString& aValue,
nsIPrincipal& aSubjectPrincipal) {
if (mType == NS_FORM_INPUT_FILE && !aSubjectPrincipal.IsSystemPrincipal()) {
@ -2646,15 +2638,7 @@ nsresult HTMLInputElement::SetValueInternal(const nsAString& aValue,
if (setValueChanged) {
SetValueChanged(true);
}
if (mType == NS_FORM_INPUT_NUMBER) {
// This has to happen before OnValueChanged is called because that
// method needs the new value of our frame's anon text control.
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
numberControlFrame->SetValueOfAnonTextControl(value);
}
} else if (mType == NS_FORM_INPUT_RANGE) {
if (mType == NS_FORM_INPUT_RANGE) {
nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
if (frame) {
frame->UpdateForValueChange();
@ -2933,19 +2917,6 @@ void HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify) {
}
void HTMLInputElement::Blur(ErrorResult& aError) {
if (mType == NS_FORM_INPUT_NUMBER) {
// Blur our anonymous text control, if we have one. (DOM 'change' event
// firing and other things depend on this.)
nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl();
if (textControl) {
textControl->Blur(aError);
return;
}
}
}
if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
!IsExperimentalMobileType(mType)) {
if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
@ -2962,19 +2933,6 @@ void HTMLInputElement::Blur(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(aOptions, aError);
return;
}
}
}
if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
!IsExperimentalMobileType(mType)) {
if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
@ -3009,14 +2967,6 @@ void HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
}
void HTMLInputElement::Select() {
if (mType == NS_FORM_INPUT_NUMBER) {
nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
numberControlFrame->HandleSelectCall();
}
return;
}
if (!IsSingleLineTextControl(false)) {
return;
}
@ -3306,65 +3256,10 @@ void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
StopNumberControlSpinnerSpin();
}
}
if (aVisitor.mEvent->mMessage == eFocus ||
aVisitor.mEvent->mMessage == eBlur) {
if (aVisitor.mEvent->mMessage == eFocus) {
// Tell our frame it's getting focus so that it can make sure focus
// is moved to our anonymous text control.
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
// This could kill the frame!
numberControlFrame->HandleFocusEvent(aVisitor.mEvent);
}
}
nsIFrame* frame = GetPrimaryFrame();
if (frame && frame->IsThemed()) {
// Our frame's nested <input type=text> will be invalidated when it
// loses focus, but since we are also native themed we need to make
// sure that our entire area is repainted since any focus highlight
// from the theme should be removed from us (the repainting of the
// sub-area occupied by the anon text control is not enough to do
// that).
frame->InvalidateFrame();
}
}
}
nsGenericHTMLFormElementWithState::GetEventTargetParent(aVisitor);
// We do this after calling the base class' GetEventTargetParent so that
// nsIContent::GetEventTargetParent doesn't reset any change we make to
// mCanHandle.
if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted() &&
aVisitor.mEvent->mOriginalTarget != this) {
// <input type=number> has an anonymous <input type=text> descendant. If
// 'input' or 'change' events are fired at that text control then we need
// to do some special handling here.
HTMLInputElement* textControl = nullptr;
nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
textControl = numberControlFrame->GetAnonTextControl();
}
if (textControl && aVisitor.mEvent->mOriginalTarget == textControl) {
if (aVisitor.mEvent->mMessage == eEditorInput) {
aVisitor.mWantsPreHandleEvent = true;
// We set NS_PRE_HANDLE_INPUT_EVENT here and handle it in PreHandleEvent
// to prevent breaking event target chain creation.
aVisitor.mItemFlags |= NS_PRE_HANDLE_INPUT_EVENT;
} else if (aVisitor.mEvent->mMessage == eFormChange) {
// We cancel the DOM 'change' event that is fired for any change to our
// anonymous text control since we fire our own 'change' events and
// content shouldn't be seeing two 'change' events. Besides that we
// (as a number) control have tighter restrictions on when our internal
// value changes than our anon text control does, so in some cases
// (if our text control's value doesn't parse as a number) we don't
// want to fire a 'change' event at all.
aVisitor.mCanHandle = false;
}
}
}
// Stop the event if the related target's first non-native ancestor is the
// same as the original target's first non-native ancestor (we are moving
// inside of the same element).
@ -3407,26 +3302,7 @@ nsresult HTMLInputElement::PreHandleEvent(EventChainVisitor& aVisitor) {
}
FireChangeEventIfNeeded();
}
rv = nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
if (aVisitor.mItemFlags & NS_PRE_HANDLE_INPUT_EVENT) {
nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
MOZ_ASSERT(aVisitor.mEvent->mMessage == eEditorInput);
MOZ_ASSERT(numberControlFrame);
MOZ_ASSERT(numberControlFrame->GetAnonTextControl() ==
aVisitor.mEvent->mOriginalTarget);
// Propogate the anon text control's new value to our HTMLInputElement:
nsAutoString value;
numberControlFrame->GetValueOfAnonTextControl(value);
numberControlFrame->HandlingInputEvent(true);
AutoWeakFrame weakNumberControlFrame(numberControlFrame);
rv = SetValueInternal(value, TextControlState::eSetValue_BySetUserInput |
TextControlState::eSetValue_Notify);
NS_ENSURE_SUCCESS(rv, rv);
if (weakNumberControlFrame.IsAlive()) {
numberControlFrame->HandlingInputEvent(false);
}
}
return rv;
return nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
}
void HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent* aEvent) {
@ -3573,8 +3449,7 @@ void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) {
// We only do this if there actually is a value typed in by/displayed to
// the user. (IsValid() can return false if the 'required' attribute is
// set and the value is the empty string.)
nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame && !numberControlFrame->AnonTextControlIsEmpty()) {
if (!IsValueEmpty()) {
// We pass 'true' for UpdateValidityUIBits' aIsFocused argument
// regardless because we need the UI to update _now_ or the user will
// wonder why the step behavior isn't functioning.
@ -3598,10 +3473,6 @@ void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) {
// is small, so we should be fine here.)
SetValueInternal(newVal, TextControlState::eSetValue_BySetUserInput |
TextControlState::eSetValue_Notify);
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Failed to dispatch input event");
}
static bool SelectTextFieldOnFocus() {
@ -4093,9 +3964,7 @@ nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
wheelEvent->mDeltaY != 0 &&
wheelEvent->mDeltaMode != WheelEvent_Binding::DOM_DELTA_PIXEL) {
if (mType == NS_FORM_INPUT_NUMBER) {
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame && numberControlFrame->IsFocused()) {
if (nsContentUtils::IsFocusedContent(this)) {
StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
FireChangeEventIfNeeded();
aVisitor.mEvent->PreventDefault();
@ -6021,50 +5890,7 @@ EventStates HTMLInputElement::IntrinsicState() const {
bool HTMLInputElement::ShouldShowPlaceholder() const {
MOZ_ASSERT(PlaceholderApplies());
if (!IsValueEmpty()) {
return false;
}
// For number controls, even though the (sanitized) value is empty, there may
// be text in the anon text control.
if (nsNumberControlFrame* frame = do_QueryFrame(GetPrimaryFrame())) {
return frame->AnonTextControlIsEmpty();
}
return true;
}
void HTMLInputElement::AddStates(EventStates aStates) {
if (mType == NS_FORM_INPUT_TEXT) {
EventStates focusStates(aStates &
(NS_EVENT_STATE_FOCUS | NS_EVENT_STATE_FOCUSRING));
if (!focusStates.IsEmpty()) {
HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
if (ownerNumberControl) {
// If this code changes, audit existing places that check for
// NS_EVENT_STATE_FOCUS.
ownerNumberControl->AddStates(focusStates);
}
}
}
nsGenericHTMLFormElementWithState::AddStates(aStates);
}
void HTMLInputElement::RemoveStates(EventStates aStates) {
if (mType == NS_FORM_INPUT_TEXT) {
EventStates focusStates(aStates &
(NS_EVENT_STATE_FOCUS | NS_EVENT_STATE_FOCUSRING));
if (!focusStates.IsEmpty()) {
HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
if (ownerNumberControl) {
// If this code changes, audit existing places that check for
// NS_EVENT_STATE_FOCUS.
ownerNumberControl->RemoveStates(focusStates);
}
}
}
nsGenericHTMLFormElementWithState::RemoveStates(aStates);
return IsValueEmpty();
}
static nsTArray<OwningFileOrDirectory> RestoreFileContentData(
@ -6415,8 +6241,7 @@ bool HTMLInputElement::PlaceholderApplies() const {
if (IsDateTimeInputType(mType)) {
return false;
}
return IsSingleLineTextOrNumberControl(false);
return IsSingleLineTextControl(false);
}
bool HTMLInputElement::DoesMinMaxApply() const {

Просмотреть файл

@ -206,11 +206,6 @@ class HTMLInputElement final : public TextControlElement,
virtual EventStates IntrinsicState() const override;
// Element
private:
virtual void AddStates(EventStates aStates) override;
virtual void RemoveStates(EventStates aStates) override;
public:
// TextControlElement
virtual nsresult SetValueChanged(bool aValueChanged) override;
@ -808,8 +803,6 @@ class HTMLInputElement final : public TextControlElement,
double GetMinimumAsDouble() { return GetMinimum().toDouble(); }
double GetMaximumAsDouble() { return GetMaximum().toDouble(); }
HTMLInputElement* GetOwnerNumberControl();
void StartNumberControlSpinnerSpin();
enum SpinnerStopState { eAllowDispatchingEvents, eDisallowDispatchingEvents };
void StopNumberControlSpinnerSpin(
@ -1394,6 +1387,13 @@ class HTMLInputElement final : public TextControlElement,
*/
static bool IsDateTimeInputType(uint8_t aType);
/**
* Returns whether getting `.value` as a string should sanitize the value.
*
* See SanitizeValue.
*/
bool SanitizesOnValueGetter() const;
/**
* Returns true if the element should prevent dispatching another DOMActivate.
* This is used in situations where the anonymous subtree should already have

Просмотреть файл

@ -39,7 +39,6 @@
#include "mozilla/dom/HTMLTextAreaElement.h"
#include "mozilla/dom/Text.h"
#include "mozilla/StaticPrefs_dom.h"
#include "nsNumberControlFrame.h"
#include "nsFrameSelection.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Telemetry.h"
@ -3090,8 +3089,7 @@ void TextControlState::InitializeKeyboardEventListeners() {
TrustedEventsAtSystemGroupBubble());
}
mSelCon->SetScrollableFrame(
do_QueryFrame(mBoundFrame->PrincipalChildList().FirstChild()));
mSelCon->SetScrollableFrame(mBoundFrame->GetScrollTargetFrame());
}
void TextControlState::ValueWasChanged(bool aNotify) {

Просмотреть файл

@ -8,7 +8,7 @@
#include "mozilla/TextControlState.h"
#include "mozilla/dom/HTMLInputElement.h"
#include "nsNumberControlFrame.h"
#include "ICUUtils.h"
bool NumericInputTypeBase::IsRangeOverflow() const {
mozilla::Decimal maximum = mInputElement->GetMaximum();
@ -94,11 +94,15 @@ nsresult NumericInputTypeBase::GetRangeUnderflowMessage(nsAString& aMessage) {
bool NumericInputTypeBase::ConvertStringToNumber(
nsAString& aValue, mozilla::Decimal& aResultValue) const {
aResultValue = mozilla::dom::HTMLInputElement::StringToDecimal(aValue);
// FIXME(emilio, bug 1605158): This should really just be
// StringToDecimal(aValue).
ICUUtils::LanguageTagIterForContent langTagIter(mInputElement);
aResultValue =
mozilla::Decimal::fromDouble(ICUUtils::ParseNumber(aValue, langTagIter));
if (!aResultValue.isFinite()) {
return false;
aResultValue = mozilla::dom::HTMLInputElement::StringToDecimal(aValue);
}
return true;
return aResultValue.isFinite();
}
bool NumericInputTypeBase::ConvertNumberToString(
@ -132,19 +136,7 @@ bool NumberInputType::IsValueMissing() const {
bool NumberInputType::HasBadInput() const {
nsAutoString value;
GetNonFileValueInternal(value);
if (!value.IsEmpty()) {
// The input can't be bad, otherwise it would have been sanitized to the
// empty string.
NS_ASSERTION(!mInputElement->GetValueAsDecimal().isNaN(),
"Should have sanitized");
return false;
}
nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame && !numberControlFrame->AnonTextControlIsEmpty()) {
// The input the user entered failed to parse as a number.
return true;
}
return false;
return !value.IsEmpty() && mInputElement->GetValueAsDecimal().isNaN();
}
nsresult NumberInputType::GetValueMissingMessage(nsAString& aMessage) {

Просмотреть файл

@ -185,13 +185,6 @@ class nsIFormControl : public nsISupports {
*/
inline bool IsTextControl(bool aExcludePassword) const;
/**
* Returns true if this is a text control or a number control.
* @param aExcludePassword to have NS_FORM_INPUT_PASSWORD returning false.
* @return true if this is a text control or a number control.
*/
inline bool IsTextOrNumberControl(bool aExcludePassword) const;
/**
* Returns whether this is a single line text control.
* @param aExcludePassword to have NS_FORM_INPUT_PASSWORD returning false.
@ -199,13 +192,6 @@ class nsIFormControl : public nsISupports {
*/
inline bool IsSingleLineTextControl(bool aExcludePassword) const;
/**
* Returns true if this is a single line text control or a number control.
* @param aExcludePassword to have NS_FORM_INPUT_PASSWORD returning false.
* @return true if this is a single line text control or a number control.
*/
inline bool IsSingleLineTextOrNumberControl(bool aExcludePassword) const;
/**
* Returns whether this is a submittable form control.
* @return whether this is a submittable form control.
@ -263,27 +249,16 @@ bool nsIFormControl::IsTextControl(bool aExcludePassword) const {
IsSingleLineTextControl(aExcludePassword, type);
}
bool nsIFormControl::IsTextOrNumberControl(bool aExcludePassword) const {
return IsTextControl(aExcludePassword) ||
ControlType() == NS_FORM_INPUT_NUMBER;
}
bool nsIFormControl::IsSingleLineTextControl(bool aExcludePassword) const {
return IsSingleLineTextControl(aExcludePassword, ControlType());
}
bool nsIFormControl::IsSingleLineTextOrNumberControl(
bool aExcludePassword) const {
return IsSingleLineTextControl(aExcludePassword) ||
ControlType() == NS_FORM_INPUT_NUMBER;
}
/*static*/
bool nsIFormControl::IsSingleLineTextControl(bool aExcludePassword,
uint32_t aType) {
return aType == NS_FORM_INPUT_TEXT || aType == NS_FORM_INPUT_EMAIL ||
aType == NS_FORM_INPUT_SEARCH || aType == NS_FORM_INPUT_TEL ||
aType == NS_FORM_INPUT_URL ||
aType == NS_FORM_INPUT_URL || aType == NS_FORM_INPUT_NUMBER ||
// TODO: those are temporary until bug 773205 is fixed.
aType == NS_FORM_INPUT_MONTH || aType == NS_FORM_INPUT_WEEK ||
aType == NS_FORM_INPUT_DATETIME_LOCAL ||

Просмотреть файл

@ -149,9 +149,6 @@ SimpleTest.waitForFocus(async () => {
if (!test.result.fireBeforeInputEvent) {
is(beforeInputEvents.length, 0,
`No "beforeinput" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
} else if (test.type === "number") {
todo_is(beforeInputEvents.length, 1,
`Only one "beforeinput" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
} else {
is(beforeInputEvents.length, 1,
`Only one "beforeinput" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
@ -179,7 +176,7 @@ SimpleTest.waitForFocus(async () => {
}
if (inputEvents.length > 0) {
if (SpecialPowers.wrap(target).isInputEventTarget) {
if (test.type === "number" || test.type === "time") {
if (test.type === "time") {
todo(inputEvents[0] instanceof InputEvent,
`"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
} else {
@ -234,9 +231,6 @@ SimpleTest.waitForFocus(async () => {
if (!test.result.fireBeforeInputEvent) {
is(beforeInputEvents.length, 0,
`No "beforeinput" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
} else if (test.type === "number") {
todo_is(beforeInputEvents.length, 1,
`Only one "beforeinput" event should be dispatched when setUserInput("${test.input.after}") is called before ${tag} gets focus`);
} else {
is(beforeInputEvents.length, 1,
`Only one "beforeinput" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
@ -264,7 +258,7 @@ SimpleTest.waitForFocus(async () => {
}
if (inputEvents.length > 0) {
if (SpecialPowers.wrap(target).isInputEventTarget) {
if (test.type === "number" || test.type === "time") {
if (test.type === "time") {
todo(inputEvents[0] instanceof InputEvent,
`"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
} else {

Просмотреть файл

@ -57,22 +57,19 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
is(aEvent.bubbles, true,
`"beforeinput" event should always bubble ${aDescription}`);
}
function checkIfInputIsInputEvent(aEvent, aToDo, aDescription) {
if (aToDo) {
// Probably, key operation should fire "input" event with InputEvent interface.
// See https://github.com/w3c/input-events/issues/88
todo(aEvent instanceof InputEvent,
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
} else {
ok(aEvent instanceof InputEvent,
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
is(aEvent.inputType, expectedInputType,
`inputType of "input" event should be "${expectedInputType}" ${aDescription}`);
is(aEvent.data, expectedData,
`data of "input" event should be ${expectedData} ${aDescription}`);
is(aEvent.dataTransfer, null,
`dataTransfer of "input" event should be null ${aDescription}`);
}
let skipExpectedDataCheck = false;
function checkIfInputIsInputEvent(aEvent, aDescription) {
ok(aEvent instanceof InputEvent,
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
is(aEvent.inputType, expectedInputType,
`inputType should be "${expectedInputType}" ${aDescription}`);
if (!skipExpectedDataCheck)
is(aEvent.data, expectedData, `data should be ${expectedData} ${aDescription}`);
else
info(`data is ${aEvent.data} ${aDescription}`);
is(aEvent.dataTransfer, null,
`dataTransfer should be null ${aDescription}`);
is(aEvent.cancelable, false,
`"input" event should be never cancelable ${aDescription}`);
is(aEvent.bubbles, true,
@ -95,7 +92,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
};
document.getElementById("textarea").oninput = (aEvent) => {
++textareaInput;
checkIfInputIsInputEvent(aEvent, false, "on textarea element");
checkIfInputIsInputEvent(aEvent, "on textarea element");
};
// These are the type were the input event apply.
@ -109,7 +106,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
};
document.getElementById(id).oninput = (aEvent) => {
++textInput[textTypes.indexOf(aEvent.target.type)];
checkIfInputIsInputEvent(aEvent, false, `on input element whose type is ${aEvent.target.type}`);
checkIfInputIsInputEvent(aEvent, `on input element whose type is ${aEvent.target.type}`);
};
}
@ -142,7 +139,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
};
document.getElementById("input_number").oninput = (aEvent) => {
++numberInput;
checkIfInputIsInputEvent(aEvent, true, "on input element whose type is number");
checkIfInputIsInputEvent(aEvent, "on input element whose type is number");
};
SimpleTest.waitForExplicitFinish();
@ -369,18 +366,22 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
number.focus();
// <input type="number">'s inputType value hasn't been decided, see
// https://github.com/w3c/input-events/issues/88
expectedInputType = "";
expectedData = null;
expectedInputType = "insertReplacementText";
expectedData = "1";
expectedBeforeInputCancelable = false;
synthesizeKey("KEY_ArrowUp");
todo_is(numberBeforeInput, 1, "beforeinput event should be dispatched for up/down arrow key keypress");
is(numberInput, 1, "input event should be dispatched for up/down arrow key keypress");
is(number.value, "1", "sanity check value of number control after keypress");
// `data` will be the value of the input, but we can't change
// `expectedData` and use {repeat: 3} at the same time.
skipExpectedDataCheck = true;
synthesizeKey("KEY_ArrowDown", {repeat: 3});
todo_is(numberBeforeInput, 4, "beforeinput event should be dispatched for each up/down arrow key keypress event, even when rapidly repeated");
is(numberInput, 4, "input event should be dispatched for each up/down arrow key keypress event, even when rapidly repeated");
is(number.value, "-2", "sanity check value of number control after multiple keydown events");
skipExpectedDataCheck = false;
number.blur();
todo_is(numberBeforeInput, 4, "beforeinput event shouldn't be dispatched on blur");

Просмотреть файл

@ -60,7 +60,7 @@ function test_focus_redirects_to_text_control_but_not_for_activeElement() {
.getService(SpecialPowers.Ci.nsIFocusManager);
var focusedElement = fm.focusedElement;
is (focusedElement.localName, "input", "focusedElement should be an input element");
is (focusedElement.getAttribute("type"), "text", "focusedElement should of type text");
is (focusedElement.getAttribute("type"), "number", "focusedElement should of type number");
}
var blurEventCounter = 0;

Просмотреть файл

@ -45,8 +45,10 @@ function runTest(test) {
elem.value = 0;
elem.select();
sendString(test.inputWithoutGrouping);
is(elem.value, String(test.value), "Test " + test.desc + " ('" + test.langTag +
is(elem.valueAsNumber, test.value, "Test " + test.desc + " ('" + test.langTag +
"') localization without grouping separator");
is(elem.value, test.inputWithoutGrouping,
"Test " + test.desc + " ('" + test.langTag + "') localization without grouping separator as string");
}
function runInvalidInputTest(test) {

Просмотреть файл

@ -51,7 +51,7 @@ const SPIN_DOWN_Y = inputRect.height - 3;
function checkInputEvent(aEvent, aDescription) {
// Probably, key operation should fire "input" event with InputEvent interface.
// See https://github.com/w3c/input-events/issues/88
todo(aEvent instanceof InputEvent, `"input" event should be dispatched with InputEvent interface on input element whose type is number ${aDescription}`);
ok(aEvent instanceof InputEvent, `"input" event should be dispatched with InputEvent interface on input element whose type is number ${aDescription}`);
is(aEvent.cancelable, false, `"input" event should be never cancelable on input element whose type is number ${aDescription}`);
is(aEvent.bubbles, true, `"input" event should always bubble on input element whose type is number ${aDescription}`);
info(`Data: ${aEvent.data}, value: ${aEvent.target.value}`);
@ -233,7 +233,6 @@ var spinTests = [
synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
}
];
</script>
</pre>
</body>

Просмотреть файл

@ -111,10 +111,6 @@ function checkIsInvalid(element, infoStr)
{
ok(element.validity.badInput,
"Element should suffer from bad input for " + infoStr);
if (element.id == "requiredinput") {
ok(element.validity.valueMissing,
"Element should suffer from value missing for " + infoStr);
}
ok(!element.validity.valid, "Element should not be valid for " + infoStr);
ok(!element.checkValidity(), "checkValidity() should return false for " + infoStr);
ok(gInvalid, "The invalid event should have been thrown for " + infoStr);

Просмотреть файл

@ -172,18 +172,6 @@ partial interface HTMLInputElement {
[ChromeOnly]
void mozSetDndFilesAndDirectories(sequence<(File or Directory)> list);
// Number controls (<input type=number>) have an anonymous text control
// (<input type=text>) in the anonymous shadow tree that they contain. On
// such an anonymous text control this property provides access to the
// number control that owns the text control. This is useful, for example,
// in code that looks at the currently focused element to make decisions
// about which IME to bring up. Such code needs to be able to check for any
// owning number control since it probably wants to bring up a number pad
// instead of the standard keyboard, even when the anonymous text control has
// focus.
[ChromeOnly]
readonly attribute HTMLInputElement? ownerNumberControl;
boolean mozIsTextField(boolean aExcludePassword);
[ChromeOnly]

Просмотреть файл

@ -385,9 +385,7 @@ nsresult EditorBase::InstallEventListeners() {
}
// Initialize the event target.
nsCOMPtr<nsIContent> rootContent = GetRoot();
NS_ENSURE_TRUE(rootContent, NS_ERROR_NOT_AVAILABLE);
mEventTarget = rootContent->GetParent();
mEventTarget = GetExposedRoot();
NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_AVAILABLE);
nsresult rv = mEventListener->Connect(this);
@ -4983,14 +4981,12 @@ void EditorBase::ReinitializeSelection(Element& aElement) {
Element* EditorBase::GetEditorRoot() const { return GetRoot(); }
Element* EditorBase::GetExposedRoot() const {
Element* rootElement = GetRoot();
// For plaintext editors, we need to ask the input/textarea element directly.
if (rootElement && rootElement->IsRootOfNativeAnonymousSubtree()) {
rootElement = rootElement->GetParent()->AsElement();
Element* root = GetRoot();
if (!root || !root->IsInNativeAnonymousSubtree()) {
return root;
}
return rootElement;
return Element::FromNodeOrNull(
root->GetClosestNativeAnonymousSubtreeRootParent());
}
nsresult EditorBase::DetermineCurrentDirection() {

Просмотреть файл

@ -41,270 +41,18 @@ NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame)
NS_QUERYFRAME_HEAD(nsNumberControlFrame)
NS_QUERYFRAME_ENTRY(nsNumberControlFrame)
NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsTextControlFrame)
nsNumberControlFrame::nsNumberControlFrame(ComputedStyle* aStyle,
nsPresContext* aPresContext)
: nsContainerFrame(aStyle, aPresContext, kClassID),
mHandlingInputEvent(false) {}
: nsTextControlFrame(aStyle, aPresContext, kClassID) {}
void nsNumberControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) {
NS_ASSERTION(
!GetPrevContinuation() && !GetNextContinuation(),
"nsNumberControlFrame should not have continuations; if it does we "
"need to call RegUnregAccessKey only for the first");
nsCheckboxRadioFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
aPostDestroyData.AddAnonymousContent(mOuterWrapper.forget());
nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
nsTextControlFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
}
nscoord nsNumberControlFrame::GetMinISize(gfxContext* aRenderingContext) {
nscoord result;
DISPLAY_MIN_INLINE_SIZE(this, result);
nsIFrame* kid = mFrames.FirstChild();
if (kid) { // display:none?
result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, kid,
nsLayoutUtils::MIN_ISIZE);
} else {
result = 0;
}
return result;
}
nscoord nsNumberControlFrame::GetPrefISize(gfxContext* aRenderingContext) {
nscoord result;
DISPLAY_PREF_INLINE_SIZE(this, result);
nsIFrame* kid = mFrames.FirstChild();
if (kid) { // display:none?
result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, kid,
nsLayoutUtils::PREF_ISIZE);
} else {
result = 0;
}
return result;
}
void nsNumberControlFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
MarkInReflow();
DO_GLOBAL_REFLOW_COUNT("nsNumberControlFrame");
DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
NS_ASSERTION(mOuterWrapper, "Outer wrapper div must exist!");
NS_ASSERTION(
!GetPrevContinuation() && !GetNextContinuation(),
"nsNumberControlFrame should not have continuations; if it does we "
"need to call RegUnregAccessKey only for the first");
NS_ASSERTION(!mFrames.FirstChild() || !mFrames.FirstChild()->GetNextSibling(),
"We expect at most one direct child frame");
if (mState & NS_FRAME_FIRST_REFLOW) {
nsCheckboxRadioFrame::RegUnRegAccessKey(this, true);
}
const WritingMode myWM = aReflowInput.GetWritingMode();
// The ISize of our content box, which is the available ISize
// for our anonymous content:
const nscoord contentBoxISize = aReflowInput.ComputedISize();
nscoord contentBoxBSize = aReflowInput.ComputedBSize();
// Figure out our border-box sizes as well (by adding borderPadding to
// content-box sizes):
const nscoord borderBoxISize =
contentBoxISize +
aReflowInput.ComputedLogicalBorderPadding().IStartEnd(myWM);
nscoord borderBoxBSize;
if (contentBoxBSize != NS_UNCONSTRAINEDSIZE) {
borderBoxBSize =
contentBoxBSize +
aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
} // else, we'll figure out borderBoxBSize after we resolve contentBoxBSize.
nsIFrame* outerWrapperFrame = mOuterWrapper->GetPrimaryFrame();
if (!outerWrapperFrame) { // display:none?
if (contentBoxBSize == NS_UNCONSTRAINEDSIZE) {
contentBoxBSize = 0;
borderBoxBSize =
aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
}
} else {
NS_ASSERTION(outerWrapperFrame == mFrames.FirstChild(), "huh?");
ReflowOutput wrappersDesiredSize(aReflowInput);
WritingMode wrapperWM = outerWrapperFrame->GetWritingMode();
LogicalSize availSize = aReflowInput.ComputedSize(wrapperWM);
availSize.BSize(wrapperWM) = NS_UNCONSTRAINEDSIZE;
ReflowInput wrapperReflowInput(aPresContext, aReflowInput,
outerWrapperFrame, availSize);
// Convert wrapper margin into my own writing-mode (in case it differs):
LogicalMargin wrapperMargin =
wrapperReflowInput.ComputedLogicalMargin().ConvertTo(myWM, wrapperWM);
// offsets of wrapper frame within this frame:
LogicalPoint wrapperOffset(
myWM,
aReflowInput.ComputedLogicalBorderPadding().IStart(myWM) +
wrapperMargin.IStart(myWM),
aReflowInput.ComputedLogicalBorderPadding().BStart(myWM) +
wrapperMargin.BStart(myWM));
nsReflowStatus childStatus;
// We initially reflow the child with a dummy containerSize; positioning
// will be fixed later.
const nsSize dummyContainerSize;
ReflowChild(outerWrapperFrame, aPresContext, wrappersDesiredSize,
wrapperReflowInput, myWM, wrapperOffset, dummyContainerSize,
ReflowChildFlags::Default, childStatus);
MOZ_ASSERT(childStatus.IsFullyComplete(),
"We gave our child unconstrained available block-size, "
"so it should be complete");
nscoord wrappersMarginBoxBSize =
wrappersDesiredSize.BSize(myWM) + wrapperMargin.BStartEnd(myWM);
if (contentBoxBSize == NS_UNCONSTRAINEDSIZE) {
// We are intrinsically sized -- we should shrinkwrap the outer wrapper's
// block-size:
contentBoxBSize = wrappersMarginBoxBSize;
// Make sure we obey min/max-bsize in the case when we're doing intrinsic
// sizing (we get it for free when we have a non-intrinsic
// aReflowInput.ComputedBSize()). Note that we do this before
// adjusting for borderpadding, since ComputedMaxBSize and
// ComputedMinBSize are content heights.
contentBoxBSize =
NS_CSS_MINMAX(contentBoxBSize, aReflowInput.ComputedMinBSize(),
aReflowInput.ComputedMaxBSize());
borderBoxBSize =
contentBoxBSize +
aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
}
// Center child in block axis
nscoord extraSpace = contentBoxBSize - wrappersMarginBoxBSize;
wrapperOffset.B(myWM) += std::max(0, extraSpace / 2);
// Needed in FinishReflowChild, for logical-to-physical conversion:
nsSize borderBoxSize =
LogicalSize(myWM, borderBoxISize, borderBoxBSize).GetPhysicalSize(myWM);
// Place the child
FinishReflowChild(outerWrapperFrame, aPresContext, wrappersDesiredSize,
&wrapperReflowInput, myWM, wrapperOffset, borderBoxSize,
ReflowChildFlags::Default);
if (!aReflowInput.mStyleDisplay->IsContainLayout()) {
nsSize contentBoxSize =
LogicalSize(myWM, contentBoxISize, contentBoxBSize)
.GetPhysicalSize(myWM);
aDesiredSize.SetBlockStartAscent(
wrappersDesiredSize.BlockStartAscent() +
outerWrapperFrame->BStart(aReflowInput.GetWritingMode(),
contentBoxSize));
} // else: we're layout-contained, and so we have no baseline.
}
LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
aDesiredSize.SetSize(myWM, logicalDesiredSize);
aDesiredSize.SetOverflowAreasToDesiredBounds();
if (outerWrapperFrame) {
ConsiderChildOverflow(aDesiredSize.mOverflowAreas, outerWrapperFrame);
}
FinishAndStoreOverflow(&aDesiredSize);
MOZ_ASSERT(aStatus.IsEmpty(), "This type of frame can't be split.");
NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
}
void nsNumberControlFrame::SyncDisabledState() {
EventStates eventStates = mContent->AsElement()->State();
if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, EmptyString(),
true);
} else {
mTextField->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
}
}
nsresult nsNumberControlFrame::AttributeChanged(int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType) {
// nsGkAtoms::disabled is handled by SyncDisabledState
if (aNameSpaceID == kNameSpaceID_None) {
if (aAttribute == nsGkAtoms::placeholder ||
aAttribute == nsGkAtoms::readonly ||
aAttribute == nsGkAtoms::tabindex) {
if (aModType == MutationEvent_Binding::REMOVAL) {
mTextField->UnsetAttr(aNameSpaceID, aAttribute, true);
} else {
MOZ_ASSERT(aModType == MutationEvent_Binding::ADDITION ||
aModType == MutationEvent_Binding::MODIFICATION);
nsAutoString value;
mContent->AsElement()->GetAttr(aNameSpaceID, aAttribute, value);
mTextField->SetAttr(aNameSpaceID, aAttribute, value, true);
}
}
}
return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
}
void nsNumberControlFrame::ContentStatesChanged(EventStates aStates) {
if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
}
}
nsITextControlFrame* nsNumberControlFrame::GetTextFieldFrame() {
return do_QueryFrame(GetAnonTextControl()->GetPrimaryFrame());
}
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)) {
// 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;
};
already_AddRefed<Element> nsNumberControlFrame::MakeAnonymousElement(
Element* aParent, nsAtom* aTagName, PseudoStyleType aPseudoType) {
// Get the NodeInfoManager and tag necessary to create the anonymous divs.
@ -332,10 +80,12 @@ nsresult nsNumberControlFrame::CreateAnonymousContent(
//
// input
// div - outer wrapper with "display:flex" by default
// input - text input field
// div - editor root
// div - spin box wrapping up/down arrow buttons
// div - spin up (up arrow button)
// div - spin down (down arrow button)
// div - placeholder
// div - preview div
//
// If you change this, be careful to change the destruction order in
// nsNumberControlFrame::DestroyFrom.
@ -346,48 +96,18 @@ nsresult nsNumberControlFrame::CreateAnonymousContent(
aElements.AppendElement(mOuterWrapper);
// Create the ::-moz-number-text pseudo-element:
mTextField = MakeAnonymousElement(mOuterWrapper, nsGkAtoms::input,
PseudoStyleType::mozNumberText);
mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
NS_LITERAL_STRING("text"), false);
HTMLInputElement* content = HTMLInputElement::FromNode(mContent);
HTMLInputElement* textField = HTMLInputElement::FromNode(mTextField);
// Initialize the text field value:
nsAutoString value;
content->GetValue(value, CallerType::System);
SetValueOfAnonTextControl(value);
// If we're readonly, make sure our anonymous text control is too:
nsAutoString readonly;
if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly,
readonly)) {
mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly,
false);
nsTArray<ContentInfo> nestedContent;
nsTextControlFrame::CreateAnonymousContent(nestedContent);
for (auto& content : nestedContent) {
// The root goes inside the container.
if (content.mContent == mRootNode) {
mOuterWrapper->AppendChildTo(content.mContent, false);
} else {
// The rest (placeholder and preview), directly under us.
aElements.AppendElement(std::move(content));
}
}
// Propogate our tabindex:
textField->SetTabIndex(content->TabIndex(), IgnoreErrors());
// Initialize the text field's placeholder, if ours is set:
nsAutoString placeholder;
if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder,
placeholder)) {
mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholder,
false);
}
if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS)) {
// We don't want to focus the frame but the text field.
RefPtr<FocusTextField> focusJob = new FocusTextField(mContent, mTextField);
nsContentUtils::AddScriptRunner(focusJob);
}
SyncDisabledState(); // Sync disabled state of 'mTextField'.
if (StyleDisplay()->mAppearance == StyleAppearance::Textfield) {
// The author has elected to hide the spinner by setting this
// -moz-appearance. We will reframe if it changes.
@ -409,19 +129,6 @@ nsresult nsNumberControlFrame::CreateAnonymousContent(
return NS_OK;
}
void nsNumberControlFrame::SetFocus(bool aOn, bool aRepaint) {
GetTextFieldFrame()->SetFocus(aOn, aRepaint);
}
nsresult nsNumberControlFrame::SetFormProperty(nsAtom* aName,
const nsAString& aValue) {
return GetTextFieldFrame()->SetFormProperty(aName, aValue);
}
HTMLInputElement* nsNumberControlFrame::GetAnonTextControl() const {
return HTMLInputElement::FromNode(mTextField);
}
/* static */
nsNumberControlFrame* nsNumberControlFrame::GetNumberControlFrameForTextField(
nsIFrame* aFrame) {
@ -525,31 +232,6 @@ bool nsNumberControlFrame::SpinnerDownButtonIsDepressed() const {
->NumberSpinnerDownButtonIsDepressed();
}
bool nsNumberControlFrame::IsFocused() const {
// Normally this depends on the state of our anonymous text control (which
// takes focus for us), but in the case that it does not have a frame we will
// have focus ourself.
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);
// 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();
}
#define STYLES_DISABLING_NATIVE_THEMING \
NS_AUTHOR_SPECIFIED_BACKGROUND | NS_AUTHOR_SPECIFIED_PADDING | \
NS_AUTHOR_SPECIFIED_BORDER
@ -573,128 +255,17 @@ bool nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const {
spinDownFrame, STYLES_DISABLING_NATIVE_THEMING);
}
bool nsNumberControlFrame::GetNaturalBaselineBOffset(
WritingMode aWM, BaselineSharingGroup aGroup, nscoord* aBaseline) const {
if (StyleDisplay()->IsContainLayout()) {
return false;
}
nsIFrame* inner = GetAnonTextControl()->GetPrimaryFrame();
nscoord baseline;
DebugOnly<bool> hasBaseline = inner->GetNaturalBaselineBOffset(
aWM, BaselineSharingGroup::First, &baseline);
MOZ_ASSERT(hasBaseline);
nsPoint offset = inner->GetOffsetToIgnoringScrolling(this);
baseline += aWM.IsVertical() ? offset.x : offset.y;
if (aGroup == BaselineSharingGroup::Last) {
baseline = BSize(aWM) - baseline;
}
*aBaseline = baseline;
return true;
}
void nsNumberControlFrame::AppendAnonymousContentTo(
nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
// Only one direct anonymous child:
if (mOuterWrapper) {
aElements.AppendElement(mOuterWrapper);
}
}
void nsNumberControlFrame::SetValueOfAnonTextControl(const nsAString& aValue) {
if (mHandlingInputEvent) {
// We have been called while our HTMLInputElement is processing a DOM
// 'input' event targeted at our anonymous text control. Our
// HTMLInputElement has taken the value of our anon text control and
// called SetValueInternal on itself to keep its own value in sync. As a
// result SetValueInternal has called us. In this one case we do not want
// to update our anon text control, especially since aValue will be the
// sanitized value, and only the internal value should be sanitized (not
// the value shown to the user, and certainly we shouldn't change it as
// they type).
return;
if (mPlaceholderDiv) {
aElements.AppendElement(mPlaceholderDiv);
}
// Init to aValue so that we set aValue as the value of our text control if
// aValue isn't a valid number (in which case the HTMLInputElement's validity
// state will be set to invalid) or if aValue can't be localized:
nsAutoString localizedValue(aValue);
// Try and localize the value we will set:
Decimal val = HTMLInputElement::StringToDecimal(aValue);
if (val.isFinite()) {
ICUUtils::LanguageTagIterForContent langTagIter(mContent);
ICUUtils::LocalizeNumber(val.toDouble(), langTagIter, localizedValue);
if (mPreviewDiv) {
aElements.AppendElement(mPreviewDiv);
}
// We need to update the value of our anonymous text control here. Note that
// this must be its value, and not its 'value' attribute (the default value),
// since the default value is ignored once a user types into the text
// control.
//
// Pass NonSystem as the caller type; this should work fine for actual number
// inputs, and be safe in case our input has a type we don't expect for some
// reason.
HTMLInputElement::FromNode(mTextField)
->SetValue(localizedValue, CallerType::NonSystem, IgnoreErrors());
}
void nsNumberControlFrame::GetValueOfAnonTextControl(nsAString& aValue) {
if (!mTextField) {
aValue.Truncate();
return;
}
HTMLInputElement::FromNode(mTextField)->GetValue(aValue, CallerType::System);
// Here we need to de-localize any number typed in by the user. That is, we
// need to convert it from the number format of the user's language, region,
// etc. to the format that the HTML 5 spec defines to be a "valid
// floating-point number":
//
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#floating-point-numbers
//
// This is necessary to allow the number that we return to be parsed by
// functions like HTMLInputElement::StringToDecimal (the HTML-5-conforming
// parsing function) which don't know how to handle numbers that are
// formatted differently (for example, with non-ASCII digits, with grouping
// separator characters or with a decimal separator character other than
// '.').
ICUUtils::LanguageTagIterForContent langTagIter(mContent);
double value = ICUUtils::ParseNumber(aValue, langTagIter);
if (!IsFinite(value)) {
aValue.Truncate();
return;
}
if (value == HTMLInputElement::StringToDecimal(aValue).toDouble()) {
// We want to preserve the formatting of the number as typed in by the user
// whenever possible. Since the localized serialization parses to the same
// number as the de-localized serialization, we can do that. This helps
// prevent normalization of input such as "2e2" (which would otherwise be
// converted to "200"). Content relies on this.
//
// Typically we will only get here for locales in which numbers are
// formatted in the same way as they are for HTML5's "valid floating-point
// number" format.
return;
}
// We can't preserve the formatting, otherwise functions such as
// HTMLInputElement::StringToDecimal would incorrectly process the number
// input by the user. For example, "12.345" with lang=de de-localizes as
// 12345, but HTMLInputElement::StringToDecimal would mistakenly parse it as
// 12.345. Another example would be "12,345" with lang=de which de-localizes
// as 12.345, but HTMLInputElement::StringToDecimal would parse it to NaN.
aValue.Truncate();
aValue.AppendFloat(value);
}
bool nsNumberControlFrame::AnonTextControlIsEmpty() {
if (!mTextField) {
return true;
}
nsAutoString value;
HTMLInputElement::FromNode(mTextField)->GetValue(value, CallerType::System);
return value.IsEmpty();
}
#ifdef ACCESSIBILITY

Просмотреть файл

@ -9,6 +9,7 @@
#include "mozilla/Attributes.h"
#include "nsContainerFrame.h"
#include "nsTextControlFrame.h"
#include "nsIFormControlFrame.h"
#include "nsIAnonymousContentCreator.h"
#include "nsCOMPtr.h"
@ -28,10 +29,10 @@ class HTMLInputElement;
/**
* This frame type is used for <input type=number>.
*
* TODO(emilio): Maybe merge with nsTextControlFrame?
*/
class nsNumberControlFrame final : public nsContainerFrame,
public nsIAnonymousContentCreator,
public nsIFormControlFrame {
class nsNumberControlFrame final : public nsTextControlFrame {
friend nsIFrame* NS_NewNumberControlFrame(mozilla::PresShell* aPresShell,
ComputedStyle* aStyle);
@ -48,80 +49,23 @@ class nsNumberControlFrame final : public nsContainerFrame,
NS_DECL_QUERYFRAME
NS_DECL_FRAMEARENA_HELPERS(nsNumberControlFrame)
virtual void DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) override;
virtual void ContentStatesChanged(mozilla::EventStates aStates) override;
void DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData&) override;
#ifdef ACCESSIBILITY
virtual mozilla::a11y::AccType AccessibleType() override;
mozilla::a11y::AccType AccessibleType() override;
#endif
virtual nscoord GetMinISize(gfxContext* aRenderingContext) override;
virtual nscoord GetPrefISize(gfxContext* aRenderingContext) override;
virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) override;
virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
int32_t aModType) override;
bool GetNaturalBaselineBOffset(mozilla::WritingMode aWM,
BaselineSharingGroup aGroup,
nscoord* aBaseline) const override;
// nsIAnonymousContentCreator
virtual nsresult CreateAnonymousContent(
nsTArray<ContentInfo>& aElements) override;
virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
uint32_t aFilter) override;
nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
uint32_t aFilter) override;
#ifdef DEBUG_FRAME_DUMP
virtual nsresult GetFrameName(nsAString& aResult) const override {
nsresult GetFrameName(nsAString& aResult) const override {
return MakeFrameName(NS_LITERAL_STRING("NumberControl"), aResult);
}
#endif
virtual bool IsFrameOfType(uint32_t aFlags) const override {
return nsContainerFrame::IsFrameOfType(
aFlags & ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
}
// nsIFormControlFrame
virtual void SetFocus(bool aOn, bool aRepaint) override;
virtual nsresult SetFormProperty(nsAtom* aName,
const nsAString& aValue) override;
/**
* This method attempts to localizes aValue and then sets the result as the
* value of our anonymous text control. It's called when our
* HTMLInputElement's value changes, when we need to sync up the value
* displayed in our anonymous text control.
*/
void SetValueOfAnonTextControl(const nsAString& aValue);
/**
* This method gets the string value of our anonymous text control,
* attempts to normalizes (de-localizes) it, then sets the outparam aValue to
* the result. It's called when user input changes the text value of our
* anonymous text control so that we can sync up the internal value of our
* HTMLInputElement.
*/
void GetValueOfAnonTextControl(nsAString& aValue);
bool AnonTextControlIsEmpty();
/**
* Called to notify this frame that its HTMLInputElement is currently
* processing a DOM 'input' event.
*/
void HandlingInputEvent(bool aHandlingEvent) {
mHandlingInputEvent = aHandlingEvent;
}
HTMLInputElement* GetAnonTextControl() const;
/**
* If the frame is the frame for an nsNumberControlFrame's anonymous text
* field, returns the nsNumberControlFrame. Else returns nullptr.
@ -150,10 +94,6 @@ class nsNumberControlFrame final : public nsContainerFrame,
bool SpinnerUpButtonIsDepressed() const;
bool SpinnerDownButtonIsDepressed() const;
bool IsFocused() const;
void HandleFocusEvent(WidgetEvent* aEvent);
/**
* Our element had HTMLInputElement::Select() called on it.
*/
@ -162,47 +102,16 @@ class nsNumberControlFrame final : public nsContainerFrame,
bool ShouldUseNativeStyleForSpinner() const;
private:
nsITextControlFrame* GetTextFieldFrame();
already_AddRefed<Element> MakeAnonymousElement(Element* aParent,
nsAtom* aTagName,
PseudoStyleType aPseudoType);
class SyncDisabledStateEvent;
friend class SyncDisabledStateEvent;
class SyncDisabledStateEvent : public mozilla::Runnable {
public:
explicit SyncDisabledStateEvent(nsNumberControlFrame* aFrame)
: mozilla::Runnable("nsNumberControlFrame::SyncDisabledStateEvent"),
mFrame(aFrame) {}
NS_IMETHOD Run() override {
nsNumberControlFrame* frame =
static_cast<nsNumberControlFrame*>(mFrame.GetFrame());
NS_ENSURE_STATE(frame);
frame->SyncDisabledState();
return NS_OK;
}
private:
WeakFrame mFrame;
};
/**
* Sync the disabled state of the anonymous children up with our content's.
*/
void SyncDisabledState();
/**
* The text field used to edit and show the number.
* @see nsNumberControlFrame::CreateAnonymousContent.
*/
// See nsNumberControlFrame::CreateAnonymousContent for a description of
// these.
nsCOMPtr<Element> mOuterWrapper;
nsCOMPtr<Element> mTextField;
nsCOMPtr<Element> mSpinBox;
nsCOMPtr<Element> mSpinUp;
nsCOMPtr<Element> mSpinDown;
bool mHandlingInputEvent;
};
#endif // nsNumberControlFrame_h__

Просмотреть файл

@ -108,8 +108,9 @@ class nsTextControlFrame::nsAnonDivObserver final
};
nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle,
nsPresContext* aPresContext)
: nsContainerFrame(aStyle, aPresContext, kClassID),
nsPresContext* aPresContext,
nsIFrame::ClassID aClassID)
: nsContainerFrame(aStyle, aPresContext, aClassID),
mFirstBaseline(NS_INTRINSIC_ISIZE_UNKNOWN),
mEditorHasBeenInitialized(false),
mIsProcessing(false)
@ -123,6 +124,13 @@ nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle,
nsTextControlFrame::~nsTextControlFrame() {}
nsIScrollableFrame* nsTextControlFrame::GetScrollTargetFrame() {
if (!mRootNode) {
return nullptr;
}
return do_QueryFrame(mRootNode->GetPrimaryFrame());
}
void nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) {
mScrollEvent.Revoke();
@ -143,8 +151,15 @@ void nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
mMutationObserver = nullptr;
}
// FIXME(emilio, bug 1400618): Do this after the child frames are destroyed.
aPostDestroyData.AddAnonymousContent(mRootNode.forget());
// If we're a subclass like nsNumberControlFrame, then it owns the root of the
// anonymous subtree where mRootNode is.
if (mClass == kClassID) {
aPostDestroyData.AddAnonymousContent(mRootNode.forget());
} else {
MOZ_ASSERT(!mRootNode || !mRootNode->IsRootOfAnonymousSubtree());
mRootNode = nullptr;
}
aPostDestroyData.AddAnonymousContent(mPlaceholderDiv.forget());
aPostDestroyData.AddAnonymousContent(mPreviewDiv.forget());
@ -212,9 +227,7 @@ LogicalSize nsTextControlFrame::CalcIntrinsicSize(
// Add in the size of the scrollbars for textarea
if (IsTextArea()) {
nsIFrame* first = PrincipalChildList().FirstChild();
nsIScrollableFrame* scrollableFrame = do_QueryFrame(first);
nsIScrollableFrame* scrollableFrame = GetScrollTargetFrame();
NS_ASSERTION(scrollableFrame, "Child must be scrollable");
if (scrollableFrame) {
@ -332,8 +345,6 @@ already_AddRefed<Element> nsTextControlFrame::CreateEmptyAnonymousDiv(
RefPtr<Element> divElement = NS_NewHTMLDivElement(nodeInfo.forget());
switch (aAnonymousDivType) {
case AnonymousDivType::Root: {
divElement->SetIsNativeAnonymousRoot();
// Make our root node editable
divElement->SetFlags(NODE_IS_EDITABLE);
@ -410,12 +421,26 @@ nsresult nsTextControlFrame::CreateAnonymousContent(
RefPtr<TextControlElement> textControlElement =
TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement);
nsresult rv = CreateRootNode();
NS_ENSURE_SUCCESS(rv, rv);
mRootNode = CreateEmptyAnonymousDiv(AnonymousDivType::Root);
if (NS_WARN_IF(!mRootNode)) {
return NS_ERROR_FAILURE;
}
// Bind the frame to its text control
rv = textControlElement->BindToFrame(this);
NS_ENSURE_SUCCESS(rv, rv);
mMutationObserver = new nsAnonDivObserver(*this);
mRootNode->AddMutationObserver(mMutationObserver);
// Bind the frame to its text control.
//
// This can realistically fail in paginated mode, where we may replicate
// fixed-positioned elements and the replicated frame will not get the chance
// to get an editor.
nsresult rv = textControlElement->BindToFrame(this);
if (NS_WARN_IF(NS_FAILED(rv))) {
mRootNode->RemoveMutationObserver(mMutationObserver);
mMutationObserver = nullptr;
mRootNode = nullptr;
return rv;
}
aElements.AppendElement(mRootNode);
CreatePlaceholderIfNeeded();
@ -477,18 +502,6 @@ void nsTextControlFrame::InitializeEagerlyIfNeeded() {
nsContentUtils::AddScriptRunner(initializer);
}
nsresult nsTextControlFrame::CreateRootNode() {
MOZ_ASSERT(!mRootNode);
mRootNode = CreateEmptyAnonymousDiv(AnonymousDivType::Root);
if (NS_WARN_IF(!mRootNode)) {
return NS_ERROR_FAILURE;
}
mMutationObserver = new nsAnonDivObserver(*this);
mRootNode->AddMutationObserver(mMutationObserver);
return NS_OK;
}
void nsTextControlFrame::CreatePlaceholderIfNeeded() {
MOZ_ASSERT(!mPlaceholderDiv);
@ -589,6 +602,30 @@ LogicalSize nsTextControlFrame::ComputeAutoSize(
return autoSize;
}
void nsTextControlFrame::ComputeBaseline(const ReflowInput& aReflowInput,
ReflowOutput& aDesiredSize) {
// If we're layout-contained, we have no baseline.
if (aReflowInput.mStyleDisplay->IsContainLayout()) {
mFirstBaseline = NS_INTRINSIC_ISIZE_UNKNOWN;
return;
}
WritingMode wm = aReflowInput.GetWritingMode();
// Calculate the baseline and store it in mFirstBaseline.
nscoord lineHeight = aReflowInput.ComputedBSize();
float inflation = nsLayoutUtils::FontSizeInflationFor(this);
if (!IsSingleLineTextControl()) {
lineHeight = ReflowInput::CalcLineHeight(
GetContent(), Style(), PresContext(), NS_UNCONSTRAINEDSIZE, inflation);
}
RefPtr<nsFontMetrics> fontMet =
nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
mFirstBaseline = nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight,
wm.IsLineInverted()) +
aReflowInput.ComputedLogicalBorderPadding().BStart(wm);
aDesiredSize.SetBlockStartAscent(mFirstBaseline);
}
void nsTextControlFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
@ -613,22 +650,7 @@ void nsTextControlFrame::Reflow(nsPresContext* aPresContext,
aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm));
aDesiredSize.SetSize(wm, finalSize);
if (!aReflowInput.mStyleDisplay->IsContainLayout()) {
// Calculate the baseline and store it in mFirstBaseline.
nscoord lineHeight = aReflowInput.ComputedBSize();
float inflation = nsLayoutUtils::FontSizeInflationFor(this);
if (!IsSingleLineTextControl()) {
lineHeight =
ReflowInput::CalcLineHeight(GetContent(), Style(), PresContext(),
NS_UNCONSTRAINEDSIZE, inflation);
}
RefPtr<nsFontMetrics> fontMet =
nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
mFirstBaseline = nsLayoutUtils::GetCenteredFontBaseline(
fontMet, lineHeight, wm.IsLineInverted()) +
aReflowInput.ComputedLogicalBorderPadding().BStart(wm);
aDesiredSize.SetBlockStartAscent(mFirstBaseline);
} // else: we're layout-contained, and so we have no baseline.
ComputeBaseline(aReflowInput, aDesiredSize);
// overflow handling
aDesiredSize.SetOverflowAreasToDesiredBounds();
@ -1182,6 +1204,21 @@ bool nsTextControlFrame::GetMaxLength(int32_t* aSize) {
// END IMPLEMENTING NS_IFORMCONTROLFRAME
// NOTE(emilio): This is needed because the root->primary frame map is not set
// up by the time this is called.
static nsIFrame* FindRootNodeFrame(const nsFrameList& aChildList,
const nsIContent* aRoot) {
for (nsIFrame* f : aChildList) {
if (f->GetContent() == aRoot) {
return f;
}
if (nsIFrame* root = FindRootNodeFrame(f->PrincipalChildList(), aRoot)) {
return root;
}
}
return nullptr;
}
void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
nsFrameList& aChildList) {
nsContainerFrame::SetInitialChildList(aListID, aChildList);
@ -1189,22 +1226,20 @@ void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
return;
}
// Mark the scroll frame as being a reflow root. This will allow
// incremental reflows to be initiated at the scroll frame, rather
// than descending from the root frame of the frame hierarchy.
if (nsIFrame* first = PrincipalChildList().FirstChild()) {
first->AddStateBits(NS_FRAME_REFLOW_ROOT);
// Mark the scroll frame as being a reflow root. This will allow incremental
// reflows to be initiated at the scroll frame, rather than descending from
// the root frame of the frame hierarchy.
if (nsIFrame* frame = FindRootNodeFrame(PrincipalChildList(), mRootNode)) {
frame->AddStateBits(NS_FRAME_REFLOW_ROOT);
TextControlElement* textControlElement =
TextControlElement::FromNode(GetContent());
auto* textControlElement = TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement);
textControlElement->InitializeKeyboardEventListeners();
nsPoint* contentScrollPos = GetProperty(ContentScrollPos());
if (contentScrollPos) {
if (nsPoint* contentScrollPos = GetProperty(ContentScrollPos())) {
// If we have a scroll pos stored to be passed to our anonymous
// div, do it here!
nsIStatefulFrame* statefulFrame = do_QueryFrame(first);
nsIStatefulFrame* statefulFrame = do_QueryFrame(frame);
NS_ASSERTION(statefulFrame,
"unexpected type of frame for the anonymous div");
UniquePtr<PresState> fakePresState = NewPresState();
@ -1213,12 +1248,13 @@ void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
RemoveProperty(ContentScrollPos());
delete contentScrollPos;
}
} else {
MOZ_ASSERT(!mRootNode || PrincipalChildList().IsEmpty());
}
}
void nsTextControlFrame::SetValueChanged(bool aValueChanged) {
TextControlElement* textControlElement =
TextControlElement::FromNode(GetContent());
auto* textControlElement = TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement);
if (mPlaceholderDiv) {
@ -1283,8 +1319,7 @@ nsresult nsTextControlFrame::UpdateValueDisplay(bool aNotify,
}
if (aBeforeEditorInit && value.IsEmpty()) {
nsIContent* node = mRootNode->GetFirstChild();
if (node) {
if (nsIContent* node = mRootNode->GetFirstChild()) {
mRootNode->RemoveChildNode(node, true);
}
return NS_OK;
@ -1309,20 +1344,15 @@ nsTextControlFrame::GetOwnedSelectionController(
}
nsFrameSelection* nsTextControlFrame::GetOwnedFrameSelection() {
TextControlElement* textControlElement =
TextControlElement::FromNode(GetContent());
auto* textControlElement = TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement);
return textControlElement->GetConstFrameSelection();
}
UniquePtr<PresState> nsTextControlFrame::SaveState() {
if (mRootNode) {
// Query the nsIStatefulFrame from the HTMLScrollFrame
nsIStatefulFrame* scrollStateFrame =
do_QueryFrame(mRootNode->GetPrimaryFrame());
if (scrollStateFrame) {
return scrollStateFrame->SaveState();
}
if (nsIStatefulFrame* scrollStateFrame =
do_QueryFrame(GetScrollTargetFrame())) {
return scrollStateFrame->SaveState();
}
return nullptr;
@ -1332,13 +1362,10 @@ NS_IMETHODIMP
nsTextControlFrame::RestoreState(PresState* aState) {
NS_ENSURE_ARG_POINTER(aState);
if (mRootNode) {
// Query the nsIStatefulFrame from the HTMLScrollFrame
nsIStatefulFrame* scrollStateFrame =
do_QueryFrame(mRootNode->GetPrimaryFrame());
if (scrollStateFrame) {
return scrollStateFrame->RestoreState(aState);
}
// Query the nsIStatefulFrame from the HTMLScrollFrame
if (nsIStatefulFrame* scrollStateFrame =
do_QueryFrame(GetScrollTargetFrame())) {
return scrollStateFrame->RestoreState(aState);
}
// Most likely, we don't have our anonymous content constructed yet, which
@ -1362,29 +1389,42 @@ void nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
*/
DO_GLOBAL_REFLOW_COUNT_DSP("nsTextControlFrame");
TextControlElement* textControlElement =
TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement);
auto* control = TextControlElement::FromNode(GetContent());
MOZ_ASSERT(control);
DisplayBorderBackgroundOutline(aBuilder, aLists);
nsIFrame* kid = mFrames.FirstChild();
// Redirect all lists to the Content list so that nothing can escape, ie
// opacity creating stacking contexts that then get sorted with stacking
// contexts external to us.
nsDisplayList* content = aLists.Content();
nsDisplayListSet set(content, content, content, content, content, content);
while (kid) {
// If the frame is the placeholder or preview frame, we should only show
// it if it has to be visible.
if (!((kid->GetContent() == mPlaceholderDiv &&
!textControlElement->GetPlaceholderVisibility()) ||
(kid->GetContent() == mPreviewDiv &&
!textControlElement->GetPreviewVisibility()))) {
BuildDisplayListForChild(aBuilder, kid, set, 0);
for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
nsIContent* kidContent = kid->GetContent();
Maybe<DisplayListClipState::AutoSaveRestore> overlayTextClip;
if (kidContent == mPlaceholderDiv && !control->GetPlaceholderVisibility()) {
continue;
}
kid = kid->GetNextSibling();
if (kidContent == mPreviewDiv && !control->GetPreviewVisibility()) {
continue;
}
// Clip the preview text to the root box, so that it doesn't, e.g., overlay
// with our <input type="number"> spin buttons.
//
// For other input types, this will be a noop since we size the root via
// ReflowTextControlChild, which sets the same available space for all
// children.
if (kidContent == mPlaceholderDiv || kidContent == mPreviewDiv) {
if (mRootNode) {
if (auto* root = mRootNode->GetPrimaryFrame()) {
overlayTextClip.emplace(aBuilder);
nsRect rootBox(aBuilder->ToReferenceFrame(root), root->GetSize());
overlayTextClip->ClipContentDescendants(rootBox);
}
}
}
BuildDisplayListForChild(aBuilder, kid, set, 0);
}
}

Просмотреть файл

@ -28,17 +28,23 @@ class Element;
} // namespace dom
} // namespace mozilla
class nsTextControlFrame final : public nsContainerFrame,
public nsIAnonymousContentCreator,
public nsITextControlFrame,
public nsIStatefulFrame {
class nsTextControlFrame : public nsContainerFrame,
public nsIAnonymousContentCreator,
public nsITextControlFrame,
public nsIStatefulFrame {
public:
NS_DECL_FRAMEARENA_HELPERS(nsTextControlFrame)
NS_DECLARE_FRAME_PROPERTY_DELETABLE(ContentScrollPos, nsPoint)
protected:
nsTextControlFrame(ComputedStyle*, nsPresContext*, nsIFrame::ClassID);
public:
explicit nsTextControlFrame(ComputedStyle* aStyle,
nsPresContext* aPresContext);
nsPresContext* aPresContext)
: nsTextControlFrame(aStyle, aPresContext, kClassID) {}
virtual ~nsTextControlFrame();
/**
@ -50,25 +56,26 @@ class nsTextControlFrame final : public nsContainerFrame,
* latter won't run content script too. Therefore, this won't run
* unsafe script.
*/
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void DestroyFrom(
nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) override;
MOZ_CAN_RUN_SCRIPT_BOUNDARY void DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData&) override;
virtual nsIScrollableFrame* GetScrollTargetFrame() override {
return do_QueryFrame(PrincipalChildList().FirstChild());
nsIScrollableFrame* GetScrollTargetFrame() override;
nsIScrollableFrame* GetScrollTargetFrame() const {
return const_cast<nsTextControlFrame*>(this)->GetScrollTargetFrame();
}
virtual nscoord GetMinISize(gfxContext* aRenderingContext) override;
virtual nscoord GetPrefISize(gfxContext* aRenderingContext) override;
nscoord GetMinISize(gfxContext* aRenderingContext) override;
nscoord GetPrefISize(gfxContext* aRenderingContext) override;
virtual mozilla::LogicalSize ComputeAutoSize(
mozilla::LogicalSize ComputeAutoSize(
gfxContext* aRenderingContext, mozilla::WritingMode aWM,
const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
const mozilla::LogicalSize& aMargin, const mozilla::LogicalSize& aBorder,
const mozilla::LogicalSize& aPadding, ComputeSizeFlags aFlags) override;
virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) override;
void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) override;
bool GetVerticalAlignBaseline(mozilla::WritingMode aWM,
nscoord* aBaseline) const override {
@ -92,21 +99,21 @@ class nsTextControlFrame final : public nsContainerFrame,
return true;
}
virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override;
virtual bool IsXULCollapsed() override;
nsSize GetXULMinSize(nsBoxLayoutState&) override;
bool IsXULCollapsed() override;
#ifdef ACCESSIBILITY
virtual mozilla::a11y::AccType AccessibleType() override;
mozilla::a11y::AccType AccessibleType() override;
#endif
#ifdef DEBUG_FRAME_DUMP
virtual nsresult GetFrameName(nsAString& aResult) const override {
nsresult GetFrameName(nsAString& aResult) const override {
aResult.AssignLiteral("nsTextControlFrame");
return NS_OK;
}
#endif
virtual bool IsFrameOfType(uint32_t aFlags) const override {
bool IsFrameOfType(uint32_t aFlags) const override {
return nsContainerFrame::IsFrameOfType(
aFlags & ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
}
@ -119,21 +126,19 @@ class nsTextControlFrame final : public nsContainerFrame,
#endif
// nsIAnonymousContentCreator
virtual nsresult CreateAnonymousContent(
nsTArray<ContentInfo>& aElements) override;
virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
uint32_t aFilter) override;
nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
uint32_t aFilter) override;
virtual void SetInitialChildList(ChildListID aListID,
nsFrameList& aChildList) override;
void SetInitialChildList(ChildListID, nsFrameList&) override;
virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists) override;
void BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists) override;
//==== BEGIN NSIFORMCONTROLFRAME
virtual void SetFocus(bool aOn, bool aRepaint) override;
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual nsresult SetFormProperty(
nsAtom* aName, const nsAString& aValue) override;
void SetFocus(bool aOn, bool aRepaint) override;
MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
SetFormProperty(nsAtom* aName, const nsAString& aValue) override;
//==== END NSIFORMCONTROLFRAME
@ -146,15 +151,14 @@ class nsTextControlFrame final : public nsContainerFrame,
SelectionDirection aDirection = eNone) override;
NS_IMETHOD GetOwnedSelectionController(
nsISelectionController** aSelCon) override;
virtual nsFrameSelection* GetOwnedFrameSelection() override;
nsFrameSelection* GetOwnedFrameSelection() override;
/**
* Ensure mEditor is initialized with the proper flags and the default value.
* @throws NS_ERROR_NOT_INITIALIZED if mEditor has not been created
* @throws various and sundry other things
*/
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual nsresult EnsureEditorInitialized()
override;
MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult EnsureEditorInitialized() override;
//==== END NSITEXTCONTROLFRAME
@ -168,8 +172,8 @@ class nsTextControlFrame final : public nsContainerFrame,
//==== OVERLOAD of nsIFrame
/** handler for attribute changes to mContent */
virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
int32_t aModType) override;
nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
int32_t aModType) override;
void GetText(nsString& aText);
@ -180,7 +184,7 @@ class nsTextControlFrame final : public nsContainerFrame,
*/
bool TextEquals(const nsAString& aText) const;
virtual nsresult PeekOffset(nsPeekOffsetStruct* aPos) override;
nsresult PeekOffset(nsPeekOffsetStruct* aPos) override;
NS_DECL_QUERYFRAME
@ -193,6 +197,8 @@ class nsTextControlFrame final : public nsContainerFrame,
nsReflowStatus& aStatus,
ReflowOutput& aParentDesiredSize);
void ComputeBaseline(const ReflowInput&, ReflowOutput&);
public: // for methods who access nsTextControlFrame directly
void SetValueChanged(bool aValueChanged);
@ -200,6 +206,8 @@ class nsTextControlFrame final : public nsContainerFrame,
mozilla::dom::Element* GetPreviewNode() const { return mPreviewDiv; }
mozilla::dom::Element* GetPlaceholderNode() const { return mPlaceholderDiv; }
// called by the focus listener
nsresult MaybeBeginSecureKeyboardInput();
void MaybeEndSecureKeyboardInput();
@ -335,7 +343,7 @@ class nsTextControlFrame final : public nsContainerFrame,
return true;
}
private:
protected:
class nsAnonDivObserver;
nsresult CreateRootNode();

Просмотреть файл

@ -4813,18 +4813,16 @@ already_AddRefed<Element> ScrollFrameHelper::MakeScrollbar(
}
bool ScrollFrameHelper::IsForTextControlWithNoScrollbars() const {
nsIFrame* parent = mOuter->GetParent();
// The anonymous <div> used by <inputs> never gets scrollbars.
nsITextControlFrame* textFrame = do_QueryFrame(parent);
if (textFrame) {
// Make sure we are not a text area.
HTMLTextAreaElement* textAreaElement =
HTMLTextAreaElement::FromNode(parent->GetContent());
if (!textAreaElement) {
return true;
}
// FIXME(emilio): we should probably make the scroller inside <input> an
// internal pseudo-element, and then this would be simpler.
//
// Also, this could just use scrollbar-width these days.
auto* content = mOuter->GetContent();
if (!content) {
return false;
}
return false;
auto* input = content->GetClosestNativeAnonymousSubtreeRootParent();
return input && input->IsHTMLElement(nsGkAtoms::input);
}
nsresult ScrollFrameHelper::CreateAnonymousContent(

Просмотреть файл

@ -5,7 +5,6 @@
/* None of these selectors should match from content */
input[type=number]::-moz-number-wrapper,
input[type=number]::-moz-number-text,
input[type=number]::-moz-number-spin-box,
input[type=number]::-moz-number-spin-up,
input[type=number]::-moz-number-spin-down {

Просмотреть файл

@ -67,9 +67,6 @@ CSS_PSEUDO_ELEMENT(mozMathAnonymous, ":-moz-math-anonymous",
CSS_PSEUDO_ELEMENT(mozNumberWrapper, ":-moz-number-wrapper",
CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)
CSS_PSEUDO_ELEMENT(mozNumberText, ":-moz-number-text",
CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)
CSS_PSEUDO_ELEMENT(mozNumberSpinBox, ":-moz-number-spin-box",
CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)

Просмотреть файл

@ -102,7 +102,7 @@ input {
overflow-clip-box: padding-box content-box;
}
input > .anonymous-div,
input .anonymous-div,
input::placeholder {
word-wrap: normal;
/* Make the line-height equal to the available height */
@ -144,12 +144,12 @@ textarea > scrollbar {
cursor: default;
}
textarea > .anonymous-div,
input > .anonymous-div,
textarea .anonymous-div,
input .anonymous-div,
input::placeholder,
textarea::placeholder,
input > .preview-div
textarea > .preview-div {
input .preview-div
textarea .preview-div {
overflow: auto;
border: 0px;
padding: inherit;
@ -163,14 +163,14 @@ textarea > .preview-div {
overflow-clip-box: inherit;
}
input > .anonymous-div,
input .anonymous-div,
input::placeholder,
input > .preview-div {
input .preview-div {
white-space: pre;
}
input[type=password] > .anonymous-div,
input[type=password] > .preview-div {
input[type=password] .anonymous-div,
input[type=password] .preview-div {
/*
* In password fields, any character should be put same direction. Otherwise,
* caret position at typing tells everybody whether the character is an RTL
@ -191,8 +191,8 @@ textarea > .anonymous-div.inherit-overflow {
input::placeholder,
textarea::placeholder,
input > .preview-div,
textarea > .preview-div {
input .preview-div,
textarea .preview-div {
/*
* Changing display to inline can leads to broken behaviour and will assert.
*/
@ -218,7 +218,7 @@ textarea::placeholder {
}
textarea::placeholder,
textarea > .preview-div {
textarea .preview-div {
white-space: pre-wrap !important;
}
@ -993,27 +993,12 @@ input[type=number]::-moz-number-wrapper {
block-size: 100%;
}
input[type=number]::-moz-number-text {
input[type=number] .anonymous-div {
display: block; /* Flex items must be block-level. Normally we do fixup in
the style system to ensure this, but that fixup is disabled
inside of form controls. So, we hardcode display here. */
-moz-appearance: none;
/* work around autofocus bug 939248 on initial load */
-moz-user-modify: read-write;
/* This pseudo-element is also an 'input' element (nested inside and
* distinct from the <input type=number> element) so we need to prevent the
* explicit setting of 'text-align' by the general CSS rule for 'input'
* above. We want to inherit its value from its <input type=number>
* ancestor, not have that general CSS rule reset it.
*/
text-align: unset;
text-decoration: inherit;
ime-mode: inherit;
flex: 1;
min-inline-size: 0;
padding: unset;
border: 0;
margin: 0;
}
input[type=number]::-moz-number-spin-box {

Просмотреть файл

@ -184,13 +184,7 @@ class AutofillDelegateTest : BaseSessionTest() {
// Wait on the promises and check for correct values.
for ((key, actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
assertThat("Auto-filled value must match ($key)", actual, equalTo(expected))
// <input type=number> nodes don't get InputEvent events.
if (key == "#number1") {
assertThat("input type=number event should be dispatched with Event interface", eventInterface, equalTo("Event"))
} else {
assertThat("input event should be dispatched with InputEvent interface", eventInterface, equalTo("InputEvent"))
}
assertThat("input event should be dispatched with InputEvent interface", eventInterface, equalTo("InputEvent"))
}
}

Просмотреть файл

@ -1869,22 +1869,7 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
#[inline]
fn pseudo_element_originating_element(&self) -> Option<Self> {
debug_assert!(self.is_pseudo_element());
let parent = self.closest_anon_subtree_root_parent()?;
// FIXME(emilio): Special-case for <input type="number">s
// pseudo-elements, which are nested NAC. Probably nsNumberControlFrame
// should instead inherit from nsTextControlFrame, and then this could
// go away.
if let Some(PseudoElement::MozNumberText) = parent.implemented_pseudo_element() {
debug_assert_eq!(
self.implemented_pseudo_element().unwrap(),
PseudoElement::Placeholder,
"You added a new pseudo, do you really want this?"
);
return parent.closest_anon_subtree_root_parent();
}
Some(parent)
self.closest_anon_subtree_root_parent()
}
#[inline]

Просмотреть файл

@ -0,0 +1,3 @@
[focus-input-type-switch.html]
max-asserts: 4
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=697207

Просмотреть файл

@ -1,6 +1,4 @@
[form-validation-validity-rangeOverflow.html]
[[INPUT in NUMBER status\] The value is greater than max(special floating number)]
expected: FAIL
[[INPUT in TIME status\] The time is inside the accepted range for reversed range]
expected: FAIL

Просмотреть файл

@ -1,7 +1,3 @@
[form-validation-validity-rangeUnderflow.html]
[[INPUT in NUMBER status\] The value is less than min(special floating number)]
expected: FAIL
[[INPUT in TIME status\] The time is max for reversed range]
expected: FAIL

Просмотреть файл

@ -1,4 +0,0 @@
[form-validation-validity-stepMismatch.html]
[[INPUT in NUMBER status\] The step attribute is not set and the value attribute is a floating number]
expected: FAIL

Просмотреть файл

@ -6,3 +6,6 @@
[value = +1]
expected: FAIL
[value ending with '.']
expected: FAIL
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1605158

Просмотреть файл

@ -0,0 +1,26 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>Inputs remain focusable upon changing type</title>
<link rel="help" href="https://wicg.github.io/auxclick">
<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=981248">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<h1>Can still focus on inputs that change types</h1>
<input type="text" value="123" onfocus="javascript:event.target.type='number'"
onblur="javascript:event.target.type='text'">
<script>
promise_test(() => {
// Click the input to attempt to focus on it
const target = document.querySelector("input");
const actions = new test_driver.Actions();
return actions.pointerMove(0, 0, {origin: target})
.pointerDown({button: actions.ButtonType.LEFT})
.pointerUp({button: actions.ButtonType.LEFT})
.send()
.then(() => assert_equals(document.activeElement, target,
"The element was correctly focused"));
}, "Can change an input's type during focus handler without breaking focus");
</script>

Просмотреть файл

@ -30,7 +30,7 @@ customElements.define(
<toolbarbutton id="print-preview-navigateHome" class="print-preview-navigate-button tabbable" oncommand="parentNode.navigate(0, 0, 'home');" data-l10n-id="printpreview-homearrow"/>
<toolbarbutton id="print-preview-navigatePrevious" class="print-preview-navigate-button tabbable" oncommand="parentNode.navigate(-1, 0, 0);" data-l10n-id="printpreview-previousarrow"/>
<hbox align="center" pack="center">
<html:input id="print-preview-pageNumber" class="input-number-mozbox" hidespinbuttons="true" type="number" value="1" min="1"/>
<html:input id="print-preview-pageNumber" hidespinbuttons="true" type="number" value="1" min="1"/>
<label data-l10n-id="printpreview-of"/>
<label id="print-preview-totalPages" value="1"/>
</hbox>

Просмотреть файл

@ -15,20 +15,3 @@ input[type="number"] {
input[type="number"][hidespinbuttons="true"] {
-moz-appearance: textfield !important;
}
/* input[type=number] uses display: flex; by default which is incompatible with XUL flexbox
Forcing XUL flexbox allows changing the size of the input. */
.input-number-mozbox,
.input-number-mozbox::-moz-number-wrapper {
display: -moz-box;
-moz-box-align: center;
}
.input-number-mozbox::-moz-number-wrapper,
.input-number-mozbox::-moz-number-text {
-moz-box-flex: 1;
}
.input-number-mozbox::-moz-number-wrapper {
width: 100%;
}

Просмотреть файл

@ -53,16 +53,6 @@ EventStates nsNativeTheme::GetContentState(nsIFrame* aFrame,
if (frameContent->IsElement()) {
flags = frameContent->AsElement()->State();
// <input type=number> needs special handling since its nested native
// anonymous <input type=text> takes focus for it.
if (aAppearance == StyleAppearance::NumberInput &&
frameContent->IsHTMLElement(nsGkAtoms::input)) {
nsNumberControlFrame* numberControlFrame = do_QueryFrame(aFrame);
if (numberControlFrame && numberControlFrame->IsFocused()) {
flags |= NS_EVENT_STATE_FOCUS;
}
}
nsNumberControlFrame* numberControlFrame =
nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
if (numberControlFrame &&

Просмотреть файл

@ -2458,7 +2458,6 @@ STATIC_ATOMS = [
PseudoElementAtom("PseudoElement_mozFocusOuter", ":-moz-focus-outer"),
PseudoElementAtom("PseudoElement_mozMathAnonymous", ":-moz-math-anonymous"),
PseudoElementAtom("PseudoElement_mozNumberWrapper", ":-moz-number-wrapper"),
PseudoElementAtom("PseudoElement_mozNumberText", ":-moz-number-text"),
PseudoElementAtom("PseudoElement_mozNumberSpinBox", ":-moz-number-spin-box"),
PseudoElementAtom("PseudoElement_mozNumberSpinUp", ":-moz-number-spin-up"),
PseudoElementAtom("PseudoElement_mozNumberSpinDown", ":-moz-number-spin-down"),