Backed out 2 changesets (bug 981248) for causing multiple failures.

CLOSED TREE

Backed out changeset 7a96708cc8b7 (bug 981248)
Backed out changeset 1eace7bd28d9 (bug 981248)
This commit is contained in:
Mihai Alexandru Michis
2020-01-14 19:28:17 +02:00
parent c7d67b115e
commit b51ee7e327
43 changed files with 1166 additions and 366 deletions

View File

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

View File

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

View File

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

View File

@@ -711,8 +711,16 @@
obj = { obj = {
role: ROLE_SPINBUTTON, role: ROLE_SPINBUTTON,
interfaces: [ nsIAccessibleValue ], interfaces: [ nsIAccessibleValue ],
children: [
{
role: ROLE_ENTRY,
extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE,
actions: "activate",
interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ],
children: [ children: [
{ role: ROLE_TEXT_LEAF }, { role: ROLE_TEXT_LEAF },
],
},
{ {
role: ROLE_PUSHBUTTON, role: ROLE_PUSHBUTTON,
actions: "press", actions: "press",

View File

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

View File

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

View File

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

View File

@@ -37,6 +37,7 @@
#include "BrowserChild.h" #include "BrowserChild.h"
#include "nsFrameLoader.h" #include "nsFrameLoader.h"
#include "nsHTMLDocument.h" #include "nsHTMLDocument.h"
#include "nsNumberControlFrame.h"
#include "nsNetUtil.h" #include "nsNetUtil.h"
#include "nsRange.h" #include "nsRange.h"
@@ -315,6 +316,24 @@ Element* nsFocusManager::GetFocusedDescendant(
// static // static
Element* nsFocusManager::GetRedirectedFocus(nsIContent* aContent) { 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 #ifdef MOZ_XUL
if (aContent->IsXULElement()) { if (aContent->IsXULElement()) {
nsCOMPtr<nsIDOMXULMenuListElement> menulist = nsCOMPtr<nsIDOMXULMenuListElement> menulist =
@@ -1460,7 +1479,8 @@ Element* nsFocusManager::FlushAndCheckIfFocusable(Element* aElement,
// this is a special case for some XUL elements or input number, where an // this is a special case for some XUL elements or input number, where an
// anonymous child is actually focusable and not the element itself. // anonymous child is actually focusable and not the element itself.
if (RefPtr<Element> redirectedFocus = GetRedirectedFocus(aElement)) { RefPtr<Element> redirectedFocus = GetRedirectedFocus(aElement);
if (redirectedFocus) {
return FlushAndCheckIfFocusable(redirectedFocus, aFlags); return FlushAndCheckIfFocusable(redirectedFocus, aFlags);
} }

View File

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

View File

@@ -1122,7 +1122,7 @@ void IMEStateManager::SetInputContextForChildProcess(
SetInputContext(widget, aInputContext, aAction); SetInputContext(widget, aInputContext, aAction);
} }
static bool IsNextFocusableElementTextControl(Element* aInputContent) { static bool IsNextFocusableElementTextOrNumberControl(Element* aInputContent) {
nsFocusManager* fm = nsFocusManager::GetFocusManager(); nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (!fm) { if (!fm) {
return false; return false;
@@ -1136,7 +1136,7 @@ static bool IsNextFocusableElementTextControl(Element* aInputContent) {
} }
nextContent = nextContent->FindFirstNonChromeOnlyAccessContent(); nextContent = nextContent->FindFirstNonChromeOnlyAccessContent();
nsCOMPtr<nsIFormControl> nextControl = do_QueryInterface(nextContent); nsCOMPtr<nsIFormControl> nextControl = do_QueryInterface(nextContent);
if (!nextControl || !nextControl->IsTextControl(false)) { if (!nextControl || !nextControl->IsTextOrNumberControl(false)) {
return false; return false;
} }
@@ -1217,7 +1217,8 @@ static void GetActionHint(nsIContent& aContent, nsAString& aActionHint) {
if (!isLastElement && formElement) { if (!isLastElement && formElement) {
// If next tabbable content in form is text control, hint should be "next" // If next tabbable content in form is text control, hint should be "next"
// even there is submit in form. // even there is submit in form.
if (IsNextFocusableElementTextControl(inputContent->AsElement())) { if (IsNextFocusableElementTextOrNumberControl(
inputContent->AsElement())) {
// This is focusable text control // This is focusable text control
// XXX What good hint for read only field? // XXX What good hint for read only field?
aActionHint.AssignLiteral("next"); aActionHint.AssignLiteral("next");
@@ -1273,7 +1274,21 @@ void IMEStateManager::SetIMEState(const IMEState& aState,
if (aContent && if (aContent &&
aContent->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) { aContent->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) {
if (!aContent->IsHTMLElement(nsGkAtoms::textarea)) { if (!aContent->IsHTMLElement(nsGkAtoms::textarea)) {
aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::type, // <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); context.mHTMLInputType);
} else { } else {
context.mHTMLInputType.Assign(nsGkAtoms::textarea->GetUTF16String()); context.mHTMLInputType.Assign(nsGkAtoms::textarea->GetUTF16String());

View File

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

View File

@@ -128,11 +128,12 @@ namespace dom {
// (1 << 11 is unused) // (1 << 11 is unused)
#define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12) #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
#define NS_PRE_HANDLE_BLUR_EVENT (1 << 13) #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_IN_SUBMIT_CLICK (1 << 15)
#define NS_CONTROL_TYPE(bits) \ #define NS_CONTROL_TYPE(bits) \
((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | \ ((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | \
NS_ORIGINAL_INDETERMINATE_VALUE | NS_PRE_HANDLE_BLUR_EVENT | \ NS_ORIGINAL_INDETERMINATE_VALUE | NS_PRE_HANDLE_BLUR_EVENT | \
NS_IN_SUBMIT_CLICK)) NS_PRE_HANDLE_INPUT_EVENT | NS_IN_SUBMIT_CLICK))
// whether textfields should be selected once focused: // whether textfields should be selected once focused:
// -1: no, 1: yes, 0: uninitialized // -1: no, 1: yes, 0: uninitialized
@@ -1300,11 +1301,15 @@ nsresult HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) { aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
SetDirectionFromValue(aNotify); SetDirectionFromValue(aNotify);
} else if (aName == nsGkAtoms::lang) { } else if (aName == nsGkAtoms::lang) {
// FIXME(emilio, bug 1605158): This doesn't account for lang changes on
// ancestors.
if (mType == NS_FORM_INPUT_NUMBER) { if (mType == NS_FORM_INPUT_NUMBER) {
// The validity of our value may have changed based on the locale. // Update the value that is displayed to the user to the new locale:
UpdateValidityState(); nsAutoString value;
GetNonFileValueInternal(value);
nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
numberControlFrame->SetValueOfAnonTextControl(value);
}
} }
} else if (aName == nsGkAtoms::autocomplete) { } else if (aName == nsGkAtoms::autocomplete) {
// Clear the cached @autocomplete attribute and autocompleteInfo state. // Clear the cached @autocomplete attribute and autocompleteInfo state.
@@ -1431,17 +1436,12 @@ uint32_t HTMLInputElement::Width() {
return GetWidthHeightForImage(currentRequest).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) { void HTMLInputElement::GetValue(nsAString& aValue, CallerType aCallerType) {
GetValueInternal(aValue, aCallerType); GetValueInternal(aValue, aCallerType);
if (SanitizesOnValueGetter()) { // Don't return non-sanitized value for types that are experimental on mobile
// or datetime types
if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
SanitizeValue(aValue); SanitizeValue(aValue);
} }
} }
@@ -1583,11 +1583,11 @@ void HTMLInputElement::SetValue(const nsAString& aValue, CallerType aCallerType,
// Some types sanitize value, so GetValue doesn't return pure // Some types sanitize value, so GetValue doesn't return pure
// previous value correctly. // previous value correctly.
//
// FIXME(emilio): Shouldn't above just use GetNonFileValueInternal() to
// get the unsanitized value?
nsresult rv = SetValueInternal( nsresult rv = SetValueInternal(
aValue, SanitizesOnValueGetter() ? nullptr : &currentValue, aValue,
(IsExperimentalMobileType(mType) || IsDateTimeInputType(mType))
? nullptr
: &currentValue,
TextControlState::eSetValue_ByContent | TextControlState::eSetValue_ByContent |
TextControlState::eSetValue_Notify | TextControlState::eSetValue_Notify |
TextControlState::eSetValue_MoveCursorToEndIfValueChanged); TextControlState::eSetValue_MoveCursorToEndIfValueChanged);
@@ -2163,17 +2163,25 @@ void HTMLInputElement::UpdateValidityState() {
bool HTMLInputElement::MozIsTextField(bool aExcludePassword) { bool HTMLInputElement::MozIsTextField(bool aExcludePassword) {
// TODO: temporary until bug 888320 is fixed. // 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 false;
} }
return IsSingleLineTextControl(aExcludePassword); 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, void HTMLInputElement::SetUserInput(const nsAString& aValue,
nsIPrincipal& aSubjectPrincipal) { nsIPrincipal& aSubjectPrincipal) {
if (mType == NS_FORM_INPUT_FILE && !aSubjectPrincipal.IsSystemPrincipal()) { if (mType == NS_FORM_INPUT_FILE && !aSubjectPrincipal.IsSystemPrincipal()) {
@@ -2638,7 +2646,15 @@ nsresult HTMLInputElement::SetValueInternal(const nsAString& aValue,
if (setValueChanged) { if (setValueChanged) {
SetValueChanged(true); SetValueChanged(true);
} }
if (mType == NS_FORM_INPUT_RANGE) { 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) {
nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame()); nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
if (frame) { if (frame) {
frame->UpdateForValueChange(); frame->UpdateForValueChange();
@@ -2917,6 +2933,19 @@ void HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify) {
} }
void HTMLInputElement::Blur(ErrorResult& aError) { 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) && if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
!IsExperimentalMobileType(mType)) { !IsExperimentalMobileType(mType)) {
if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) { if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
@@ -2933,6 +2962,19 @@ void HTMLInputElement::Blur(ErrorResult& aError) {
void HTMLInputElement::Focus(const FocusOptions& aOptions, void HTMLInputElement::Focus(const FocusOptions& aOptions,
ErrorResult& aError) { 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) && if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
!IsExperimentalMobileType(mType)) { !IsExperimentalMobileType(mType)) {
if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) { if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
@@ -2967,6 +3009,14 @@ void HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
} }
void HTMLInputElement::Select() { void HTMLInputElement::Select() {
if (mType == NS_FORM_INPUT_NUMBER) {
nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame) {
numberControlFrame->HandleSelectCall();
}
return;
}
if (!IsSingleLineTextControl(false)) { if (!IsSingleLineTextControl(false)) {
return; return;
} }
@@ -3256,10 +3306,65 @@ void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
StopNumberControlSpinnerSpin(); 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); 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 // 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 // same as the original target's first non-native ancestor (we are moving
// inside of the same element). // inside of the same element).
@@ -3302,7 +3407,26 @@ nsresult HTMLInputElement::PreHandleEvent(EventChainVisitor& aVisitor) {
} }
FireChangeEventIfNeeded(); FireChangeEventIfNeeded();
} }
return nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor); 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;
} }
void HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent* aEvent) { void HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent* aEvent) {
@@ -3449,7 +3573,8 @@ void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) {
// We only do this if there actually is a value typed in by/displayed to // 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 // the user. (IsValid() can return false if the 'required' attribute is
// set and the value is the empty string.) // set and the value is the empty string.)
if (!IsValueEmpty()) { nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame && !numberControlFrame->AnonTextControlIsEmpty()) {
// We pass 'true' for UpdateValidityUIBits' aIsFocused argument // We pass 'true' for UpdateValidityUIBits' aIsFocused argument
// regardless because we need the UI to update _now_ or the user will // regardless because we need the UI to update _now_ or the user will
// wonder why the step behavior isn't functioning. // wonder why the step behavior isn't functioning.
@@ -3473,6 +3598,10 @@ void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) {
// is small, so we should be fine here.) // is small, so we should be fine here.)
SetValueInternal(newVal, TextControlState::eSetValue_BySetUserInput | SetValueInternal(newVal, TextControlState::eSetValue_BySetUserInput |
TextControlState::eSetValue_Notify); TextControlState::eSetValue_Notify);
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Failed to dispatch input event");
} }
static bool SelectTextFieldOnFocus() { static bool SelectTextFieldOnFocus() {
@@ -3964,7 +4093,9 @@ nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
wheelEvent->mDeltaY != 0 && wheelEvent->mDeltaY != 0 &&
wheelEvent->mDeltaMode != WheelEvent_Binding::DOM_DELTA_PIXEL) { wheelEvent->mDeltaMode != WheelEvent_Binding::DOM_DELTA_PIXEL) {
if (mType == NS_FORM_INPUT_NUMBER) { if (mType == NS_FORM_INPUT_NUMBER) {
if (nsContentUtils::IsFocusedContent(this)) { nsNumberControlFrame* numberControlFrame =
do_QueryFrame(GetPrimaryFrame());
if (numberControlFrame && numberControlFrame->IsFocused()) {
StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1); StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
FireChangeEventIfNeeded(); FireChangeEventIfNeeded();
aVisitor.mEvent->PreventDefault(); aVisitor.mEvent->PreventDefault();
@@ -5890,7 +6021,50 @@ EventStates HTMLInputElement::IntrinsicState() const {
bool HTMLInputElement::ShouldShowPlaceholder() const { bool HTMLInputElement::ShouldShowPlaceholder() const {
MOZ_ASSERT(PlaceholderApplies()); MOZ_ASSERT(PlaceholderApplies());
return IsValueEmpty();
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);
} }
static nsTArray<OwningFileOrDirectory> RestoreFileContentData( static nsTArray<OwningFileOrDirectory> RestoreFileContentData(
@@ -6241,7 +6415,8 @@ bool HTMLInputElement::PlaceholderApplies() const {
if (IsDateTimeInputType(mType)) { if (IsDateTimeInputType(mType)) {
return false; return false;
} }
return IsSingleLineTextControl(false);
return IsSingleLineTextOrNumberControl(false);
} }
bool HTMLInputElement::DoesMinMaxApply() const { bool HTMLInputElement::DoesMinMaxApply() const {

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
#include "mozilla/TextControlState.h" #include "mozilla/TextControlState.h"
#include "mozilla/dom/HTMLInputElement.h" #include "mozilla/dom/HTMLInputElement.h"
#include "ICUUtils.h" #include "nsNumberControlFrame.h"
bool NumericInputTypeBase::IsRangeOverflow() const { bool NumericInputTypeBase::IsRangeOverflow() const {
mozilla::Decimal maximum = mInputElement->GetMaximum(); mozilla::Decimal maximum = mInputElement->GetMaximum();
@@ -94,15 +94,11 @@ nsresult NumericInputTypeBase::GetRangeUnderflowMessage(nsAString& aMessage) {
bool NumericInputTypeBase::ConvertStringToNumber( bool NumericInputTypeBase::ConvertStringToNumber(
nsAString& aValue, mozilla::Decimal& aResultValue) const { nsAString& aValue, mozilla::Decimal& aResultValue) const {
// 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()) {
aResultValue = mozilla::dom::HTMLInputElement::StringToDecimal(aValue); aResultValue = mozilla::dom::HTMLInputElement::StringToDecimal(aValue);
if (!aResultValue.isFinite()) {
return false;
} }
return aResultValue.isFinite(); return true;
} }
bool NumericInputTypeBase::ConvertNumberToString( bool NumericInputTypeBase::ConvertNumberToString(
@@ -136,7 +132,19 @@ bool NumberInputType::IsValueMissing() const {
bool NumberInputType::HasBadInput() const { bool NumberInputType::HasBadInput() const {
nsAutoString value; nsAutoString value;
GetNonFileValueInternal(value); GetNonFileValueInternal(value);
return !value.IsEmpty() && mInputElement->GetValueAsDecimal().isNaN(); 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;
} }
nsresult NumberInputType::GetValueMissingMessage(nsAString& aMessage) { nsresult NumberInputType::GetValueMissingMessage(nsAString& aMessage) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -111,6 +111,10 @@ function checkIsInvalid(element, infoStr)
{ {
ok(element.validity.badInput, ok(element.validity.badInput,
"Element should suffer from bad input for " + infoStr); "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.validity.valid, "Element should not be valid for " + infoStr);
ok(!element.checkValidity(), "checkValidity() should return false for " + infoStr); ok(!element.checkValidity(), "checkValidity() should return false for " + infoStr);
ok(gInvalid, "The invalid event should have been thrown for " + infoStr); ok(gInvalid, "The invalid event should have been thrown for " + infoStr);

View File

@@ -172,6 +172,18 @@ partial interface HTMLInputElement {
[ChromeOnly] [ChromeOnly]
void mozSetDndFilesAndDirectories(sequence<(File or Directory)> list); 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); boolean mozIsTextField(boolean aExcludePassword);
[ChromeOnly] [ChromeOnly]

View File

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

View File

@@ -41,18 +41,270 @@ NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame)
NS_QUERYFRAME_HEAD(nsNumberControlFrame) NS_QUERYFRAME_HEAD(nsNumberControlFrame)
NS_QUERYFRAME_ENTRY(nsNumberControlFrame) NS_QUERYFRAME_ENTRY(nsNumberControlFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsTextControlFrame) NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
nsNumberControlFrame::nsNumberControlFrame(ComputedStyle* aStyle, nsNumberControlFrame::nsNumberControlFrame(ComputedStyle* aStyle,
nsPresContext* aPresContext) nsPresContext* aPresContext)
: nsTextControlFrame(aStyle, aPresContext, kClassID) {} : nsContainerFrame(aStyle, aPresContext, kClassID),
mHandlingInputEvent(false) {}
void nsNumberControlFrame::DestroyFrom(nsIFrame* aDestructRoot, void nsNumberControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) { 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()); aPostDestroyData.AddAnonymousContent(mOuterWrapper.forget());
nsTextControlFrame::DestroyFrom(aDestructRoot, aPostDestroyData); nsContainerFrame::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( already_AddRefed<Element> nsNumberControlFrame::MakeAnonymousElement(
Element* aParent, nsAtom* aTagName, PseudoStyleType aPseudoType) { Element* aParent, nsAtom* aTagName, PseudoStyleType aPseudoType) {
// Get the NodeInfoManager and tag necessary to create the anonymous divs. // Get the NodeInfoManager and tag necessary to create the anonymous divs.
@@ -80,12 +332,10 @@ nsresult nsNumberControlFrame::CreateAnonymousContent(
// //
// input // input
// div - outer wrapper with "display:flex" by default // div - outer wrapper with "display:flex" by default
// div - editor root // input - text input field
// div - spin box wrapping up/down arrow buttons // div - spin box wrapping up/down arrow buttons
// div - spin up (up arrow button) // div - spin up (up arrow button)
// div - spin down (down 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 // If you change this, be careful to change the destruction order in
// nsNumberControlFrame::DestroyFrom. // nsNumberControlFrame::DestroyFrom.
@@ -96,18 +346,48 @@ nsresult nsNumberControlFrame::CreateAnonymousContent(
aElements.AppendElement(mOuterWrapper); aElements.AppendElement(mOuterWrapper);
nsTArray<ContentInfo> nestedContent; // Create the ::-moz-number-text pseudo-element:
nsTextControlFrame::CreateAnonymousContent(nestedContent); mTextField = MakeAnonymousElement(mOuterWrapper, nsGkAtoms::input,
for (auto& content : nestedContent) { PseudoStyleType::mozNumberText);
// The root goes inside the container.
if (content.mContent == mRootNode) { mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
mOuterWrapper->AppendChildTo(content.mContent, false); NS_LITERAL_STRING("text"), false);
} else {
// The rest (placeholder and preview), directly under us. HTMLInputElement* content = HTMLInputElement::FromNode(mContent);
aElements.AppendElement(std::move(content)); 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);
} }
// 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) { if (StyleDisplay()->mAppearance == StyleAppearance::Textfield) {
// The author has elected to hide the spinner by setting this // The author has elected to hide the spinner by setting this
// -moz-appearance. We will reframe if it changes. // -moz-appearance. We will reframe if it changes.
@@ -129,6 +409,19 @@ nsresult nsNumberControlFrame::CreateAnonymousContent(
return NS_OK; 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 */ /* static */
nsNumberControlFrame* nsNumberControlFrame::GetNumberControlFrameForTextField( nsNumberControlFrame* nsNumberControlFrame::GetNumberControlFrameForTextField(
nsIFrame* aFrame) { nsIFrame* aFrame) {
@@ -232,6 +525,31 @@ bool nsNumberControlFrame::SpinnerDownButtonIsDepressed() const {
->NumberSpinnerDownButtonIsDepressed(); ->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 \ #define STYLES_DISABLING_NATIVE_THEMING \
NS_AUTHOR_SPECIFIED_BACKGROUND | NS_AUTHOR_SPECIFIED_PADDING | \ NS_AUTHOR_SPECIFIED_BACKGROUND | NS_AUTHOR_SPECIFIED_PADDING | \
NS_AUTHOR_SPECIFIED_BORDER NS_AUTHOR_SPECIFIED_BORDER
@@ -255,17 +573,128 @@ bool nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const {
spinDownFrame, STYLES_DISABLING_NATIVE_THEMING); 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( void nsNumberControlFrame::AppendAnonymousContentTo(
nsTArray<nsIContent*>& aElements, uint32_t aFilter) { nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
// Only one direct anonymous child:
if (mOuterWrapper) { if (mOuterWrapper) {
aElements.AppendElement(mOuterWrapper); aElements.AppendElement(mOuterWrapper);
} }
if (mPlaceholderDiv) { }
aElements.AppendElement(mPlaceholderDiv);
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 (mPreviewDiv) {
aElements.AppendElement(mPreviewDiv); // 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);
} }
// 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 #ifdef ACCESSIBILITY

View File

@@ -9,7 +9,6 @@
#include "mozilla/Attributes.h" #include "mozilla/Attributes.h"
#include "nsContainerFrame.h" #include "nsContainerFrame.h"
#include "nsTextControlFrame.h"
#include "nsIFormControlFrame.h" #include "nsIFormControlFrame.h"
#include "nsIAnonymousContentCreator.h" #include "nsIAnonymousContentCreator.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
@@ -29,10 +28,10 @@ class HTMLInputElement;
/** /**
* This frame type is used for <input type=number>. * This frame type is used for <input type=number>.
*
* TODO(emilio): Maybe merge with nsTextControlFrame?
*/ */
class nsNumberControlFrame final : public nsTextControlFrame { class nsNumberControlFrame final : public nsContainerFrame,
public nsIAnonymousContentCreator,
public nsIFormControlFrame {
friend nsIFrame* NS_NewNumberControlFrame(mozilla::PresShell* aPresShell, friend nsIFrame* NS_NewNumberControlFrame(mozilla::PresShell* aPresShell,
ComputedStyle* aStyle); ComputedStyle* aStyle);
@@ -49,23 +48,80 @@ class nsNumberControlFrame final : public nsTextControlFrame {
NS_DECL_QUERYFRAME NS_DECL_QUERYFRAME
NS_DECL_FRAMEARENA_HELPERS(nsNumberControlFrame) NS_DECL_FRAMEARENA_HELPERS(nsNumberControlFrame)
void DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData&) override; virtual void DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) override;
virtual void ContentStatesChanged(mozilla::EventStates aStates) override;
#ifdef ACCESSIBILITY #ifdef ACCESSIBILITY
mozilla::a11y::AccType AccessibleType() override; virtual mozilla::a11y::AccType AccessibleType() override;
#endif #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 // nsIAnonymousContentCreator
nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override; virtual nsresult CreateAnonymousContent(
void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, nsTArray<ContentInfo>& aElements) override;
virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
uint32_t aFilter) override; uint32_t aFilter) override;
#ifdef DEBUG_FRAME_DUMP #ifdef DEBUG_FRAME_DUMP
nsresult GetFrameName(nsAString& aResult) const override { virtual nsresult GetFrameName(nsAString& aResult) const override {
return MakeFrameName(NS_LITERAL_STRING("NumberControl"), aResult); return MakeFrameName(NS_LITERAL_STRING("NumberControl"), aResult);
} }
#endif #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 * If the frame is the frame for an nsNumberControlFrame's anonymous text
* field, returns the nsNumberControlFrame. Else returns nullptr. * field, returns the nsNumberControlFrame. Else returns nullptr.
@@ -94,6 +150,10 @@ class nsNumberControlFrame final : public nsTextControlFrame {
bool SpinnerUpButtonIsDepressed() const; bool SpinnerUpButtonIsDepressed() const;
bool SpinnerDownButtonIsDepressed() const; bool SpinnerDownButtonIsDepressed() const;
bool IsFocused() const;
void HandleFocusEvent(WidgetEvent* aEvent);
/** /**
* Our element had HTMLInputElement::Select() called on it. * Our element had HTMLInputElement::Select() called on it.
*/ */
@@ -102,16 +162,47 @@ class nsNumberControlFrame final : public nsTextControlFrame {
bool ShouldUseNativeStyleForSpinner() const; bool ShouldUseNativeStyleForSpinner() const;
private: private:
nsITextControlFrame* GetTextFieldFrame();
already_AddRefed<Element> MakeAnonymousElement(Element* aParent, already_AddRefed<Element> MakeAnonymousElement(Element* aParent,
nsAtom* aTagName, nsAtom* aTagName,
PseudoStyleType aPseudoType); PseudoStyleType aPseudoType);
// See nsNumberControlFrame::CreateAnonymousContent for a description of class SyncDisabledStateEvent;
// these. 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.
*/
nsCOMPtr<Element> mOuterWrapper; nsCOMPtr<Element> mOuterWrapper;
nsCOMPtr<Element> mTextField;
nsCOMPtr<Element> mSpinBox; nsCOMPtr<Element> mSpinBox;
nsCOMPtr<Element> mSpinUp; nsCOMPtr<Element> mSpinUp;
nsCOMPtr<Element> mSpinDown; nsCOMPtr<Element> mSpinDown;
bool mHandlingInputEvent;
}; };
#endif // nsNumberControlFrame_h__ #endif // nsNumberControlFrame_h__

View File

@@ -108,9 +108,8 @@ class nsTextControlFrame::nsAnonDivObserver final
}; };
nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle, nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle,
nsPresContext* aPresContext, nsPresContext* aPresContext)
nsIFrame::ClassID aClassID) : nsContainerFrame(aStyle, aPresContext, kClassID),
: nsContainerFrame(aStyle, aPresContext, aClassID),
mFirstBaseline(NS_INTRINSIC_ISIZE_UNKNOWN), mFirstBaseline(NS_INTRINSIC_ISIZE_UNKNOWN),
mEditorHasBeenInitialized(false), mEditorHasBeenInitialized(false),
mIsProcessing(false) mIsProcessing(false)
@@ -124,13 +123,6 @@ nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle,
nsTextControlFrame::~nsTextControlFrame() {} nsTextControlFrame::~nsTextControlFrame() {}
nsIScrollableFrame* nsTextControlFrame::GetScrollTargetFrame() {
if (!mRootNode) {
return nullptr;
}
return do_QueryFrame(mRootNode->GetPrimaryFrame());
}
void nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot, void nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) { PostDestroyData& aPostDestroyData) {
mScrollEvent.Revoke(); mScrollEvent.Revoke();
@@ -151,15 +143,8 @@ void nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
mMutationObserver = nullptr; mMutationObserver = nullptr;
} }
// If we're a subclass like nsNumberControlFrame, then it owns the root of the // FIXME(emilio, bug 1400618): Do this after the child frames are destroyed.
// anonymous subtree where mRootNode is.
if (mClass == kClassID) {
aPostDestroyData.AddAnonymousContent(mRootNode.forget()); aPostDestroyData.AddAnonymousContent(mRootNode.forget());
} else {
MOZ_ASSERT(!mRootNode || !mRootNode->IsRootOfAnonymousSubtree());
mRootNode = nullptr;
}
aPostDestroyData.AddAnonymousContent(mPlaceholderDiv.forget()); aPostDestroyData.AddAnonymousContent(mPlaceholderDiv.forget());
aPostDestroyData.AddAnonymousContent(mPreviewDiv.forget()); aPostDestroyData.AddAnonymousContent(mPreviewDiv.forget());
@@ -227,7 +212,9 @@ LogicalSize nsTextControlFrame::CalcIntrinsicSize(
// Add in the size of the scrollbars for textarea // Add in the size of the scrollbars for textarea
if (IsTextArea()) { if (IsTextArea()) {
nsIScrollableFrame* scrollableFrame = GetScrollTargetFrame(); nsIFrame* first = PrincipalChildList().FirstChild();
nsIScrollableFrame* scrollableFrame = do_QueryFrame(first);
NS_ASSERTION(scrollableFrame, "Child must be scrollable"); NS_ASSERTION(scrollableFrame, "Child must be scrollable");
if (scrollableFrame) { if (scrollableFrame) {
@@ -345,6 +332,8 @@ already_AddRefed<Element> nsTextControlFrame::CreateEmptyAnonymousDiv(
RefPtr<Element> divElement = NS_NewHTMLDivElement(nodeInfo.forget()); RefPtr<Element> divElement = NS_NewHTMLDivElement(nodeInfo.forget());
switch (aAnonymousDivType) { switch (aAnonymousDivType) {
case AnonymousDivType::Root: { case AnonymousDivType::Root: {
divElement->SetIsNativeAnonymousRoot();
// Make our root node editable // Make our root node editable
divElement->SetFlags(NODE_IS_EDITABLE); divElement->SetFlags(NODE_IS_EDITABLE);
@@ -421,26 +410,12 @@ nsresult nsTextControlFrame::CreateAnonymousContent(
RefPtr<TextControlElement> textControlElement = RefPtr<TextControlElement> textControlElement =
TextControlElement::FromNode(GetContent()); TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement); MOZ_ASSERT(textControlElement);
mRootNode = CreateEmptyAnonymousDiv(AnonymousDivType::Root); nsresult rv = CreateRootNode();
if (NS_WARN_IF(!mRootNode)) { NS_ENSURE_SUCCESS(rv, rv);
return NS_ERROR_FAILURE;
}
mMutationObserver = new nsAnonDivObserver(*this); // Bind the frame to its text control
mRootNode->AddMutationObserver(mMutationObserver); rv = textControlElement->BindToFrame(this);
NS_ENSURE_SUCCESS(rv, rv);
// 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); aElements.AppendElement(mRootNode);
CreatePlaceholderIfNeeded(); CreatePlaceholderIfNeeded();
@@ -502,6 +477,18 @@ void nsTextControlFrame::InitializeEagerlyIfNeeded() {
nsContentUtils::AddScriptRunner(initializer); 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() { void nsTextControlFrame::CreatePlaceholderIfNeeded() {
MOZ_ASSERT(!mPlaceholderDiv); MOZ_ASSERT(!mPlaceholderDiv);
@@ -602,30 +589,6 @@ LogicalSize nsTextControlFrame::ComputeAutoSize(
return autoSize; 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, void nsTextControlFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize, ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput, const ReflowInput& aReflowInput,
@@ -650,7 +613,22 @@ void nsTextControlFrame::Reflow(nsPresContext* aPresContext,
aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm)); aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm));
aDesiredSize.SetSize(wm, finalSize); aDesiredSize.SetSize(wm, finalSize);
ComputeBaseline(aReflowInput, aDesiredSize); 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.
// overflow handling // overflow handling
aDesiredSize.SetOverflowAreasToDesiredBounds(); aDesiredSize.SetOverflowAreasToDesiredBounds();
@@ -1204,21 +1182,6 @@ bool nsTextControlFrame::GetMaxLength(int32_t* aSize) {
// END IMPLEMENTING NS_IFORMCONTROLFRAME // 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, void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
nsFrameList& aChildList) { nsFrameList& aChildList) {
nsContainerFrame::SetInitialChildList(aListID, aChildList); nsContainerFrame::SetInitialChildList(aListID, aChildList);
@@ -1226,20 +1189,22 @@ void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
return; return;
} }
// Mark the scroll frame as being a reflow root. This will allow incremental // Mark the scroll frame as being a reflow root. This will allow
// reflows to be initiated at the scroll frame, rather than descending from // incremental reflows to be initiated at the scroll frame, rather
// the root frame of the frame hierarchy. // than descending from the root frame of the frame hierarchy.
if (nsIFrame* frame = FindRootNodeFrame(PrincipalChildList(), mRootNode)) { if (nsIFrame* first = PrincipalChildList().FirstChild()) {
frame->AddStateBits(NS_FRAME_REFLOW_ROOT); first->AddStateBits(NS_FRAME_REFLOW_ROOT);
auto* textControlElement = TextControlElement::FromNode(GetContent()); TextControlElement* textControlElement =
TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement); MOZ_ASSERT(textControlElement);
textControlElement->InitializeKeyboardEventListeners(); textControlElement->InitializeKeyboardEventListeners();
if (nsPoint* contentScrollPos = GetProperty(ContentScrollPos())) { nsPoint* contentScrollPos = GetProperty(ContentScrollPos());
if (contentScrollPos) {
// If we have a scroll pos stored to be passed to our anonymous // If we have a scroll pos stored to be passed to our anonymous
// div, do it here! // div, do it here!
nsIStatefulFrame* statefulFrame = do_QueryFrame(frame); nsIStatefulFrame* statefulFrame = do_QueryFrame(first);
NS_ASSERTION(statefulFrame, NS_ASSERTION(statefulFrame,
"unexpected type of frame for the anonymous div"); "unexpected type of frame for the anonymous div");
UniquePtr<PresState> fakePresState = NewPresState(); UniquePtr<PresState> fakePresState = NewPresState();
@@ -1248,13 +1213,12 @@ void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
RemoveProperty(ContentScrollPos()); RemoveProperty(ContentScrollPos());
delete contentScrollPos; delete contentScrollPos;
} }
} else {
MOZ_ASSERT(!mRootNode || PrincipalChildList().IsEmpty());
} }
} }
void nsTextControlFrame::SetValueChanged(bool aValueChanged) { void nsTextControlFrame::SetValueChanged(bool aValueChanged) {
auto* textControlElement = TextControlElement::FromNode(GetContent()); TextControlElement* textControlElement =
TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement); MOZ_ASSERT(textControlElement);
if (mPlaceholderDiv) { if (mPlaceholderDiv) {
@@ -1319,7 +1283,8 @@ nsresult nsTextControlFrame::UpdateValueDisplay(bool aNotify,
} }
if (aBeforeEditorInit && value.IsEmpty()) { if (aBeforeEditorInit && value.IsEmpty()) {
if (nsIContent* node = mRootNode->GetFirstChild()) { nsIContent* node = mRootNode->GetFirstChild();
if (node) {
mRootNode->RemoveChildNode(node, true); mRootNode->RemoveChildNode(node, true);
} }
return NS_OK; return NS_OK;
@@ -1344,16 +1309,21 @@ nsTextControlFrame::GetOwnedSelectionController(
} }
nsFrameSelection* nsTextControlFrame::GetOwnedFrameSelection() { nsFrameSelection* nsTextControlFrame::GetOwnedFrameSelection() {
auto* textControlElement = TextControlElement::FromNode(GetContent()); TextControlElement* textControlElement =
TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement); MOZ_ASSERT(textControlElement);
return textControlElement->GetConstFrameSelection(); return textControlElement->GetConstFrameSelection();
} }
UniquePtr<PresState> nsTextControlFrame::SaveState() { UniquePtr<PresState> nsTextControlFrame::SaveState() {
if (nsIStatefulFrame* scrollStateFrame = if (mRootNode) {
do_QueryFrame(GetScrollTargetFrame())) { // Query the nsIStatefulFrame from the HTMLScrollFrame
nsIStatefulFrame* scrollStateFrame =
do_QueryFrame(mRootNode->GetPrimaryFrame());
if (scrollStateFrame) {
return scrollStateFrame->SaveState(); return scrollStateFrame->SaveState();
} }
}
return nullptr; return nullptr;
} }
@@ -1362,11 +1332,14 @@ NS_IMETHODIMP
nsTextControlFrame::RestoreState(PresState* aState) { nsTextControlFrame::RestoreState(PresState* aState) {
NS_ENSURE_ARG_POINTER(aState); NS_ENSURE_ARG_POINTER(aState);
if (mRootNode) {
// Query the nsIStatefulFrame from the HTMLScrollFrame // Query the nsIStatefulFrame from the HTMLScrollFrame
if (nsIStatefulFrame* scrollStateFrame = nsIStatefulFrame* scrollStateFrame =
do_QueryFrame(GetScrollTargetFrame())) { do_QueryFrame(mRootNode->GetPrimaryFrame());
if (scrollStateFrame) {
return scrollStateFrame->RestoreState(aState); return scrollStateFrame->RestoreState(aState);
} }
}
// Most likely, we don't have our anonymous content constructed yet, which // Most likely, we don't have our anonymous content constructed yet, which
// would cause us to end up here. In this case, we'll just store the scroll // would cause us to end up here. In this case, we'll just store the scroll
@@ -1389,43 +1362,30 @@ void nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
*/ */
DO_GLOBAL_REFLOW_COUNT_DSP("nsTextControlFrame"); DO_GLOBAL_REFLOW_COUNT_DSP("nsTextControlFrame");
auto* control = TextControlElement::FromNode(GetContent()); TextControlElement* textControlElement =
MOZ_ASSERT(control); TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement);
DisplayBorderBackgroundOutline(aBuilder, aLists); DisplayBorderBackgroundOutline(aBuilder, aLists);
nsIFrame* kid = mFrames.FirstChild();
// Redirect all lists to the Content list so that nothing can escape, ie // Redirect all lists to the Content list so that nothing can escape, ie
// opacity creating stacking contexts that then get sorted with stacking // opacity creating stacking contexts that then get sorted with stacking
// contexts external to us. // contexts external to us.
nsDisplayList* content = aLists.Content(); nsDisplayList* content = aLists.Content();
nsDisplayListSet set(content, content, content, content, content, content); nsDisplayListSet set(content, content, content, content, content, content);
for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { while (kid) {
nsIContent* kidContent = kid->GetContent(); // If the frame is the placeholder or preview frame, we should only show
Maybe<DisplayListClipState::AutoSaveRestore> overlayTextClip; // it if it has to be visible.
if (kidContent == mPlaceholderDiv && !control->GetPlaceholderVisibility()) { if (!((kid->GetContent() == mPlaceholderDiv &&
continue; !textControlElement->GetPlaceholderVisibility()) ||
} (kid->GetContent() == mPreviewDiv &&
if (kidContent == mPreviewDiv && !control->GetPreviewVisibility()) { !textControlElement->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); BuildDisplayListForChild(aBuilder, kid, set, 0);
} }
kid = kid->GetNextSibling();
}
} }
NS_IMETHODIMP NS_IMETHODIMP

View File

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

View File

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

View File

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

View File

@@ -67,6 +67,9 @@ CSS_PSEUDO_ELEMENT(mozMathAnonymous, ":-moz-math-anonymous",
CSS_PSEUDO_ELEMENT(mozNumberWrapper, ":-moz-number-wrapper", CSS_PSEUDO_ELEMENT(mozNumberWrapper, ":-moz-number-wrapper",
CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE | CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME) 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(mozNumberSpinBox, ":-moz-number-spin-box",
CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE | CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME) CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)

View File

@@ -102,7 +102,7 @@ input {
overflow-clip-box: padding-box content-box; overflow-clip-box: padding-box content-box;
} }
input .anonymous-div, input > .anonymous-div,
input::placeholder { input::placeholder {
word-wrap: normal; word-wrap: normal;
/* Make the line-height equal to the available height */ /* Make the line-height equal to the available height */
@@ -144,12 +144,12 @@ textarea > scrollbar {
cursor: default; cursor: default;
} }
textarea .anonymous-div, textarea > .anonymous-div,
input .anonymous-div, input > .anonymous-div,
input::placeholder, input::placeholder,
textarea::placeholder, textarea::placeholder,
input .preview-div input > .preview-div
textarea .preview-div { textarea > .preview-div {
overflow: auto; overflow: auto;
border: 0px; border: 0px;
padding: inherit; padding: inherit;
@@ -163,14 +163,14 @@ textarea .preview-div {
overflow-clip-box: inherit; overflow-clip-box: inherit;
} }
input .anonymous-div, input > .anonymous-div,
input::placeholder, input::placeholder,
input .preview-div { input > .preview-div {
white-space: pre; white-space: pre;
} }
input[type=password] .anonymous-div, input[type=password] > .anonymous-div,
input[type=password] .preview-div { input[type=password] > .preview-div {
/* /*
* In password fields, any character should be put same direction. Otherwise, * In password fields, any character should be put same direction. Otherwise,
* caret position at typing tells everybody whether the character is an RTL * caret position at typing tells everybody whether the character is an RTL
@@ -191,8 +191,8 @@ textarea > .anonymous-div.inherit-overflow {
input::placeholder, input::placeholder,
textarea::placeholder, textarea::placeholder,
input .preview-div, input > .preview-div,
textarea .preview-div { textarea > .preview-div {
/* /*
* Changing display to inline can leads to broken behaviour and will assert. * Changing display to inline can leads to broken behaviour and will assert.
*/ */
@@ -218,7 +218,7 @@ textarea::placeholder {
} }
textarea::placeholder, textarea::placeholder,
textarea .preview-div { textarea > .preview-div {
white-space: pre-wrap !important; white-space: pre-wrap !important;
} }
@@ -993,12 +993,27 @@ input[type=number]::-moz-number-wrapper {
block-size: 100%; block-size: 100%;
} }
input[type=number] .anonymous-div { input[type=number]::-moz-number-text {
display: block; /* Flex items must be block-level. Normally we do fixup in display: block; /* Flex items must be block-level. Normally we do fixup in
the style system to ensure this, but that fixup is disabled the style system to ensure this, but that fixup is disabled
inside of form controls. So, we hardcode display here. */ 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; flex: 1;
min-inline-size: 0; min-inline-size: 0;
padding: unset;
border: 0;
margin: 0;
} }
input[type=number]::-moz-number-spin-box { input[type=number]::-moz-number-spin-box {

View File

@@ -184,9 +184,15 @@ class AutofillDelegateTest : BaseSessionTest() {
// Wait on the promises and check for correct values. // Wait on the promises and check for correct values.
for ((key, actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) { for ((key, actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
assertThat("Auto-filled value must match ($key)", actual, equalTo(expected)) 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"))
} }
} }
}
private fun countAutofillNodes(cond: (Autofill.Node) -> Boolean = private fun countAutofillNodes(cond: (Autofill.Node) -> Boolean =
{ it.inputType != Autofill.InputType.NONE }, { it.inputType != Autofill.InputType.NONE },

View File

@@ -1869,7 +1869,22 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
#[inline] #[inline]
fn pseudo_element_originating_element(&self) -> Option<Self> { fn pseudo_element_originating_element(&self) -> Option<Self> {
debug_assert!(self.is_pseudo_element()); debug_assert!(self.is_pseudo_element());
self.closest_anon_subtree_root_parent() 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)
} }
#[inline] #[inline]

View File

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

View File

@@ -1,3 +1,7 @@
[form-validation-validity-rangeUnderflow.html] [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] [[INPUT in TIME status\] The time is max for reversed range]
expected: FAIL expected: FAIL

View File

@@ -0,0 +1,4 @@
[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

View File

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

View File

@@ -1,26 +0,0 @@
<!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>

View File

@@ -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-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"/> <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"> <hbox align="center" pack="center">
<html:input id="print-preview-pageNumber" hidespinbuttons="true" type="number" value="1" min="1"/> <html:input id="print-preview-pageNumber" class="input-number-mozbox" hidespinbuttons="true" type="number" value="1" min="1"/>
<label data-l10n-id="printpreview-of"/> <label data-l10n-id="printpreview-of"/>
<label id="print-preview-totalPages" value="1"/> <label id="print-preview-totalPages" value="1"/>
</hbox> </hbox>

View File

@@ -15,3 +15,20 @@ input[type="number"] {
input[type="number"][hidespinbuttons="true"] { input[type="number"][hidespinbuttons="true"] {
-moz-appearance: textfield !important; -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%;
}

View File

@@ -53,6 +53,16 @@ EventStates nsNativeTheme::GetContentState(nsIFrame* aFrame,
if (frameContent->IsElement()) { if (frameContent->IsElement()) {
flags = frameContent->AsElement()->State(); 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* numberControlFrame =
nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame); nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
if (numberControlFrame && if (numberControlFrame &&

View File

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