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