зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
dddb83ab28
Коммит
0d01c60c37
|
@ -466,7 +466,7 @@ Accessible* HTMLFileInputAccessible::CurrentItem() const {
|
|||
role HTMLSpinnerAccessible::NativeRole() const { return roles::SPINBUTTON; }
|
||||
|
||||
void HTMLSpinnerAccessible::Value(nsString& aValue) const {
|
||||
HTMLTextFieldAccessible::Value(aValue);
|
||||
AccessibleWrap::Value(aValue);
|
||||
if (!aValue.IsEmpty()) return;
|
||||
|
||||
// Pass NonSystem as the caller type, to be safe. We don't expect to have a
|
||||
|
@ -475,28 +475,28 @@ void HTMLSpinnerAccessible::Value(nsString& aValue) const {
|
|||
}
|
||||
|
||||
double HTMLSpinnerAccessible::MaxValue() const {
|
||||
double value = HTMLTextFieldAccessible::MaxValue();
|
||||
double value = AccessibleWrap::MaxValue();
|
||||
if (!IsNaN(value)) return value;
|
||||
|
||||
return HTMLInputElement::FromNode(mContent)->GetMaximum().toDouble();
|
||||
}
|
||||
|
||||
double HTMLSpinnerAccessible::MinValue() const {
|
||||
double value = HTMLTextFieldAccessible::MinValue();
|
||||
double value = AccessibleWrap::MinValue();
|
||||
if (!IsNaN(value)) return value;
|
||||
|
||||
return HTMLInputElement::FromNode(mContent)->GetMinimum().toDouble();
|
||||
}
|
||||
|
||||
double HTMLSpinnerAccessible::Step() const {
|
||||
double value = HTMLTextFieldAccessible::Step();
|
||||
double value = AccessibleWrap::Step();
|
||||
if (!IsNaN(value)) return value;
|
||||
|
||||
return HTMLInputElement::FromNode(mContent)->GetStep().toDouble();
|
||||
}
|
||||
|
||||
double HTMLSpinnerAccessible::CurValue() const {
|
||||
double value = HTMLTextFieldAccessible::CurValue();
|
||||
double value = AccessibleWrap::CurValue();
|
||||
if (!IsNaN(value)) return value;
|
||||
|
||||
return HTMLInputElement::FromNode(mContent)->GetValueAsDecimal().toDouble();
|
||||
|
|
|
@ -63,7 +63,7 @@ class HTMLButtonAccessible : public HyperTextAccessibleWrap {
|
|||
* Accessible for HTML input@type="text", input@type="password", textarea and
|
||||
* other HTML text controls.
|
||||
*/
|
||||
class HTMLTextFieldAccessible : public HyperTextAccessibleWrap {
|
||||
class HTMLTextFieldAccessible final : public HyperTextAccessibleWrap {
|
||||
public:
|
||||
enum { eAction_Click = 0 };
|
||||
|
||||
|
@ -99,7 +99,8 @@ class HTMLTextFieldAccessible : public HyperTextAccessibleWrap {
|
|||
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.
|
||||
*/
|
||||
|
@ -128,10 +129,10 @@ class HTMLFileInputAccessible : public HyperTextAccessibleWrap {
|
|||
/**
|
||||
* Used for HTML input@type="number".
|
||||
*/
|
||||
class HTMLSpinnerAccessible final : public HTMLTextFieldAccessible {
|
||||
class HTMLSpinnerAccessible : public AccessibleWrap {
|
||||
public:
|
||||
HTMLSpinnerAccessible(nsIContent* aContent, DocAccessible* aDoc)
|
||||
: HTMLTextFieldAccessible(aContent, aDoc) {
|
||||
: AccessibleWrap(aContent, aDoc) {
|
||||
mStateFlags |= eHasNumericValue;
|
||||
}
|
||||
|
||||
|
|
|
@ -124,7 +124,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=558036
|
|||
testAttrs("search", {"text-input-type": "search"}, true);
|
||||
testAttrs("tel", {"text-input-type": "tel"}, true);
|
||||
testAttrs("url", {"text-input-type": "url"}, true);
|
||||
testAttrs("number", {"text-input-type": "number"}, true);
|
||||
testAttrs(getAccessible("number").firstChild, {"text-input-type": "number"}, true);
|
||||
|
||||
// ARIA
|
||||
testAttrs("searchbox", {"text-input-type": "search"}, true);
|
||||
|
|
|
@ -712,7 +712,15 @@
|
|||
role: ROLE_SPINBUTTON,
|
||||
interfaces: [ nsIAccessibleValue ],
|
||||
children: [
|
||||
{ role: ROLE_TEXT_LEAF },
|
||||
{
|
||||
role: ROLE_ENTRY,
|
||||
extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE,
|
||||
actions: "activate",
|
||||
interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ],
|
||||
children: [
|
||||
{ role: ROLE_TEXT_LEAF },
|
||||
],
|
||||
},
|
||||
{
|
||||
role: ROLE_PUSHBUTTON,
|
||||
actions: "press",
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
// input@type="number"
|
||||
accTree =
|
||||
{ SPINBUTTON: [
|
||||
{ ENTRY: [ ] },
|
||||
{ PUSHBUTTON: [ ] },
|
||||
{ PUSHBUTTON: [ ] },
|
||||
] };
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
<html:input id="networkProxyHTTP" type="text" style="-moz-box-flex: 1;"
|
||||
preference="network.proxy.http"/>
|
||||
<label data-l10n-id="connection-proxy-http-port" control="networkProxyHTTP_Port" />
|
||||
<html:input id="networkProxyHTTP_Port" class="proxy-port-input" 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"/>
|
||||
</hbox>
|
||||
<hbox/>
|
||||
|
@ -73,7 +73,7 @@
|
|||
<hbox align="center">
|
||||
<html:input id="networkProxySSL" type="text" style="-moz-box-flex: 1;" preference="network.proxy.ssl"/>
|
||||
<label data-l10n-id="connection-proxy-ssl-port" control="networkProxySSL_Port" />
|
||||
<html:input id="networkProxySSL_Port" class="proxy-port-input" 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"/>
|
||||
</hbox>
|
||||
<hbox pack="end">
|
||||
|
@ -82,7 +82,7 @@
|
|||
<hbox align="center">
|
||||
<html:input id="networkProxyFTP" type="text" style="-moz-box-flex: 1;" preference="network.proxy.ftp"/>
|
||||
<label data-l10n-id="connection-proxy-ftp-port" control="networkProxyFTP_Port"/>
|
||||
<html:input id="networkProxyFTP_Port" class="proxy-port-input" 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"/>
|
||||
</hbox>
|
||||
<separator class="thin"/>
|
||||
|
@ -92,7 +92,7 @@
|
|||
<hbox align="center">
|
||||
<html:input id="networkProxySOCKS" type="text" style="-moz-box-flex: 1;" preference="network.proxy.socks"/>
|
||||
<label data-l10n-id="connection-proxy-socks-port" control="networkProxySOCKS_Port"/>
|
||||
<html:input id="networkProxySOCKS_Port" class="proxy-port-input" 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"/>
|
||||
</hbox>
|
||||
<spacer/>
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
#include "nsITextControlFrame.h"
|
||||
#include "nsCommandManager.h"
|
||||
#include "nsCommandParams.h"
|
||||
#include "nsNumberControlFrame.h"
|
||||
#include "nsUnicharUtils.h"
|
||||
#include "nsContentList.h"
|
||||
#include "nsCSSPseudoElements.h"
|
||||
|
@ -12343,7 +12344,9 @@ already_AddRefed<nsDOMCaretPosition> Document::CaretPositionFromPoint(
|
|||
nsIContent* nonanon = node->FindFirstNonChromeOnlyAccessContent();
|
||||
HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(nonanon);
|
||||
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
|
||||
// that we get the appropriate child, as otherwise the offset may not be
|
||||
// correct when we construct a range for it.
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "BrowserChild.h"
|
||||
#include "nsFrameLoader.h"
|
||||
#include "nsHTMLDocument.h"
|
||||
#include "nsNumberControlFrame.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsRange.h"
|
||||
|
||||
|
@ -315,6 +316,24 @@ Element* nsFocusManager::GetFocusedDescendant(
|
|||
|
||||
// static
|
||||
Element* nsFocusManager::GetRedirectedFocus(nsIContent* aContent) {
|
||||
// For input number, redirect focus to our anonymous text control.
|
||||
if (aContent->IsHTMLElement(nsGkAtoms::input)) {
|
||||
bool typeIsNumber =
|
||||
static_cast<dom::HTMLInputElement*>(aContent)->ControlType() ==
|
||||
NS_FORM_INPUT_NUMBER;
|
||||
|
||||
if (typeIsNumber) {
|
||||
nsNumberControlFrame* numberControlFrame =
|
||||
do_QueryFrame(aContent->GetPrimaryFrame());
|
||||
|
||||
if (numberControlFrame) {
|
||||
HTMLInputElement* textControl =
|
||||
numberControlFrame->GetAnonTextControl();
|
||||
return textControl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MOZ_XUL
|
||||
if (aContent->IsXULElement()) {
|
||||
nsCOMPtr<nsIDOMXULMenuListElement> menulist =
|
||||
|
@ -1460,7 +1479,8 @@ Element* nsFocusManager::FlushAndCheckIfFocusable(Element* aElement,
|
|||
|
||||
// this is a special case for some XUL elements or input number, where an
|
||||
// anonymous child is actually focusable and not the element itself.
|
||||
if (RefPtr<Element> redirectedFocus = GetRedirectedFocus(aElement)) {
|
||||
RefPtr<Element> redirectedFocus = GetRedirectedFocus(aElement);
|
||||
if (redirectedFocus) {
|
||||
return FlushAndCheckIfFocusable(redirectedFocus, aFlags);
|
||||
}
|
||||
|
||||
|
|
|
@ -1580,7 +1580,7 @@ void EventStateManager::FireContextClick() {
|
|||
|
||||
if (formCtrl) {
|
||||
allowedToDispatch =
|
||||
formCtrl->IsTextControl(/*aExcludePassword*/ false) ||
|
||||
formCtrl->IsTextOrNumberControl(/*aExcludePassword*/ false) ||
|
||||
formCtrl->ControlType() == NS_FORM_INPUT_FILE;
|
||||
} else if (mGestureDownContent->IsAnyOfHTMLElements(
|
||||
nsGkAtoms::embed, nsGkAtoms::object, nsGkAtoms::label)) {
|
||||
|
|
|
@ -1122,7 +1122,7 @@ void IMEStateManager::SetInputContextForChildProcess(
|
|||
SetInputContext(widget, aInputContext, aAction);
|
||||
}
|
||||
|
||||
static bool IsNextFocusableElementTextControl(Element* aInputContent) {
|
||||
static bool IsNextFocusableElementTextOrNumberControl(Element* aInputContent) {
|
||||
nsFocusManager* fm = nsFocusManager::GetFocusManager();
|
||||
if (!fm) {
|
||||
return false;
|
||||
|
@ -1136,7 +1136,7 @@ static bool IsNextFocusableElementTextControl(Element* aInputContent) {
|
|||
}
|
||||
nextContent = nextContent->FindFirstNonChromeOnlyAccessContent();
|
||||
nsCOMPtr<nsIFormControl> nextControl = do_QueryInterface(nextContent);
|
||||
if (!nextControl || !nextControl->IsTextControl(false)) {
|
||||
if (!nextControl || !nextControl->IsTextOrNumberControl(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1217,7 +1217,8 @@ static void GetActionHint(nsIContent& aContent, nsAString& aActionHint) {
|
|||
if (!isLastElement && formElement) {
|
||||
// If next tabbable content in form is text control, hint should be "next"
|
||||
// even there is submit in form.
|
||||
if (IsNextFocusableElementTextControl(inputContent->AsElement())) {
|
||||
if (IsNextFocusableElementTextOrNumberControl(
|
||||
inputContent->AsElement())) {
|
||||
// This is focusable text control
|
||||
// XXX What good hint for read only field?
|
||||
aActionHint.AssignLiteral("next");
|
||||
|
@ -1273,8 +1274,22 @@ void IMEStateManager::SetIMEState(const IMEState& aState,
|
|||
if (aContent &&
|
||||
aContent->IsAnyOfHTMLElements(nsGkAtoms::input, nsGkAtoms::textarea)) {
|
||||
if (!aContent->IsHTMLElement(nsGkAtoms::textarea)) {
|
||||
aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::type,
|
||||
context.mHTMLInputType);
|
||||
// <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);
|
||||
} else {
|
||||
context.mHTMLInputType.Assign(nsGkAtoms::textarea->GetUTF16String());
|
||||
}
|
||||
|
|
|
@ -1754,7 +1754,7 @@ bool HTMLFormElement::ImplicitSubmissionIsDisabled() const {
|
|||
uint32_t numDisablingControlsFound = 0;
|
||||
uint32_t length = mControls->mElements.Length();
|
||||
for (uint32_t i = 0; i < length && numDisablingControlsFound < 2; ++i) {
|
||||
if (mControls->mElements[i]->IsSingleLineTextControl(false)) {
|
||||
if (mControls->mElements[i]->IsSingleLineTextOrNumberControl(false)) {
|
||||
numDisablingControlsFound++;
|
||||
}
|
||||
}
|
||||
|
@ -1767,7 +1767,7 @@ bool HTMLFormElement::IsLastActiveElement(
|
|||
|
||||
for (auto* element : Reversed(mControls->mElements)) {
|
||||
// XXX How about date/time control?
|
||||
if (element->IsTextControl(false) && !element->IsDisabled()) {
|
||||
if (element->IsTextOrNumberControl(false) && !element->IsDisabled()) {
|
||||
return element == aControl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,11 +128,12 @@ namespace dom {
|
|||
// (1 << 11 is unused)
|
||||
#define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
|
||||
#define NS_PRE_HANDLE_BLUR_EVENT (1 << 13)
|
||||
#define NS_PRE_HANDLE_INPUT_EVENT (1 << 14)
|
||||
#define NS_IN_SUBMIT_CLICK (1 << 15)
|
||||
#define NS_CONTROL_TYPE(bits) \
|
||||
((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | \
|
||||
NS_ORIGINAL_INDETERMINATE_VALUE | NS_PRE_HANDLE_BLUR_EVENT | \
|
||||
NS_IN_SUBMIT_CLICK))
|
||||
NS_PRE_HANDLE_INPUT_EVENT | NS_IN_SUBMIT_CLICK))
|
||||
|
||||
// whether textfields should be selected once focused:
|
||||
// -1: no, 1: yes, 0: uninitialized
|
||||
|
@ -1300,11 +1301,15 @@ nsresult HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
|
|||
aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
|
||||
SetDirectionFromValue(aNotify);
|
||||
} else if (aName == nsGkAtoms::lang) {
|
||||
// FIXME(emilio, bug 1605158): This doesn't account for lang changes on
|
||||
// ancestors.
|
||||
if (mType == NS_FORM_INPUT_NUMBER) {
|
||||
// The validity of our value may have changed based on the locale.
|
||||
UpdateValidityState();
|
||||
// Update the value that is displayed to the user to the new locale:
|
||||
nsAutoString value;
|
||||
GetNonFileValueInternal(value);
|
||||
nsNumberControlFrame* numberControlFrame =
|
||||
do_QueryFrame(GetPrimaryFrame());
|
||||
if (numberControlFrame) {
|
||||
numberControlFrame->SetValueOfAnonTextControl(value);
|
||||
}
|
||||
}
|
||||
} else if (aName == nsGkAtoms::autocomplete) {
|
||||
// Clear the cached @autocomplete attribute and autocompleteInfo state.
|
||||
|
@ -1431,17 +1436,12 @@ uint32_t HTMLInputElement::Width() {
|
|||
return GetWidthHeightForImage(currentRequest).width;
|
||||
}
|
||||
|
||||
bool HTMLInputElement::SanitizesOnValueGetter() const {
|
||||
// Don't return non-sanitized value for types that are experimental on mobile
|
||||
// or datetime types or number.
|
||||
return mType == NS_FORM_INPUT_NUMBER || IsExperimentalMobileType(mType) ||
|
||||
IsDateTimeInputType(mType);
|
||||
}
|
||||
|
||||
void HTMLInputElement::GetValue(nsAString& aValue, CallerType aCallerType) {
|
||||
GetValueInternal(aValue, aCallerType);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1583,11 +1583,11 @@ void HTMLInputElement::SetValue(const nsAString& aValue, CallerType aCallerType,
|
|||
|
||||
// Some types sanitize value, so GetValue doesn't return pure
|
||||
// previous value correctly.
|
||||
//
|
||||
// FIXME(emilio): Shouldn't above just use GetNonFileValueInternal() to
|
||||
// get the unsanitized value?
|
||||
nsresult rv = SetValueInternal(
|
||||
aValue, SanitizesOnValueGetter() ? nullptr : ¤tValue,
|
||||
aValue,
|
||||
(IsExperimentalMobileType(mType) || IsDateTimeInputType(mType))
|
||||
? nullptr
|
||||
: ¤tValue,
|
||||
TextControlState::eSetValue_ByContent |
|
||||
TextControlState::eSetValue_Notify |
|
||||
TextControlState::eSetValue_MoveCursorToEndIfValueChanged);
|
||||
|
@ -2163,17 +2163,25 @@ void HTMLInputElement::UpdateValidityState() {
|
|||
|
||||
bool HTMLInputElement::MozIsTextField(bool aExcludePassword) {
|
||||
// TODO: temporary until bug 888320 is fixed.
|
||||
//
|
||||
// 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) {
|
||||
if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsSingleLineTextControl(aExcludePassword);
|
||||
}
|
||||
|
||||
HTMLInputElement* HTMLInputElement::GetOwnerNumberControl() {
|
||||
if (IsInNativeAnonymousSubtree() && mType == NS_FORM_INPUT_TEXT &&
|
||||
GetParent() && GetParent()->GetParent()) {
|
||||
HTMLInputElement* grandparent =
|
||||
HTMLInputElement::FromNodeOrNull(GetParent()->GetParent());
|
||||
if (grandparent && grandparent->mType == NS_FORM_INPUT_NUMBER) {
|
||||
return grandparent;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void HTMLInputElement::SetUserInput(const nsAString& aValue,
|
||||
nsIPrincipal& aSubjectPrincipal) {
|
||||
if (mType == NS_FORM_INPUT_FILE && !aSubjectPrincipal.IsSystemPrincipal()) {
|
||||
|
@ -2638,7 +2646,15 @@ nsresult HTMLInputElement::SetValueInternal(const nsAString& aValue,
|
|||
if (setValueChanged) {
|
||||
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());
|
||||
if (frame) {
|
||||
frame->UpdateForValueChange();
|
||||
|
@ -2917,6 +2933,19 @@ void HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify) {
|
|||
}
|
||||
|
||||
void HTMLInputElement::Blur(ErrorResult& aError) {
|
||||
if (mType == NS_FORM_INPUT_NUMBER) {
|
||||
// Blur our anonymous text control, if we have one. (DOM 'change' event
|
||||
// firing and other things depend on this.)
|
||||
nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
|
||||
if (numberControlFrame) {
|
||||
HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl();
|
||||
if (textControl) {
|
||||
textControl->Blur(aError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
|
||||
!IsExperimentalMobileType(mType)) {
|
||||
if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
|
||||
|
@ -2933,6 +2962,19 @@ void HTMLInputElement::Blur(ErrorResult& aError) {
|
|||
|
||||
void HTMLInputElement::Focus(const FocusOptions& aOptions,
|
||||
ErrorResult& aError) {
|
||||
if (mType == NS_FORM_INPUT_NUMBER) {
|
||||
// Focus our anonymous text control, if we have one.
|
||||
nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
|
||||
if (numberControlFrame) {
|
||||
RefPtr<HTMLInputElement> textControl =
|
||||
numberControlFrame->GetAnonTextControl();
|
||||
if (textControl) {
|
||||
textControl->Focus(aOptions, aError);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((mType == NS_FORM_INPUT_TIME || mType == NS_FORM_INPUT_DATE) &&
|
||||
!IsExperimentalMobileType(mType)) {
|
||||
if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
|
||||
|
@ -2967,6 +3009,14 @@ void HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
|
|||
}
|
||||
|
||||
void HTMLInputElement::Select() {
|
||||
if (mType == NS_FORM_INPUT_NUMBER) {
|
||||
nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
|
||||
if (numberControlFrame) {
|
||||
numberControlFrame->HandleSelectCall();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsSingleLineTextControl(false)) {
|
||||
return;
|
||||
}
|
||||
|
@ -3256,10 +3306,65 @@ void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
|
|||
StopNumberControlSpinnerSpin();
|
||||
}
|
||||
}
|
||||
if (aVisitor.mEvent->mMessage == eFocus ||
|
||||
aVisitor.mEvent->mMessage == eBlur) {
|
||||
if (aVisitor.mEvent->mMessage == eFocus) {
|
||||
// Tell our frame it's getting focus so that it can make sure focus
|
||||
// is moved to our anonymous text control.
|
||||
nsNumberControlFrame* numberControlFrame =
|
||||
do_QueryFrame(GetPrimaryFrame());
|
||||
if (numberControlFrame) {
|
||||
// This could kill the frame!
|
||||
numberControlFrame->HandleFocusEvent(aVisitor.mEvent);
|
||||
}
|
||||
}
|
||||
nsIFrame* frame = GetPrimaryFrame();
|
||||
if (frame && frame->IsThemed()) {
|
||||
// Our frame's nested <input type=text> will be invalidated when it
|
||||
// loses focus, but since we are also native themed we need to make
|
||||
// sure that our entire area is repainted since any focus highlight
|
||||
// from the theme should be removed from us (the repainting of the
|
||||
// sub-area occupied by the anon text control is not enough to do
|
||||
// that).
|
||||
frame->InvalidateFrame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsGenericHTMLFormElementWithState::GetEventTargetParent(aVisitor);
|
||||
|
||||
// We do this after calling the base class' GetEventTargetParent so that
|
||||
// nsIContent::GetEventTargetParent doesn't reset any change we make to
|
||||
// mCanHandle.
|
||||
if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted() &&
|
||||
aVisitor.mEvent->mOriginalTarget != this) {
|
||||
// <input type=number> has an anonymous <input type=text> descendant. If
|
||||
// 'input' or 'change' events are fired at that text control then we need
|
||||
// to do some special handling here.
|
||||
HTMLInputElement* textControl = nullptr;
|
||||
nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
|
||||
if (numberControlFrame) {
|
||||
textControl = numberControlFrame->GetAnonTextControl();
|
||||
}
|
||||
if (textControl && aVisitor.mEvent->mOriginalTarget == textControl) {
|
||||
if (aVisitor.mEvent->mMessage == eEditorInput) {
|
||||
aVisitor.mWantsPreHandleEvent = true;
|
||||
// We set NS_PRE_HANDLE_INPUT_EVENT here and handle it in PreHandleEvent
|
||||
// to prevent breaking event target chain creation.
|
||||
aVisitor.mItemFlags |= NS_PRE_HANDLE_INPUT_EVENT;
|
||||
} else if (aVisitor.mEvent->mMessage == eFormChange) {
|
||||
// We cancel the DOM 'change' event that is fired for any change to our
|
||||
// anonymous text control since we fire our own 'change' events and
|
||||
// content shouldn't be seeing two 'change' events. Besides that we
|
||||
// (as a number) control have tighter restrictions on when our internal
|
||||
// value changes than our anon text control does, so in some cases
|
||||
// (if our text control's value doesn't parse as a number) we don't
|
||||
// want to fire a 'change' event at all.
|
||||
aVisitor.mCanHandle = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the event if the related target's first non-native ancestor is the
|
||||
// same as the original target's first non-native ancestor (we are moving
|
||||
// inside of the same element).
|
||||
|
@ -3302,7 +3407,26 @@ nsresult HTMLInputElement::PreHandleEvent(EventChainVisitor& aVisitor) {
|
|||
}
|
||||
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) {
|
||||
|
@ -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
|
||||
// the user. (IsValid() can return false if the 'required' attribute is
|
||||
// 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
|
||||
// regardless because we need the UI to update _now_ or the user will
|
||||
// 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.)
|
||||
SetValueInternal(newVal, TextControlState::eSetValue_BySetUserInput |
|
||||
TextControlState::eSetValue_Notify);
|
||||
|
||||
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||
"Failed to dispatch input event");
|
||||
}
|
||||
|
||||
static bool SelectTextFieldOnFocus() {
|
||||
|
@ -3964,7 +4093,9 @@ nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
|
|||
wheelEvent->mDeltaY != 0 &&
|
||||
wheelEvent->mDeltaMode != WheelEvent_Binding::DOM_DELTA_PIXEL) {
|
||||
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);
|
||||
FireChangeEventIfNeeded();
|
||||
aVisitor.mEvent->PreventDefault();
|
||||
|
@ -5890,7 +6021,50 @@ EventStates HTMLInputElement::IntrinsicState() const {
|
|||
|
||||
bool HTMLInputElement::ShouldShowPlaceholder() const {
|
||||
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(
|
||||
|
@ -6241,7 +6415,8 @@ bool HTMLInputElement::PlaceholderApplies() const {
|
|||
if (IsDateTimeInputType(mType)) {
|
||||
return false;
|
||||
}
|
||||
return IsSingleLineTextControl(false);
|
||||
|
||||
return IsSingleLineTextOrNumberControl(false);
|
||||
}
|
||||
|
||||
bool HTMLInputElement::DoesMinMaxApply() const {
|
||||
|
|
|
@ -206,6 +206,11 @@ class HTMLInputElement final : public TextControlElement,
|
|||
|
||||
virtual EventStates IntrinsicState() const override;
|
||||
|
||||
// Element
|
||||
private:
|
||||
virtual void AddStates(EventStates aStates) override;
|
||||
virtual void RemoveStates(EventStates aStates) override;
|
||||
|
||||
public:
|
||||
// TextControlElement
|
||||
virtual nsresult SetValueChanged(bool aValueChanged) override;
|
||||
|
@ -803,6 +808,8 @@ class HTMLInputElement final : public TextControlElement,
|
|||
double GetMinimumAsDouble() { return GetMinimum().toDouble(); }
|
||||
double GetMaximumAsDouble() { return GetMaximum().toDouble(); }
|
||||
|
||||
HTMLInputElement* GetOwnerNumberControl();
|
||||
|
||||
void StartNumberControlSpinnerSpin();
|
||||
enum SpinnerStopState { eAllowDispatchingEvents, eDisallowDispatchingEvents };
|
||||
void StopNumberControlSpinnerSpin(
|
||||
|
@ -1387,13 +1394,6 @@ class HTMLInputElement final : public TextControlElement,
|
|||
*/
|
||||
static bool IsDateTimeInputType(uint8_t aType);
|
||||
|
||||
/**
|
||||
* Returns whether getting `.value` as a string should sanitize the value.
|
||||
*
|
||||
* See SanitizeValue.
|
||||
*/
|
||||
bool SanitizesOnValueGetter() const;
|
||||
|
||||
/**
|
||||
* Returns true if the element should prevent dispatching another DOMActivate.
|
||||
* This is used in situations where the anonymous subtree should already have
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "mozilla/dom/HTMLTextAreaElement.h"
|
||||
#include "mozilla/dom/Text.h"
|
||||
#include "mozilla/StaticPrefs_dom.h"
|
||||
#include "nsNumberControlFrame.h"
|
||||
#include "nsFrameSelection.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
@ -3089,7 +3090,8 @@ void TextControlState::InitializeKeyboardEventListeners() {
|
|||
TrustedEventsAtSystemGroupBubble());
|
||||
}
|
||||
|
||||
mSelCon->SetScrollableFrame(mBoundFrame->GetScrollTargetFrame());
|
||||
mSelCon->SetScrollableFrame(
|
||||
do_QueryFrame(mBoundFrame->PrincipalChildList().FirstChild()));
|
||||
}
|
||||
|
||||
void TextControlState::ValueWasChanged(bool aNotify) {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include "mozilla/TextControlState.h"
|
||||
#include "mozilla/dom/HTMLInputElement.h"
|
||||
#include "ICUUtils.h"
|
||||
#include "nsNumberControlFrame.h"
|
||||
|
||||
bool NumericInputTypeBase::IsRangeOverflow() const {
|
||||
mozilla::Decimal maximum = mInputElement->GetMaximum();
|
||||
|
@ -94,15 +94,11 @@ nsresult NumericInputTypeBase::GetRangeUnderflowMessage(nsAString& aMessage) {
|
|||
|
||||
bool NumericInputTypeBase::ConvertStringToNumber(
|
||||
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));
|
||||
aResultValue = mozilla::dom::HTMLInputElement::StringToDecimal(aValue);
|
||||
if (!aResultValue.isFinite()) {
|
||||
aResultValue = mozilla::dom::HTMLInputElement::StringToDecimal(aValue);
|
||||
return false;
|
||||
}
|
||||
return aResultValue.isFinite();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NumericInputTypeBase::ConvertNumberToString(
|
||||
|
@ -136,7 +132,19 @@ bool NumberInputType::IsValueMissing() const {
|
|||
bool NumberInputType::HasBadInput() const {
|
||||
nsAutoString 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) {
|
||||
|
|
|
@ -185,6 +185,13 @@ class nsIFormControl : public nsISupports {
|
|||
*/
|
||||
inline bool IsTextControl(bool aExcludePassword) const;
|
||||
|
||||
/**
|
||||
* Returns true if this is a text control or a number control.
|
||||
* @param aExcludePassword to have NS_FORM_INPUT_PASSWORD returning false.
|
||||
* @return true if this is a text control or a number control.
|
||||
*/
|
||||
inline bool IsTextOrNumberControl(bool aExcludePassword) const;
|
||||
|
||||
/**
|
||||
* Returns whether this is a single line text control.
|
||||
* @param aExcludePassword to have NS_FORM_INPUT_PASSWORD returning false.
|
||||
|
@ -192,6 +199,13 @@ class nsIFormControl : public nsISupports {
|
|||
*/
|
||||
inline bool IsSingleLineTextControl(bool aExcludePassword) const;
|
||||
|
||||
/**
|
||||
* Returns true if this is a single line text control or a number control.
|
||||
* @param aExcludePassword to have NS_FORM_INPUT_PASSWORD returning false.
|
||||
* @return true if this is a single line text control or a number control.
|
||||
*/
|
||||
inline bool IsSingleLineTextOrNumberControl(bool aExcludePassword) const;
|
||||
|
||||
/**
|
||||
* Returns whether this is a submittable form control.
|
||||
* @return whether this is a submittable form control.
|
||||
|
@ -249,16 +263,27 @@ bool nsIFormControl::IsTextControl(bool aExcludePassword) const {
|
|||
IsSingleLineTextControl(aExcludePassword, type);
|
||||
}
|
||||
|
||||
bool nsIFormControl::IsTextOrNumberControl(bool aExcludePassword) const {
|
||||
return IsTextControl(aExcludePassword) ||
|
||||
ControlType() == NS_FORM_INPUT_NUMBER;
|
||||
}
|
||||
|
||||
bool nsIFormControl::IsSingleLineTextControl(bool aExcludePassword) const {
|
||||
return IsSingleLineTextControl(aExcludePassword, ControlType());
|
||||
}
|
||||
|
||||
bool nsIFormControl::IsSingleLineTextOrNumberControl(
|
||||
bool aExcludePassword) const {
|
||||
return IsSingleLineTextControl(aExcludePassword) ||
|
||||
ControlType() == NS_FORM_INPUT_NUMBER;
|
||||
}
|
||||
|
||||
/*static*/
|
||||
bool nsIFormControl::IsSingleLineTextControl(bool aExcludePassword,
|
||||
uint32_t aType) {
|
||||
return aType == NS_FORM_INPUT_TEXT || aType == NS_FORM_INPUT_EMAIL ||
|
||||
aType == NS_FORM_INPUT_SEARCH || aType == NS_FORM_INPUT_TEL ||
|
||||
aType == NS_FORM_INPUT_URL || aType == NS_FORM_INPUT_NUMBER ||
|
||||
aType == NS_FORM_INPUT_URL ||
|
||||
// TODO: those are temporary until bug 773205 is fixed.
|
||||
aType == NS_FORM_INPUT_MONTH || aType == NS_FORM_INPUT_WEEK ||
|
||||
aType == NS_FORM_INPUT_DATETIME_LOCAL ||
|
||||
|
|
|
@ -149,6 +149,9 @@ SimpleTest.waitForFocus(async () => {
|
|||
if (!test.result.fireBeforeInputEvent) {
|
||||
is(beforeInputEvents.length, 0,
|
||||
`No "beforeinput" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||
} else if (test.type === "number") {
|
||||
todo_is(beforeInputEvents.length, 1,
|
||||
`Only one "beforeinput" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||
} else {
|
||||
is(beforeInputEvents.length, 1,
|
||||
`Only one "beforeinput" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||
|
@ -176,7 +179,7 @@ SimpleTest.waitForFocus(async () => {
|
|||
}
|
||||
if (inputEvents.length > 0) {
|
||||
if (SpecialPowers.wrap(target).isInputEventTarget) {
|
||||
if (test.type === "time") {
|
||||
if (test.type === "number" || test.type === "time") {
|
||||
todo(inputEvents[0] instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||
} else {
|
||||
|
@ -231,6 +234,9 @@ SimpleTest.waitForFocus(async () => {
|
|||
if (!test.result.fireBeforeInputEvent) {
|
||||
is(beforeInputEvents.length, 0,
|
||||
`No "beforeinput" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||
} else if (test.type === "number") {
|
||||
todo_is(beforeInputEvents.length, 1,
|
||||
`Only one "beforeinput" event should be dispatched when setUserInput("${test.input.after}") is called before ${tag} gets focus`);
|
||||
} else {
|
||||
is(beforeInputEvents.length, 1,
|
||||
`Only one "beforeinput" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||
|
@ -258,7 +264,7 @@ SimpleTest.waitForFocus(async () => {
|
|||
}
|
||||
if (inputEvents.length > 0) {
|
||||
if (SpecialPowers.wrap(target).isInputEventTarget) {
|
||||
if (test.type === "time") {
|
||||
if (test.type === "number" || test.type === "time") {
|
||||
todo(inputEvents[0] instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||
} else {
|
||||
|
|
|
@ -57,19 +57,22 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||
is(aEvent.bubbles, true,
|
||||
`"beforeinput" event should always bubble ${aDescription}`);
|
||||
}
|
||||
|
||||
let skipExpectedDataCheck = false;
|
||||
function checkIfInputIsInputEvent(aEvent, aDescription) {
|
||||
ok(aEvent instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||
is(aEvent.inputType, expectedInputType,
|
||||
`inputType should be "${expectedInputType}" ${aDescription}`);
|
||||
if (!skipExpectedDataCheck)
|
||||
is(aEvent.data, expectedData, `data should be ${expectedData} ${aDescription}`);
|
||||
else
|
||||
info(`data is ${aEvent.data} ${aDescription}`);
|
||||
is(aEvent.dataTransfer, null,
|
||||
`dataTransfer should be null ${aDescription}`);
|
||||
function checkIfInputIsInputEvent(aEvent, aToDo, aDescription) {
|
||||
if (aToDo) {
|
||||
// Probably, key operation should fire "input" event with InputEvent interface.
|
||||
// See https://github.com/w3c/input-events/issues/88
|
||||
todo(aEvent instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||
} else {
|
||||
ok(aEvent instanceof InputEvent,
|
||||
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||
is(aEvent.inputType, expectedInputType,
|
||||
`inputType of "input" event should be "${expectedInputType}" ${aDescription}`);
|
||||
is(aEvent.data, expectedData,
|
||||
`data of "input" event should be ${expectedData} ${aDescription}`);
|
||||
is(aEvent.dataTransfer, null,
|
||||
`dataTransfer of "input" event should be null ${aDescription}`);
|
||||
}
|
||||
is(aEvent.cancelable, false,
|
||||
`"input" event should be never cancelable ${aDescription}`);
|
||||
is(aEvent.bubbles, true,
|
||||
|
@ -92,7 +95,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||
};
|
||||
document.getElementById("textarea").oninput = (aEvent) => {
|
||||
++textareaInput;
|
||||
checkIfInputIsInputEvent(aEvent, "on textarea element");
|
||||
checkIfInputIsInputEvent(aEvent, false, "on textarea element");
|
||||
};
|
||||
|
||||
// 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) => {
|
||||
++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) => {
|
||||
++numberInput;
|
||||
checkIfInputIsInputEvent(aEvent, "on input element whose type is number");
|
||||
checkIfInputIsInputEvent(aEvent, true, "on input element whose type is number");
|
||||
};
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
@ -366,22 +369,18 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||
number.focus();
|
||||
// <input type="number">'s inputType value hasn't been decided, see
|
||||
// https://github.com/w3c/input-events/issues/88
|
||||
expectedInputType = "insertReplacementText";
|
||||
expectedData = "1";
|
||||
expectedInputType = "";
|
||||
expectedData = null;
|
||||
expectedBeforeInputCancelable = false;
|
||||
synthesizeKey("KEY_ArrowUp");
|
||||
todo_is(numberBeforeInput, 1, "beforeinput event should be dispatched for up/down arrow key keypress");
|
||||
is(numberInput, 1, "input event should be dispatched for up/down arrow key keypress");
|
||||
is(number.value, "1", "sanity check value of number control after keypress");
|
||||
|
||||
// `data` will be the value of the input, but we can't change
|
||||
// `expectedData` and use {repeat: 3} at the same time.
|
||||
skipExpectedDataCheck = true;
|
||||
synthesizeKey("KEY_ArrowDown", {repeat: 3});
|
||||
todo_is(numberBeforeInput, 4, "beforeinput event should be dispatched for each up/down arrow key keypress event, even when rapidly repeated");
|
||||
is(numberInput, 4, "input event should be dispatched for each up/down arrow key keypress event, even when rapidly repeated");
|
||||
is(number.value, "-2", "sanity check value of number control after multiple keydown events");
|
||||
skipExpectedDataCheck = false;
|
||||
|
||||
number.blur();
|
||||
todo_is(numberBeforeInput, 4, "beforeinput event shouldn't be dispatched on blur");
|
||||
|
|
|
@ -60,7 +60,7 @@ function test_focus_redirects_to_text_control_but_not_for_activeElement() {
|
|||
.getService(SpecialPowers.Ci.nsIFocusManager);
|
||||
var focusedElement = fm.focusedElement;
|
||||
is (focusedElement.localName, "input", "focusedElement should be an input element");
|
||||
is (focusedElement.getAttribute("type"), "number", "focusedElement should of type number");
|
||||
is (focusedElement.getAttribute("type"), "text", "focusedElement should of type text");
|
||||
}
|
||||
|
||||
var blurEventCounter = 0;
|
||||
|
|
|
@ -45,10 +45,8 @@ function runTest(test) {
|
|||
elem.value = 0;
|
||||
elem.select();
|
||||
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");
|
||||
is(elem.value, test.inputWithoutGrouping,
|
||||
"Test " + test.desc + " ('" + test.langTag + "') localization without grouping separator as string");
|
||||
}
|
||||
|
||||
function runInvalidInputTest(test) {
|
||||
|
|
|
@ -51,7 +51,7 @@ const SPIN_DOWN_Y = inputRect.height - 3;
|
|||
function checkInputEvent(aEvent, aDescription) {
|
||||
// Probably, key operation should fire "input" event with InputEvent interface.
|
||||
// See https://github.com/w3c/input-events/issues/88
|
||||
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.bubbles, true, `"input" event should always bubble on input element whose type is number ${aDescription}`);
|
||||
info(`Data: ${aEvent.data}, value: ${aEvent.target.value}`);
|
||||
|
@ -233,6 +233,7 @@ var spinTests = [
|
|||
synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
|
||||
}
|
||||
];
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
|
|
|
@ -111,6 +111,10 @@ function checkIsInvalid(element, infoStr)
|
|||
{
|
||||
ok(element.validity.badInput,
|
||||
"Element should suffer from bad input for " + infoStr);
|
||||
if (element.id == "requiredinput") {
|
||||
ok(element.validity.valueMissing,
|
||||
"Element should suffer from value missing for " + infoStr);
|
||||
}
|
||||
ok(!element.validity.valid, "Element should not be valid for " + infoStr);
|
||||
ok(!element.checkValidity(), "checkValidity() should return false for " + infoStr);
|
||||
ok(gInvalid, "The invalid event should have been thrown for " + infoStr);
|
||||
|
|
|
@ -172,6 +172,18 @@ partial interface HTMLInputElement {
|
|||
[ChromeOnly]
|
||||
void mozSetDndFilesAndDirectories(sequence<(File or Directory)> list);
|
||||
|
||||
// Number controls (<input type=number>) have an anonymous text control
|
||||
// (<input type=text>) in the anonymous shadow tree that they contain. On
|
||||
// such an anonymous text control this property provides access to the
|
||||
// number control that owns the text control. This is useful, for example,
|
||||
// in code that looks at the currently focused element to make decisions
|
||||
// about which IME to bring up. Such code needs to be able to check for any
|
||||
// owning number control since it probably wants to bring up a number pad
|
||||
// instead of the standard keyboard, even when the anonymous text control has
|
||||
// focus.
|
||||
[ChromeOnly]
|
||||
readonly attribute HTMLInputElement? ownerNumberControl;
|
||||
|
||||
boolean mozIsTextField(boolean aExcludePassword);
|
||||
|
||||
[ChromeOnly]
|
||||
|
|
|
@ -385,7 +385,9 @@ nsresult EditorBase::InstallEventListeners() {
|
|||
}
|
||||
|
||||
// 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);
|
||||
|
||||
nsresult rv = mEventListener->Connect(this);
|
||||
|
@ -4981,12 +4983,14 @@ void EditorBase::ReinitializeSelection(Element& aElement) {
|
|||
Element* EditorBase::GetEditorRoot() const { return GetRoot(); }
|
||||
|
||||
Element* EditorBase::GetExposedRoot() const {
|
||||
Element* root = GetRoot();
|
||||
if (!root || !root->IsInNativeAnonymousSubtree()) {
|
||||
return root;
|
||||
Element* rootElement = GetRoot();
|
||||
|
||||
// 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() {
|
||||
|
|
|
@ -41,18 +41,270 @@ NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame)
|
|||
|
||||
NS_QUERYFRAME_HEAD(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,
|
||||
nsPresContext* aPresContext)
|
||||
: nsTextControlFrame(aStyle, aPresContext, kClassID) {}
|
||||
: nsContainerFrame(aStyle, aPresContext, kClassID),
|
||||
mHandlingInputEvent(false) {}
|
||||
|
||||
void nsNumberControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
|
||||
PostDestroyData& aPostDestroyData) {
|
||||
NS_ASSERTION(
|
||||
!GetPrevContinuation() && !GetNextContinuation(),
|
||||
"nsNumberControlFrame should not have continuations; if it does we "
|
||||
"need to call RegUnregAccessKey only for the first");
|
||||
nsCheckboxRadioFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
|
||||
aPostDestroyData.AddAnonymousContent(mOuterWrapper.forget());
|
||||
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(
|
||||
Element* aParent, nsAtom* aTagName, PseudoStyleType aPseudoType) {
|
||||
// Get the NodeInfoManager and tag necessary to create the anonymous divs.
|
||||
|
@ -80,12 +332,10 @@ nsresult nsNumberControlFrame::CreateAnonymousContent(
|
|||
//
|
||||
// input
|
||||
// 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 up (up arrow button)
|
||||
// div - spin down (down arrow button)
|
||||
// div - placeholder
|
||||
// div - preview div
|
||||
//
|
||||
// If you change this, be careful to change the destruction order in
|
||||
// nsNumberControlFrame::DestroyFrom.
|
||||
|
@ -96,18 +346,48 @@ nsresult nsNumberControlFrame::CreateAnonymousContent(
|
|||
|
||||
aElements.AppendElement(mOuterWrapper);
|
||||
|
||||
nsTArray<ContentInfo> nestedContent;
|
||||
nsTextControlFrame::CreateAnonymousContent(nestedContent);
|
||||
for (auto& content : nestedContent) {
|
||||
// The root goes inside the container.
|
||||
if (content.mContent == mRootNode) {
|
||||
mOuterWrapper->AppendChildTo(content.mContent, false);
|
||||
} else {
|
||||
// The rest (placeholder and preview), directly under us.
|
||||
aElements.AppendElement(std::move(content));
|
||||
}
|
||||
// Create the ::-moz-number-text pseudo-element:
|
||||
mTextField = MakeAnonymousElement(mOuterWrapper, nsGkAtoms::input,
|
||||
PseudoStyleType::mozNumberText);
|
||||
|
||||
mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
|
||||
NS_LITERAL_STRING("text"), false);
|
||||
|
||||
HTMLInputElement* content = HTMLInputElement::FromNode(mContent);
|
||||
HTMLInputElement* textField = HTMLInputElement::FromNode(mTextField);
|
||||
|
||||
// Initialize the text field value:
|
||||
nsAutoString value;
|
||||
content->GetValue(value, CallerType::System);
|
||||
SetValueOfAnonTextControl(value);
|
||||
|
||||
// If we're readonly, make sure our anonymous text control is too:
|
||||
nsAutoString readonly;
|
||||
if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly,
|
||||
readonly)) {
|
||||
mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly,
|
||||
false);
|
||||
}
|
||||
|
||||
// Propogate our tabindex:
|
||||
textField->SetTabIndex(content->TabIndex(), IgnoreErrors());
|
||||
|
||||
// Initialize the text field's placeholder, if ours is set:
|
||||
nsAutoString placeholder;
|
||||
if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder,
|
||||
placeholder)) {
|
||||
mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholder,
|
||||
false);
|
||||
}
|
||||
|
||||
if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS)) {
|
||||
// We don't want to focus the frame but the text field.
|
||||
RefPtr<FocusTextField> focusJob = new FocusTextField(mContent, mTextField);
|
||||
nsContentUtils::AddScriptRunner(focusJob);
|
||||
}
|
||||
|
||||
SyncDisabledState(); // Sync disabled state of 'mTextField'.
|
||||
|
||||
if (StyleDisplay()->mAppearance == StyleAppearance::Textfield) {
|
||||
// The author has elected to hide the spinner by setting this
|
||||
// -moz-appearance. We will reframe if it changes.
|
||||
|
@ -129,6 +409,19 @@ nsresult nsNumberControlFrame::CreateAnonymousContent(
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsNumberControlFrame::SetFocus(bool aOn, bool aRepaint) {
|
||||
GetTextFieldFrame()->SetFocus(aOn, aRepaint);
|
||||
}
|
||||
|
||||
nsresult nsNumberControlFrame::SetFormProperty(nsAtom* aName,
|
||||
const nsAString& aValue) {
|
||||
return GetTextFieldFrame()->SetFormProperty(aName, aValue);
|
||||
}
|
||||
|
||||
HTMLInputElement* nsNumberControlFrame::GetAnonTextControl() const {
|
||||
return HTMLInputElement::FromNode(mTextField);
|
||||
}
|
||||
|
||||
/* static */
|
||||
nsNumberControlFrame* nsNumberControlFrame::GetNumberControlFrameForTextField(
|
||||
nsIFrame* aFrame) {
|
||||
|
@ -232,6 +525,31 @@ bool nsNumberControlFrame::SpinnerDownButtonIsDepressed() const {
|
|||
->NumberSpinnerDownButtonIsDepressed();
|
||||
}
|
||||
|
||||
bool nsNumberControlFrame::IsFocused() const {
|
||||
// Normally this depends on the state of our anonymous text control (which
|
||||
// takes focus for us), but in the case that it does not have a frame we will
|
||||
// have focus ourself.
|
||||
return mTextField->State().HasState(NS_EVENT_STATE_FOCUS) ||
|
||||
mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS);
|
||||
}
|
||||
|
||||
void nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent) {
|
||||
if (aEvent->mOriginalTarget != mTextField) {
|
||||
// Move focus to our text field
|
||||
RefPtr<HTMLInputElement> textField = HTMLInputElement::FromNode(mTextField);
|
||||
|
||||
// Use default FocusOptions, because this method isn't supposed to be called
|
||||
// from a WebIDL interface.
|
||||
FocusOptions options;
|
||||
textField->Focus(options, IgnoreErrors());
|
||||
}
|
||||
}
|
||||
|
||||
void nsNumberControlFrame::HandleSelectCall() {
|
||||
RefPtr<HTMLInputElement> textField = HTMLInputElement::FromNode(mTextField);
|
||||
textField->Select();
|
||||
}
|
||||
|
||||
#define STYLES_DISABLING_NATIVE_THEMING \
|
||||
NS_AUTHOR_SPECIFIED_BACKGROUND | NS_AUTHOR_SPECIFIED_PADDING | \
|
||||
NS_AUTHOR_SPECIFIED_BORDER
|
||||
|
@ -255,17 +573,128 @@ bool nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const {
|
|||
spinDownFrame, STYLES_DISABLING_NATIVE_THEMING);
|
||||
}
|
||||
|
||||
bool nsNumberControlFrame::GetNaturalBaselineBOffset(
|
||||
WritingMode aWM, BaselineSharingGroup aGroup, nscoord* aBaseline) const {
|
||||
if (StyleDisplay()->IsContainLayout()) {
|
||||
return false;
|
||||
}
|
||||
nsIFrame* inner = GetAnonTextControl()->GetPrimaryFrame();
|
||||
nscoord baseline;
|
||||
DebugOnly<bool> hasBaseline = inner->GetNaturalBaselineBOffset(
|
||||
aWM, BaselineSharingGroup::First, &baseline);
|
||||
MOZ_ASSERT(hasBaseline);
|
||||
nsPoint offset = inner->GetOffsetToIgnoringScrolling(this);
|
||||
baseline += aWM.IsVertical() ? offset.x : offset.y;
|
||||
if (aGroup == BaselineSharingGroup::Last) {
|
||||
baseline = BSize(aWM) - baseline;
|
||||
}
|
||||
*aBaseline = baseline;
|
||||
return true;
|
||||
}
|
||||
|
||||
void nsNumberControlFrame::AppendAnonymousContentTo(
|
||||
nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
|
||||
// Only one direct anonymous child:
|
||||
if (mOuterWrapper) {
|
||||
aElements.AppendElement(mOuterWrapper);
|
||||
}
|
||||
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
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "nsContainerFrame.h"
|
||||
#include "nsTextControlFrame.h"
|
||||
#include "nsIFormControlFrame.h"
|
||||
#include "nsIAnonymousContentCreator.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
@ -29,10 +28,10 @@ class HTMLInputElement;
|
|||
|
||||
/**
|
||||
* 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,
|
||||
ComputedStyle* aStyle);
|
||||
|
||||
|
@ -49,23 +48,80 @@ class nsNumberControlFrame final : public nsTextControlFrame {
|
|||
NS_DECL_QUERYFRAME
|
||||
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
|
||||
mozilla::a11y::AccType AccessibleType() override;
|
||||
virtual mozilla::a11y::AccType AccessibleType() override;
|
||||
#endif
|
||||
|
||||
virtual nscoord GetMinISize(gfxContext* aRenderingContext) override;
|
||||
|
||||
virtual nscoord GetPrefISize(gfxContext* aRenderingContext) override;
|
||||
|
||||
virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
|
||||
const ReflowInput& aReflowInput,
|
||||
nsReflowStatus& aStatus) override;
|
||||
|
||||
virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
|
||||
int32_t aModType) override;
|
||||
|
||||
bool GetNaturalBaselineBOffset(mozilla::WritingMode aWM,
|
||||
BaselineSharingGroup aGroup,
|
||||
nscoord* aBaseline) const override;
|
||||
|
||||
// nsIAnonymousContentCreator
|
||||
nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
|
||||
void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
|
||||
uint32_t aFilter) override;
|
||||
virtual nsresult CreateAnonymousContent(
|
||||
nsTArray<ContentInfo>& aElements) override;
|
||||
virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
|
||||
uint32_t aFilter) override;
|
||||
|
||||
#ifdef DEBUG_FRAME_DUMP
|
||||
nsresult GetFrameName(nsAString& aResult) const override {
|
||||
virtual nsresult GetFrameName(nsAString& aResult) const override {
|
||||
return MakeFrameName(NS_LITERAL_STRING("NumberControl"), aResult);
|
||||
}
|
||||
#endif
|
||||
|
||||
virtual bool IsFrameOfType(uint32_t aFlags) const override {
|
||||
return nsContainerFrame::IsFrameOfType(
|
||||
aFlags & ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
|
||||
}
|
||||
|
||||
// nsIFormControlFrame
|
||||
virtual void SetFocus(bool aOn, bool aRepaint) override;
|
||||
virtual nsresult SetFormProperty(nsAtom* aName,
|
||||
const nsAString& aValue) override;
|
||||
|
||||
/**
|
||||
* This method attempts to localizes aValue and then sets the result as the
|
||||
* value of our anonymous text control. It's called when our
|
||||
* HTMLInputElement's value changes, when we need to sync up the value
|
||||
* displayed in our anonymous text control.
|
||||
*/
|
||||
void SetValueOfAnonTextControl(const nsAString& aValue);
|
||||
|
||||
/**
|
||||
* This method gets the string value of our anonymous text control,
|
||||
* attempts to normalizes (de-localizes) it, then sets the outparam aValue to
|
||||
* the result. It's called when user input changes the text value of our
|
||||
* anonymous text control so that we can sync up the internal value of our
|
||||
* HTMLInputElement.
|
||||
*/
|
||||
void GetValueOfAnonTextControl(nsAString& aValue);
|
||||
|
||||
bool AnonTextControlIsEmpty();
|
||||
|
||||
/**
|
||||
* Called to notify this frame that its HTMLInputElement is currently
|
||||
* processing a DOM 'input' event.
|
||||
*/
|
||||
void HandlingInputEvent(bool aHandlingEvent) {
|
||||
mHandlingInputEvent = aHandlingEvent;
|
||||
}
|
||||
|
||||
HTMLInputElement* GetAnonTextControl() const;
|
||||
|
||||
/**
|
||||
* If the frame is the frame for an nsNumberControlFrame's anonymous text
|
||||
* field, returns the nsNumberControlFrame. Else returns nullptr.
|
||||
|
@ -94,6 +150,10 @@ class nsNumberControlFrame final : public nsTextControlFrame {
|
|||
bool SpinnerUpButtonIsDepressed() const;
|
||||
bool SpinnerDownButtonIsDepressed() const;
|
||||
|
||||
bool IsFocused() const;
|
||||
|
||||
void HandleFocusEvent(WidgetEvent* aEvent);
|
||||
|
||||
/**
|
||||
* Our element had HTMLInputElement::Select() called on it.
|
||||
*/
|
||||
|
@ -102,16 +162,47 @@ class nsNumberControlFrame final : public nsTextControlFrame {
|
|||
bool ShouldUseNativeStyleForSpinner() const;
|
||||
|
||||
private:
|
||||
nsITextControlFrame* GetTextFieldFrame();
|
||||
already_AddRefed<Element> MakeAnonymousElement(Element* aParent,
|
||||
nsAtom* aTagName,
|
||||
PseudoStyleType aPseudoType);
|
||||
|
||||
// See nsNumberControlFrame::CreateAnonymousContent for a description of
|
||||
// these.
|
||||
class SyncDisabledStateEvent;
|
||||
friend class SyncDisabledStateEvent;
|
||||
class SyncDisabledStateEvent : public mozilla::Runnable {
|
||||
public:
|
||||
explicit SyncDisabledStateEvent(nsNumberControlFrame* aFrame)
|
||||
: mozilla::Runnable("nsNumberControlFrame::SyncDisabledStateEvent"),
|
||||
mFrame(aFrame) {}
|
||||
|
||||
NS_IMETHOD Run() override {
|
||||
nsNumberControlFrame* frame =
|
||||
static_cast<nsNumberControlFrame*>(mFrame.GetFrame());
|
||||
NS_ENSURE_STATE(frame);
|
||||
|
||||
frame->SyncDisabledState();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
WeakFrame mFrame;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync the disabled state of the anonymous children up with our content's.
|
||||
*/
|
||||
void SyncDisabledState();
|
||||
|
||||
/**
|
||||
* The text field used to edit and show the number.
|
||||
* @see nsNumberControlFrame::CreateAnonymousContent.
|
||||
*/
|
||||
nsCOMPtr<Element> mOuterWrapper;
|
||||
nsCOMPtr<Element> mTextField;
|
||||
nsCOMPtr<Element> mSpinBox;
|
||||
nsCOMPtr<Element> mSpinUp;
|
||||
nsCOMPtr<Element> mSpinDown;
|
||||
bool mHandlingInputEvent;
|
||||
};
|
||||
|
||||
#endif // nsNumberControlFrame_h__
|
||||
|
|
|
@ -108,9 +108,8 @@ class nsTextControlFrame::nsAnonDivObserver final
|
|||
};
|
||||
|
||||
nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle,
|
||||
nsPresContext* aPresContext,
|
||||
nsIFrame::ClassID aClassID)
|
||||
: nsContainerFrame(aStyle, aPresContext, aClassID),
|
||||
nsPresContext* aPresContext)
|
||||
: nsContainerFrame(aStyle, aPresContext, kClassID),
|
||||
mFirstBaseline(NS_INTRINSIC_ISIZE_UNKNOWN),
|
||||
mEditorHasBeenInitialized(false),
|
||||
mIsProcessing(false)
|
||||
|
@ -124,13 +123,6 @@ nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle,
|
|||
|
||||
nsTextControlFrame::~nsTextControlFrame() {}
|
||||
|
||||
nsIScrollableFrame* nsTextControlFrame::GetScrollTargetFrame() {
|
||||
if (!mRootNode) {
|
||||
return nullptr;
|
||||
}
|
||||
return do_QueryFrame(mRootNode->GetPrimaryFrame());
|
||||
}
|
||||
|
||||
void nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
|
||||
PostDestroyData& aPostDestroyData) {
|
||||
mScrollEvent.Revoke();
|
||||
|
@ -151,15 +143,8 @@ void nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
|
|||
mMutationObserver = nullptr;
|
||||
}
|
||||
|
||||
// If we're a subclass like nsNumberControlFrame, then it owns the root of the
|
||||
// anonymous subtree where mRootNode is.
|
||||
if (mClass == kClassID) {
|
||||
aPostDestroyData.AddAnonymousContent(mRootNode.forget());
|
||||
} else {
|
||||
MOZ_ASSERT(!mRootNode || !mRootNode->IsRootOfAnonymousSubtree());
|
||||
mRootNode = nullptr;
|
||||
}
|
||||
|
||||
// FIXME(emilio, bug 1400618): Do this after the child frames are destroyed.
|
||||
aPostDestroyData.AddAnonymousContent(mRootNode.forget());
|
||||
aPostDestroyData.AddAnonymousContent(mPlaceholderDiv.forget());
|
||||
aPostDestroyData.AddAnonymousContent(mPreviewDiv.forget());
|
||||
|
||||
|
@ -227,7 +212,9 @@ LogicalSize nsTextControlFrame::CalcIntrinsicSize(
|
|||
|
||||
// Add in the size of the scrollbars for textarea
|
||||
if (IsTextArea()) {
|
||||
nsIScrollableFrame* scrollableFrame = GetScrollTargetFrame();
|
||||
nsIFrame* first = PrincipalChildList().FirstChild();
|
||||
|
||||
nsIScrollableFrame* scrollableFrame = do_QueryFrame(first);
|
||||
NS_ASSERTION(scrollableFrame, "Child must be scrollable");
|
||||
|
||||
if (scrollableFrame) {
|
||||
|
@ -345,6 +332,8 @@ already_AddRefed<Element> nsTextControlFrame::CreateEmptyAnonymousDiv(
|
|||
RefPtr<Element> divElement = NS_NewHTMLDivElement(nodeInfo.forget());
|
||||
switch (aAnonymousDivType) {
|
||||
case AnonymousDivType::Root: {
|
||||
divElement->SetIsNativeAnonymousRoot();
|
||||
|
||||
// Make our root node editable
|
||||
divElement->SetFlags(NODE_IS_EDITABLE);
|
||||
|
||||
|
@ -421,26 +410,12 @@ nsresult nsTextControlFrame::CreateAnonymousContent(
|
|||
RefPtr<TextControlElement> textControlElement =
|
||||
TextControlElement::FromNode(GetContent());
|
||||
MOZ_ASSERT(textControlElement);
|
||||
mRootNode = CreateEmptyAnonymousDiv(AnonymousDivType::Root);
|
||||
if (NS_WARN_IF(!mRootNode)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
nsresult rv = CreateRootNode();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mMutationObserver = new nsAnonDivObserver(*this);
|
||||
mRootNode->AddMutationObserver(mMutationObserver);
|
||||
|
||||
// Bind the frame to its text control.
|
||||
//
|
||||
// This can realistically fail in paginated mode, where we may replicate
|
||||
// fixed-positioned elements and the replicated frame will not get the chance
|
||||
// to get an editor.
|
||||
nsresult rv = textControlElement->BindToFrame(this);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
mRootNode->RemoveMutationObserver(mMutationObserver);
|
||||
mMutationObserver = nullptr;
|
||||
mRootNode = nullptr;
|
||||
return rv;
|
||||
}
|
||||
// Bind the frame to its text control
|
||||
rv = textControlElement->BindToFrame(this);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
aElements.AppendElement(mRootNode);
|
||||
CreatePlaceholderIfNeeded();
|
||||
|
@ -502,6 +477,18 @@ void nsTextControlFrame::InitializeEagerlyIfNeeded() {
|
|||
nsContentUtils::AddScriptRunner(initializer);
|
||||
}
|
||||
|
||||
nsresult nsTextControlFrame::CreateRootNode() {
|
||||
MOZ_ASSERT(!mRootNode);
|
||||
|
||||
mRootNode = CreateEmptyAnonymousDiv(AnonymousDivType::Root);
|
||||
if (NS_WARN_IF(!mRootNode)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mMutationObserver = new nsAnonDivObserver(*this);
|
||||
mRootNode->AddMutationObserver(mMutationObserver);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsTextControlFrame::CreatePlaceholderIfNeeded() {
|
||||
MOZ_ASSERT(!mPlaceholderDiv);
|
||||
|
||||
|
@ -602,30 +589,6 @@ LogicalSize nsTextControlFrame::ComputeAutoSize(
|
|||
return autoSize;
|
||||
}
|
||||
|
||||
void nsTextControlFrame::ComputeBaseline(const ReflowInput& aReflowInput,
|
||||
ReflowOutput& aDesiredSize) {
|
||||
// If we're layout-contained, we have no baseline.
|
||||
if (aReflowInput.mStyleDisplay->IsContainLayout()) {
|
||||
mFirstBaseline = NS_INTRINSIC_ISIZE_UNKNOWN;
|
||||
return;
|
||||
}
|
||||
WritingMode wm = aReflowInput.GetWritingMode();
|
||||
|
||||
// Calculate the baseline and store it in mFirstBaseline.
|
||||
nscoord lineHeight = aReflowInput.ComputedBSize();
|
||||
float inflation = nsLayoutUtils::FontSizeInflationFor(this);
|
||||
if (!IsSingleLineTextControl()) {
|
||||
lineHeight = ReflowInput::CalcLineHeight(
|
||||
GetContent(), Style(), PresContext(), NS_UNCONSTRAINEDSIZE, inflation);
|
||||
}
|
||||
RefPtr<nsFontMetrics> fontMet =
|
||||
nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
|
||||
mFirstBaseline = nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight,
|
||||
wm.IsLineInverted()) +
|
||||
aReflowInput.ComputedLogicalBorderPadding().BStart(wm);
|
||||
aDesiredSize.SetBlockStartAscent(mFirstBaseline);
|
||||
}
|
||||
|
||||
void nsTextControlFrame::Reflow(nsPresContext* aPresContext,
|
||||
ReflowOutput& aDesiredSize,
|
||||
const ReflowInput& aReflowInput,
|
||||
|
@ -650,7 +613,22 @@ void nsTextControlFrame::Reflow(nsPresContext* aPresContext,
|
|||
aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm));
|
||||
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
|
||||
aDesiredSize.SetOverflowAreasToDesiredBounds();
|
||||
|
@ -1204,21 +1182,6 @@ bool nsTextControlFrame::GetMaxLength(int32_t* aSize) {
|
|||
|
||||
// END IMPLEMENTING NS_IFORMCONTROLFRAME
|
||||
|
||||
// NOTE(emilio): This is needed because the root->primary frame map is not set
|
||||
// up by the time this is called.
|
||||
static nsIFrame* FindRootNodeFrame(const nsFrameList& aChildList,
|
||||
const nsIContent* aRoot) {
|
||||
for (nsIFrame* f : aChildList) {
|
||||
if (f->GetContent() == aRoot) {
|
||||
return f;
|
||||
}
|
||||
if (nsIFrame* root = FindRootNodeFrame(f->PrincipalChildList(), aRoot)) {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
|
||||
nsFrameList& aChildList) {
|
||||
nsContainerFrame::SetInitialChildList(aListID, aChildList);
|
||||
|
@ -1226,20 +1189,22 @@ void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
|
|||
return;
|
||||
}
|
||||
|
||||
// Mark the scroll frame as being a reflow root. This will allow incremental
|
||||
// reflows to be initiated at the scroll frame, rather than descending from
|
||||
// the root frame of the frame hierarchy.
|
||||
if (nsIFrame* frame = FindRootNodeFrame(PrincipalChildList(), mRootNode)) {
|
||||
frame->AddStateBits(NS_FRAME_REFLOW_ROOT);
|
||||
// Mark the scroll frame as being a reflow root. This will allow
|
||||
// incremental reflows to be initiated at the scroll frame, rather
|
||||
// than descending from the root frame of the frame hierarchy.
|
||||
if (nsIFrame* first = PrincipalChildList().FirstChild()) {
|
||||
first->AddStateBits(NS_FRAME_REFLOW_ROOT);
|
||||
|
||||
auto* textControlElement = TextControlElement::FromNode(GetContent());
|
||||
TextControlElement* textControlElement =
|
||||
TextControlElement::FromNode(GetContent());
|
||||
MOZ_ASSERT(textControlElement);
|
||||
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
|
||||
// div, do it here!
|
||||
nsIStatefulFrame* statefulFrame = do_QueryFrame(frame);
|
||||
nsIStatefulFrame* statefulFrame = do_QueryFrame(first);
|
||||
NS_ASSERTION(statefulFrame,
|
||||
"unexpected type of frame for the anonymous div");
|
||||
UniquePtr<PresState> fakePresState = NewPresState();
|
||||
|
@ -1248,13 +1213,12 @@ void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
|
|||
RemoveProperty(ContentScrollPos());
|
||||
delete contentScrollPos;
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(!mRootNode || PrincipalChildList().IsEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
void nsTextControlFrame::SetValueChanged(bool aValueChanged) {
|
||||
auto* textControlElement = TextControlElement::FromNode(GetContent());
|
||||
TextControlElement* textControlElement =
|
||||
TextControlElement::FromNode(GetContent());
|
||||
MOZ_ASSERT(textControlElement);
|
||||
|
||||
if (mPlaceholderDiv) {
|
||||
|
@ -1319,7 +1283,8 @@ nsresult nsTextControlFrame::UpdateValueDisplay(bool aNotify,
|
|||
}
|
||||
|
||||
if (aBeforeEditorInit && value.IsEmpty()) {
|
||||
if (nsIContent* node = mRootNode->GetFirstChild()) {
|
||||
nsIContent* node = mRootNode->GetFirstChild();
|
||||
if (node) {
|
||||
mRootNode->RemoveChildNode(node, true);
|
||||
}
|
||||
return NS_OK;
|
||||
|
@ -1344,15 +1309,20 @@ nsTextControlFrame::GetOwnedSelectionController(
|
|||
}
|
||||
|
||||
nsFrameSelection* nsTextControlFrame::GetOwnedFrameSelection() {
|
||||
auto* textControlElement = TextControlElement::FromNode(GetContent());
|
||||
TextControlElement* textControlElement =
|
||||
TextControlElement::FromNode(GetContent());
|
||||
MOZ_ASSERT(textControlElement);
|
||||
return textControlElement->GetConstFrameSelection();
|
||||
}
|
||||
|
||||
UniquePtr<PresState> nsTextControlFrame::SaveState() {
|
||||
if (nsIStatefulFrame* scrollStateFrame =
|
||||
do_QueryFrame(GetScrollTargetFrame())) {
|
||||
return scrollStateFrame->SaveState();
|
||||
if (mRootNode) {
|
||||
// Query the nsIStatefulFrame from the HTMLScrollFrame
|
||||
nsIStatefulFrame* scrollStateFrame =
|
||||
do_QueryFrame(mRootNode->GetPrimaryFrame());
|
||||
if (scrollStateFrame) {
|
||||
return scrollStateFrame->SaveState();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
@ -1362,10 +1332,13 @@ NS_IMETHODIMP
|
|||
nsTextControlFrame::RestoreState(PresState* aState) {
|
||||
NS_ENSURE_ARG_POINTER(aState);
|
||||
|
||||
// Query the nsIStatefulFrame from the HTMLScrollFrame
|
||||
if (nsIStatefulFrame* scrollStateFrame =
|
||||
do_QueryFrame(GetScrollTargetFrame())) {
|
||||
return scrollStateFrame->RestoreState(aState);
|
||||
if (mRootNode) {
|
||||
// Query the nsIStatefulFrame from the HTMLScrollFrame
|
||||
nsIStatefulFrame* scrollStateFrame =
|
||||
do_QueryFrame(mRootNode->GetPrimaryFrame());
|
||||
if (scrollStateFrame) {
|
||||
return scrollStateFrame->RestoreState(aState);
|
||||
}
|
||||
}
|
||||
|
||||
// Most likely, we don't have our anonymous content constructed yet, which
|
||||
|
@ -1389,42 +1362,29 @@ void nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
|||
*/
|
||||
DO_GLOBAL_REFLOW_COUNT_DSP("nsTextControlFrame");
|
||||
|
||||
auto* control = TextControlElement::FromNode(GetContent());
|
||||
MOZ_ASSERT(control);
|
||||
TextControlElement* textControlElement =
|
||||
TextControlElement::FromNode(GetContent());
|
||||
MOZ_ASSERT(textControlElement);
|
||||
|
||||
DisplayBorderBackgroundOutline(aBuilder, aLists);
|
||||
|
||||
nsIFrame* kid = mFrames.FirstChild();
|
||||
// Redirect all lists to the Content list so that nothing can escape, ie
|
||||
// opacity creating stacking contexts that then get sorted with stacking
|
||||
// contexts external to us.
|
||||
nsDisplayList* content = aLists.Content();
|
||||
nsDisplayListSet set(content, content, content, content, content, content);
|
||||
|
||||
for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
|
||||
nsIContent* kidContent = kid->GetContent();
|
||||
Maybe<DisplayListClipState::AutoSaveRestore> overlayTextClip;
|
||||
if (kidContent == mPlaceholderDiv && !control->GetPlaceholderVisibility()) {
|
||||
continue;
|
||||
while (kid) {
|
||||
// If the frame is the placeholder or preview frame, we should only show
|
||||
// it if it has to be visible.
|
||||
if (!((kid->GetContent() == mPlaceholderDiv &&
|
||||
!textControlElement->GetPlaceholderVisibility()) ||
|
||||
(kid->GetContent() == mPreviewDiv &&
|
||||
!textControlElement->GetPreviewVisibility()))) {
|
||||
BuildDisplayListForChild(aBuilder, kid, set, 0);
|
||||
}
|
||||
if (kidContent == mPreviewDiv && !control->GetPreviewVisibility()) {
|
||||
continue;
|
||||
}
|
||||
// Clip the preview text to the root box, so that it doesn't, e.g., overlay
|
||||
// with our <input type="number"> spin buttons.
|
||||
//
|
||||
// For other input types, this will be a noop since we size the root via
|
||||
// ReflowTextControlChild, which sets the same available space for all
|
||||
// children.
|
||||
if (kidContent == mPlaceholderDiv || kidContent == mPreviewDiv) {
|
||||
if (mRootNode) {
|
||||
if (auto* root = mRootNode->GetPrimaryFrame()) {
|
||||
overlayTextClip.emplace(aBuilder);
|
||||
nsRect rootBox(aBuilder->ToReferenceFrame(root), root->GetSize());
|
||||
overlayTextClip->ClipContentDescendants(rootBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
BuildDisplayListForChild(aBuilder, kid, set, 0);
|
||||
kid = kid->GetNextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,23 +28,17 @@ class Element;
|
|||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
class nsTextControlFrame : public nsContainerFrame,
|
||||
public nsIAnonymousContentCreator,
|
||||
public nsITextControlFrame,
|
||||
public nsIStatefulFrame {
|
||||
class nsTextControlFrame final : public nsContainerFrame,
|
||||
public nsIAnonymousContentCreator,
|
||||
public nsITextControlFrame,
|
||||
public nsIStatefulFrame {
|
||||
public:
|
||||
NS_DECL_FRAMEARENA_HELPERS(nsTextControlFrame)
|
||||
|
||||
NS_DECLARE_FRAME_PROPERTY_DELETABLE(ContentScrollPos, nsPoint)
|
||||
|
||||
protected:
|
||||
nsTextControlFrame(ComputedStyle*, nsPresContext*, nsIFrame::ClassID);
|
||||
|
||||
public:
|
||||
explicit nsTextControlFrame(ComputedStyle* aStyle,
|
||||
nsPresContext* aPresContext)
|
||||
: nsTextControlFrame(aStyle, aPresContext, kClassID) {}
|
||||
|
||||
nsPresContext* aPresContext);
|
||||
virtual ~nsTextControlFrame();
|
||||
|
||||
/**
|
||||
|
@ -56,26 +50,25 @@ class nsTextControlFrame : public nsContainerFrame,
|
|||
* latter won't run content script too. Therefore, this won't run
|
||||
* unsafe script.
|
||||
*/
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY void DestroyFrom(nsIFrame* aDestructRoot,
|
||||
PostDestroyData&) override;
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void DestroyFrom(
|
||||
nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) override;
|
||||
|
||||
nsIScrollableFrame* GetScrollTargetFrame() override;
|
||||
nsIScrollableFrame* GetScrollTargetFrame() const {
|
||||
return const_cast<nsTextControlFrame*>(this)->GetScrollTargetFrame();
|
||||
virtual nsIScrollableFrame* GetScrollTargetFrame() override {
|
||||
return do_QueryFrame(PrincipalChildList().FirstChild());
|
||||
}
|
||||
|
||||
nscoord GetMinISize(gfxContext* aRenderingContext) override;
|
||||
nscoord GetPrefISize(gfxContext* aRenderingContext) override;
|
||||
virtual nscoord GetMinISize(gfxContext* aRenderingContext) override;
|
||||
virtual nscoord GetPrefISize(gfxContext* aRenderingContext) override;
|
||||
|
||||
mozilla::LogicalSize ComputeAutoSize(
|
||||
virtual mozilla::LogicalSize ComputeAutoSize(
|
||||
gfxContext* aRenderingContext, mozilla::WritingMode aWM,
|
||||
const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize,
|
||||
const mozilla::LogicalSize& aMargin, const mozilla::LogicalSize& aBorder,
|
||||
const mozilla::LogicalSize& aPadding, ComputeSizeFlags aFlags) override;
|
||||
|
||||
void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
|
||||
const ReflowInput& aReflowInput,
|
||||
nsReflowStatus& aStatus) override;
|
||||
virtual void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
|
||||
const ReflowInput& aReflowInput,
|
||||
nsReflowStatus& aStatus) override;
|
||||
|
||||
bool GetVerticalAlignBaseline(mozilla::WritingMode aWM,
|
||||
nscoord* aBaseline) const override {
|
||||
|
@ -99,21 +92,21 @@ class nsTextControlFrame : public nsContainerFrame,
|
|||
return true;
|
||||
}
|
||||
|
||||
nsSize GetXULMinSize(nsBoxLayoutState&) override;
|
||||
bool IsXULCollapsed() override;
|
||||
virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override;
|
||||
virtual bool IsXULCollapsed() override;
|
||||
|
||||
#ifdef ACCESSIBILITY
|
||||
mozilla::a11y::AccType AccessibleType() override;
|
||||
virtual mozilla::a11y::AccType AccessibleType() override;
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_FRAME_DUMP
|
||||
nsresult GetFrameName(nsAString& aResult) const override {
|
||||
virtual nsresult GetFrameName(nsAString& aResult) const override {
|
||||
aResult.AssignLiteral("nsTextControlFrame");
|
||||
return NS_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool IsFrameOfType(uint32_t aFlags) const override {
|
||||
virtual bool IsFrameOfType(uint32_t aFlags) const override {
|
||||
return nsContainerFrame::IsFrameOfType(
|
||||
aFlags & ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
|
||||
}
|
||||
|
@ -126,19 +119,21 @@ class nsTextControlFrame : public nsContainerFrame,
|
|||
#endif
|
||||
|
||||
// nsIAnonymousContentCreator
|
||||
nsresult CreateAnonymousContent(nsTArray<ContentInfo>& aElements) override;
|
||||
void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
|
||||
uint32_t aFilter) override;
|
||||
virtual nsresult CreateAnonymousContent(
|
||||
nsTArray<ContentInfo>& aElements) override;
|
||||
virtual void AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
|
||||
uint32_t aFilter) override;
|
||||
|
||||
void SetInitialChildList(ChildListID, nsFrameList&) override;
|
||||
virtual void SetInitialChildList(ChildListID aListID,
|
||||
nsFrameList& aChildList) override;
|
||||
|
||||
void BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
||||
const nsDisplayListSet& aLists) override;
|
||||
virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
|
||||
const nsDisplayListSet& aLists) override;
|
||||
|
||||
//==== BEGIN NSIFORMCONTROLFRAME
|
||||
void SetFocus(bool aOn, bool aRepaint) override;
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
|
||||
SetFormProperty(nsAtom* aName, const nsAString& aValue) override;
|
||||
virtual void SetFocus(bool aOn, bool aRepaint) override;
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual nsresult SetFormProperty(
|
||||
nsAtom* aName, const nsAString& aValue) override;
|
||||
|
||||
//==== END NSIFORMCONTROLFRAME
|
||||
|
||||
|
@ -151,14 +146,15 @@ class nsTextControlFrame : public nsContainerFrame,
|
|||
SelectionDirection aDirection = eNone) override;
|
||||
NS_IMETHOD GetOwnedSelectionController(
|
||||
nsISelectionController** aSelCon) override;
|
||||
nsFrameSelection* GetOwnedFrameSelection() override;
|
||||
virtual nsFrameSelection* GetOwnedFrameSelection() override;
|
||||
|
||||
/**
|
||||
* Ensure mEditor is initialized with the proper flags and the default value.
|
||||
* @throws NS_ERROR_NOT_INITIALIZED if mEditor has not been created
|
||||
* @throws various and sundry other things
|
||||
*/
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult EnsureEditorInitialized() override;
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual nsresult EnsureEditorInitialized()
|
||||
override;
|
||||
|
||||
//==== END NSITEXTCONTROLFRAME
|
||||
|
||||
|
@ -172,8 +168,8 @@ class nsTextControlFrame : public nsContainerFrame,
|
|||
//==== OVERLOAD of nsIFrame
|
||||
|
||||
/** handler for attribute changes to mContent */
|
||||
nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
|
||||
int32_t aModType) override;
|
||||
virtual nsresult AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
|
||||
int32_t aModType) override;
|
||||
|
||||
void GetText(nsString& aText);
|
||||
|
||||
|
@ -184,7 +180,7 @@ class nsTextControlFrame : public nsContainerFrame,
|
|||
*/
|
||||
bool TextEquals(const nsAString& aText) const;
|
||||
|
||||
nsresult PeekOffset(nsPeekOffsetStruct* aPos) override;
|
||||
virtual nsresult PeekOffset(nsPeekOffsetStruct* aPos) override;
|
||||
|
||||
NS_DECL_QUERYFRAME
|
||||
|
||||
|
@ -197,8 +193,6 @@ class nsTextControlFrame : public nsContainerFrame,
|
|||
nsReflowStatus& aStatus,
|
||||
ReflowOutput& aParentDesiredSize);
|
||||
|
||||
void ComputeBaseline(const ReflowInput&, ReflowOutput&);
|
||||
|
||||
public: // for methods who access nsTextControlFrame directly
|
||||
void SetValueChanged(bool aValueChanged);
|
||||
|
||||
|
@ -206,8 +200,6 @@ class nsTextControlFrame : public nsContainerFrame,
|
|||
|
||||
mozilla::dom::Element* GetPreviewNode() const { return mPreviewDiv; }
|
||||
|
||||
mozilla::dom::Element* GetPlaceholderNode() const { return mPlaceholderDiv; }
|
||||
|
||||
// called by the focus listener
|
||||
nsresult MaybeBeginSecureKeyboardInput();
|
||||
void MaybeEndSecureKeyboardInput();
|
||||
|
@ -343,7 +335,7 @@ class nsTextControlFrame : public nsContainerFrame,
|
|||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
private:
|
||||
class nsAnonDivObserver;
|
||||
|
||||
nsresult CreateRootNode();
|
||||
|
|
|
@ -4813,16 +4813,18 @@ already_AddRefed<Element> ScrollFrameHelper::MakeScrollbar(
|
|||
}
|
||||
|
||||
bool ScrollFrameHelper::IsForTextControlWithNoScrollbars() const {
|
||||
// FIXME(emilio): we should probably make the scroller inside <input> an
|
||||
// internal pseudo-element, and then this would be simpler.
|
||||
//
|
||||
// Also, this could just use scrollbar-width these days.
|
||||
auto* content = mOuter->GetContent();
|
||||
if (!content) {
|
||||
return false;
|
||||
nsIFrame* parent = mOuter->GetParent();
|
||||
// The anonymous <div> used by <inputs> never gets scrollbars.
|
||||
nsITextControlFrame* textFrame = do_QueryFrame(parent);
|
||||
if (textFrame) {
|
||||
// Make sure we are not a text area.
|
||||
HTMLTextAreaElement* textAreaElement =
|
||||
HTMLTextAreaElement::FromNode(parent->GetContent());
|
||||
if (!textAreaElement) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
auto* input = content->GetClosestNativeAnonymousSubtreeRootParent();
|
||||
return input && input->IsHTMLElement(nsGkAtoms::input);
|
||||
return false;
|
||||
}
|
||||
|
||||
nsresult ScrollFrameHelper::CreateAnonymousContent(
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
/* None of these selectors should match from content */
|
||||
input[type=number]::-moz-number-wrapper,
|
||||
input[type=number]::-moz-number-text,
|
||||
input[type=number]::-moz-number-spin-box,
|
||||
input[type=number]::-moz-number-spin-up,
|
||||
input[type=number]::-moz-number-spin-down {
|
||||
|
|
|
@ -67,6 +67,9 @@ CSS_PSEUDO_ELEMENT(mozMathAnonymous, ":-moz-math-anonymous",
|
|||
CSS_PSEUDO_ELEMENT(mozNumberWrapper, ":-moz-number-wrapper",
|
||||
CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
|
||||
CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)
|
||||
CSS_PSEUDO_ELEMENT(mozNumberText, ":-moz-number-text",
|
||||
CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
|
||||
CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)
|
||||
CSS_PSEUDO_ELEMENT(mozNumberSpinBox, ":-moz-number-spin-box",
|
||||
CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE |
|
||||
CSS_PSEUDO_ELEMENT_ENABLED_IN_UA_SHEETS_AND_CHROME)
|
||||
|
|
|
@ -102,7 +102,7 @@ input {
|
|||
overflow-clip-box: padding-box content-box;
|
||||
}
|
||||
|
||||
input .anonymous-div,
|
||||
input > .anonymous-div,
|
||||
input::placeholder {
|
||||
word-wrap: normal;
|
||||
/* Make the line-height equal to the available height */
|
||||
|
@ -144,12 +144,12 @@ textarea > scrollbar {
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
textarea .anonymous-div,
|
||||
input .anonymous-div,
|
||||
textarea > .anonymous-div,
|
||||
input > .anonymous-div,
|
||||
input::placeholder,
|
||||
textarea::placeholder,
|
||||
input .preview-div
|
||||
textarea .preview-div {
|
||||
input > .preview-div
|
||||
textarea > .preview-div {
|
||||
overflow: auto;
|
||||
border: 0px;
|
||||
padding: inherit;
|
||||
|
@ -163,14 +163,14 @@ textarea .preview-div {
|
|||
overflow-clip-box: inherit;
|
||||
}
|
||||
|
||||
input .anonymous-div,
|
||||
input > .anonymous-div,
|
||||
input::placeholder,
|
||||
input .preview-div {
|
||||
input > .preview-div {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
input[type=password] .anonymous-div,
|
||||
input[type=password] .preview-div {
|
||||
input[type=password] > .anonymous-div,
|
||||
input[type=password] > .preview-div {
|
||||
/*
|
||||
* In password fields, any character should be put same direction. Otherwise,
|
||||
* caret position at typing tells everybody whether the character is an RTL
|
||||
|
@ -191,8 +191,8 @@ textarea > .anonymous-div.inherit-overflow {
|
|||
|
||||
input::placeholder,
|
||||
textarea::placeholder,
|
||||
input .preview-div,
|
||||
textarea .preview-div {
|
||||
input > .preview-div,
|
||||
textarea > .preview-div {
|
||||
/*
|
||||
* Changing display to inline can leads to broken behaviour and will assert.
|
||||
*/
|
||||
|
@ -218,7 +218,7 @@ textarea::placeholder {
|
|||
}
|
||||
|
||||
textarea::placeholder,
|
||||
textarea .preview-div {
|
||||
textarea > .preview-div {
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
|
@ -993,12 +993,27 @@ input[type=number]::-moz-number-wrapper {
|
|||
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
|
||||
the style system to ensure this, but that fixup is disabled
|
||||
inside of form controls. So, we hardcode display here. */
|
||||
-moz-appearance: none;
|
||||
/* work around autofocus bug 939248 on initial load */
|
||||
-moz-user-modify: read-write;
|
||||
/* This pseudo-element is also an 'input' element (nested inside and
|
||||
* distinct from the <input type=number> element) so we need to prevent the
|
||||
* explicit setting of 'text-align' by the general CSS rule for 'input'
|
||||
* above. We want to inherit its value from its <input type=number>
|
||||
* ancestor, not have that general CSS rule reset it.
|
||||
*/
|
||||
text-align: unset;
|
||||
text-decoration: inherit;
|
||||
ime-mode: inherit;
|
||||
flex: 1;
|
||||
min-inline-size: 0;
|
||||
padding: unset;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=number]::-moz-number-spin-box {
|
||||
|
|
|
@ -184,7 +184,13 @@ class AutofillDelegateTest : BaseSessionTest() {
|
|||
// Wait on the promises and check for correct values.
|
||||
for ((key, actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
|
||||
assertThat("Auto-filled value must match ($key)", actual, equalTo(expected))
|
||||
assertThat("input event should be dispatched with InputEvent interface", eventInterface, equalTo("InputEvent"))
|
||||
|
||||
// <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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1869,7 +1869,22 @@ impl<'le> ::selectors::Element for GeckoElement<'le> {
|
|||
#[inline]
|
||||
fn pseudo_element_originating_element(&self) -> Option<Self> {
|
||||
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]
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[focus-input-type-switch.html]
|
||||
max-asserts: 4
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=697207
|
|
@ -1,3 +1,7 @@
|
|||
[form-validation-validity-rangeUnderflow.html]
|
||||
[[INPUT in NUMBER status\] The value is less than min(special floating number)]
|
||||
expected: FAIL
|
||||
|
||||
[[INPUT in TIME status\] The time is max for reversed range]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
@ -6,6 +6,3 @@
|
|||
[value = +1]
|
||||
expected: FAIL
|
||||
|
||||
[value ending with '.']
|
||||
expected: FAIL
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1605158
|
||||
|
|
|
@ -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>
|
|
@ -30,7 +30,7 @@ customElements.define(
|
|||
<toolbarbutton id="print-preview-navigateHome" class="print-preview-navigate-button tabbable" oncommand="parentNode.navigate(0, 0, 'home');" data-l10n-id="printpreview-homearrow"/>
|
||||
<toolbarbutton id="print-preview-navigatePrevious" class="print-preview-navigate-button tabbable" oncommand="parentNode.navigate(-1, 0, 0);" data-l10n-id="printpreview-previousarrow"/>
|
||||
<hbox align="center" pack="center">
|
||||
<html:input id="print-preview-pageNumber" 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 id="print-preview-totalPages" value="1"/>
|
||||
</hbox>
|
||||
|
|
|
@ -15,3 +15,20 @@ input[type="number"] {
|
|||
input[type="number"][hidespinbuttons="true"] {
|
||||
-moz-appearance: textfield !important;
|
||||
}
|
||||
|
||||
/* input[type=number] uses display: flex; by default which is incompatible with XUL flexbox
|
||||
Forcing XUL flexbox allows changing the size of the input. */
|
||||
.input-number-mozbox,
|
||||
.input-number-mozbox::-moz-number-wrapper {
|
||||
display: -moz-box;
|
||||
-moz-box-align: center;
|
||||
}
|
||||
|
||||
.input-number-mozbox::-moz-number-wrapper,
|
||||
.input-number-mozbox::-moz-number-text {
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
|
||||
.input-number-mozbox::-moz-number-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,16 @@ EventStates nsNativeTheme::GetContentState(nsIFrame* aFrame,
|
|||
if (frameContent->IsElement()) {
|
||||
flags = frameContent->AsElement()->State();
|
||||
|
||||
// <input type=number> needs special handling since its nested native
|
||||
// anonymous <input type=text> takes focus for it.
|
||||
if (aAppearance == StyleAppearance::NumberInput &&
|
||||
frameContent->IsHTMLElement(nsGkAtoms::input)) {
|
||||
nsNumberControlFrame* numberControlFrame = do_QueryFrame(aFrame);
|
||||
if (numberControlFrame && numberControlFrame->IsFocused()) {
|
||||
flags |= NS_EVENT_STATE_FOCUS;
|
||||
}
|
||||
}
|
||||
|
||||
nsNumberControlFrame* numberControlFrame =
|
||||
nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame);
|
||||
if (numberControlFrame &&
|
||||
|
|
|
@ -2458,6 +2458,7 @@ STATIC_ATOMS = [
|
|||
PseudoElementAtom("PseudoElement_mozFocusOuter", ":-moz-focus-outer"),
|
||||
PseudoElementAtom("PseudoElement_mozMathAnonymous", ":-moz-math-anonymous"),
|
||||
PseudoElementAtom("PseudoElement_mozNumberWrapper", ":-moz-number-wrapper"),
|
||||
PseudoElementAtom("PseudoElement_mozNumberText", ":-moz-number-text"),
|
||||
PseudoElementAtom("PseudoElement_mozNumberSpinBox", ":-moz-number-spin-box"),
|
||||
PseudoElementAtom("PseudoElement_mozNumberSpinUp", ":-moz-number-spin-up"),
|
||||
PseudoElementAtom("PseudoElement_mozNumberSpinDown", ":-moz-number-spin-down"),
|
||||
|
|
Загрузка…
Ссылка в новой задаче