1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/dom/HTMLInputElement.h"
8
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/AsyncEventDispatcher.h"
11 #include "mozilla/BasePrincipal.h"
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/Components.h"
14 #include "mozilla/dom/AutocompleteInfoBinding.h"
15 #include "mozilla/dom/BlobImpl.h"
16 #include "mozilla/dom/Directory.h"
17 #include "mozilla/dom/DocumentOrShadowRoot.h"
18 #include "mozilla/dom/ElementBinding.h"
19 #include "mozilla/dom/FileSystemUtils.h"
20 #include "mozilla/dom/GetFilesHelper.h"
21 #include "mozilla/dom/HTMLFormSubmission.h"
22 #include "mozilla/dom/WindowContext.h"
23 #include "mozilla/dom/InputType.h"
24 #include "mozilla/dom/UserActivation.h"
25 #include "mozilla/dom/MutationEventBinding.h"
26 #include "mozilla/dom/WheelEventBinding.h"
27 #include "mozilla/PresShell.h"
28 #include "mozilla/StaticPrefs_dom.h"
29 #include "mozilla/TextUtils.h"
30 #include "nsAttrValueInlines.h"
31 #include "nsCRTGlue.h"
32 #include "nsNetUtil.h"
33 #include "nsQueryObject.h"
34
35 #include "nsIRadioVisitor.h"
36
37 #include "HTMLFormSubmissionConstants.h"
38 #include "mozilla/Telemetry.h"
39 #include "nsBaseCommandController.h"
40 #include "nsIStringBundle.h"
41 #include "nsFocusManager.h"
42 #include "nsColorControlFrame.h"
43 #include "nsNumberControlFrame.h"
44 #include "nsSearchControlFrame.h"
45 #include "nsPIDOMWindow.h"
46 #include "nsRepeatService.h"
47 #include "nsContentCID.h"
48 #include "mozilla/dom/ProgressEvent.h"
49 #include "nsGkAtoms.h"
50 #include "nsStyleConsts.h"
51 #include "nsPresContext.h"
52 #include "nsMappedAttributes.h"
53 #include "nsIFormControl.h"
54 #include "mozilla/dom/Document.h"
55 #include "nsIFormControlFrame.h"
56 #include "nsITextControlFrame.h"
57 #include "nsIFrame.h"
58 #include "nsRangeFrame.h"
59 #include "nsError.h"
60 #include "nsIEditor.h"
61 #include "nsAttrValueOrString.h"
62 #include "nsIPromptCollection.h"
63
64 #include "mozilla/PresState.h"
65 #include "nsLinebreakConverter.h" //to strip out carriage returns
66 #include "nsReadableUtils.h"
67 #include "nsUnicharUtils.h"
68 #include "nsLayoutUtils.h"
69 #include "nsVariant.h"
70
71 #include "mozilla/ContentEvents.h"
72 #include "mozilla/EventDispatcher.h"
73 #include "mozilla/EventStates.h"
74 #include "mozilla/MappedDeclarations.h"
75 #include "mozilla/InternalMutationEvent.h"
76 #include "mozilla/TextControlState.h"
77 #include "mozilla/TextEditor.h"
78 #include "mozilla/TextEvents.h"
79 #include "mozilla/TouchEvents.h"
80
81 #include <algorithm>
82
83 // input type=radio
84 #include "nsIRadioGroupContainer.h"
85
86 // input type=file
87 #include "mozilla/dom/FileSystemEntry.h"
88 #include "mozilla/dom/FileSystem.h"
89 #include "mozilla/dom/File.h"
90 #include "mozilla/dom/FileList.h"
91 #include "nsIFile.h"
92 #include "nsDirectoryServiceDefs.h"
93 #include "nsIContentPrefService2.h"
94 #include "nsIMIMEService.h"
95 #include "nsIObserverService.h"
96 #include "nsGlobalWindow.h"
97
98 // input type=image
99 #include "nsImageLoadingContent.h"
100 #include "imgRequestProxy.h"
101
102 #include "mozAutoDocUpdate.h"
103 #include "nsContentCreatorFunctions.h"
104 #include "nsContentUtils.h"
105 #include "mozilla/dom/DirectionalityUtils.h"
106 #include "nsRadioVisitor.h"
107
108 #include "mozilla/LookAndFeel.h"
109 #include "mozilla/Preferences.h"
110 #include "mozilla/MathAlgorithms.h"
111 #include "mozilla/TextUtils.h"
112
113 #include <limits>
114
115 #include "nsIColorPicker.h"
116 #include "nsIStringEnumerator.h"
117 #include "HTMLSplitOnSpacesTokenizer.h"
118 #include "nsIMIMEInfo.h"
119 #include "nsFrameSelection.h"
120 #include "nsBaseCommandController.h"
121 #include "nsXULControllers.h"
122
123 // input type=date
124 #include "js/Date.h"
125
126 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
127
128 // XXX align=left, hspace, vspace, border? other nav4 attrs
129
130 namespace mozilla::dom {
131
132 // First bits are needed for the control type.
133 #define NS_OUTER_ACTIVATE_EVENT (1 << 9)
134 #define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
135 // (1 << 11 is unused)
136 #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
137 #define NS_PRE_HANDLE_BLUR_EVENT (1 << 13)
138 #define NS_IN_SUBMIT_CLICK (1 << 15)
139 #define NS_CONTROL_TYPE(bits) \
140 ((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | \
141 NS_ORIGINAL_INDETERMINATE_VALUE | NS_PRE_HANDLE_BLUR_EVENT | \
142 NS_IN_SUBMIT_CLICK))
143
144 // whether textfields should be selected once focused:
145 // -1: no, 1: yes, 0: uninitialized
146 static int32_t gSelectTextFieldOnFocus;
147 UploadLastDir* HTMLInputElement::gUploadLastDir;
148
149 static const nsAttrValue::EnumTable kInputTypeTable[] = {
150 {"button", FormControlType::InputButton},
151 {"checkbox", FormControlType::InputCheckbox},
152 {"color", FormControlType::InputColor},
153 {"date", FormControlType::InputDate},
154 {"datetime-local", FormControlType::InputDatetimeLocal},
155 {"email", FormControlType::InputEmail},
156 {"file", FormControlType::InputFile},
157 {"hidden", FormControlType::InputHidden},
158 {"reset", FormControlType::InputReset},
159 {"image", FormControlType::InputImage},
160 {"month", FormControlType::InputMonth},
161 {"number", FormControlType::InputNumber},
162 {"password", FormControlType::InputPassword},
163 {"radio", FormControlType::InputRadio},
164 {"range", FormControlType::InputRange},
165 {"search", FormControlType::InputSearch},
166 {"submit", FormControlType::InputSubmit},
167 {"tel", FormControlType::InputTel},
168 {"time", FormControlType::InputTime},
169 {"url", FormControlType::InputUrl},
170 {"week", FormControlType::InputWeek},
171 // "text" must be last for ParseAttribute to work right. If you add things
172 // before it, please update kInputDefaultType.
173 {"text", FormControlType::InputText},
174 {nullptr, 0}};
175
176 // Default type is 'text'.
177 static const nsAttrValue::EnumTable* kInputDefaultType =
178 &kInputTypeTable[ArrayLength(kInputTypeTable) - 2];
179
180 static const nsAttrValue::EnumTable kCaptureTable[] = {
181 {"user", static_cast<int16_t>(nsIFilePicker::captureUser)},
182 {"environment", static_cast<int16_t>(nsIFilePicker::captureEnv)},
183 {"", static_cast<int16_t>(nsIFilePicker::captureDefault)},
184 {nullptr, static_cast<int16_t>(nsIFilePicker::captureNone)}};
185
186 static const nsAttrValue::EnumTable* kCaptureDefault = &kCaptureTable[2];
187
188 const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000);
189 const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
190 const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
191 const Decimal HTMLInputElement::kStepScaleFactorMonth = Decimal(1);
192 const Decimal HTMLInputElement::kStepScaleFactorWeek = Decimal(7 * 86400000);
193 const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0);
194 const Decimal HTMLInputElement::kDefaultStepBaseWeek = Decimal(-259200000);
195 const Decimal HTMLInputElement::kDefaultStep = Decimal(1);
196 const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60);
197 const Decimal HTMLInputElement::kStepAny = Decimal(0);
198
199 const double HTMLInputElement::kMinimumYear = 1;
200 const double HTMLInputElement::kMaximumYear = 275760;
201 const double HTMLInputElement::kMaximumWeekInMaximumYear = 37;
202 const double HTMLInputElement::kMaximumDayInMaximumYear = 13;
203 const double HTMLInputElement::kMaximumMonthInMaximumYear = 9;
204 const double HTMLInputElement::kMaximumWeekInYear = 53;
205 const double HTMLInputElement::kMsPerDay = 24 * 60 * 60 * 1000;
206
207 // An helper class for the dispatching of the 'change' event.
208 // This class is used when the FilePicker finished its task (or when files and
209 // directories are set by some chrome/test only method).
210 // The task of this class is to postpone the dispatching of 'change' and 'input'
211 // events at the end of the exploration of the directories.
212 class DispatchChangeEventCallback final : public GetFilesCallback {
213 public:
DispatchChangeEventCallback(HTMLInputElement * aInputElement)214 explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement)
215 : mInputElement(aInputElement) {
216 MOZ_ASSERT(aInputElement);
217 }
218
Callback(nsresult aStatus,const FallibleTArray<RefPtr<BlobImpl>> & aBlobImpls)219 virtual void Callback(
220 nsresult aStatus,
221 const FallibleTArray<RefPtr<BlobImpl>>& aBlobImpls) override {
222 if (!mInputElement->GetOwnerGlobal()) {
223 return;
224 }
225
226 nsTArray<OwningFileOrDirectory> array;
227 for (uint32_t i = 0; i < aBlobImpls.Length(); ++i) {
228 OwningFileOrDirectory* element = array.AppendElement();
229 RefPtr<File> file =
230 File::Create(mInputElement->GetOwnerGlobal(), aBlobImpls[i]);
231 if (NS_WARN_IF(!file)) {
232 return;
233 }
234
235 element->SetAsFile() = file;
236 }
237
238 mInputElement->SetFilesOrDirectories(array, true);
239 Unused << NS_WARN_IF(NS_FAILED(DispatchEvents()));
240 }
241
242 MOZ_CAN_RUN_SCRIPT_BOUNDARY
DispatchEvents()243 nsresult DispatchEvents() {
244 RefPtr<HTMLInputElement> inputElement(mInputElement);
245 nsresult rv = nsContentUtils::DispatchInputEvent(inputElement);
246 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch input event");
247
248 rv = nsContentUtils::DispatchTrustedEvent(
249 mInputElement->OwnerDoc(), static_cast<Element*>(mInputElement.get()),
250 u"change"_ns, CanBubble::eYes, Cancelable::eNo);
251
252 return rv;
253 }
254
255 private:
256 RefPtr<HTMLInputElement> mInputElement;
257 };
258
259 struct HTMLInputElement::FileData {
260 /**
261 * The value of the input if it is a file input. This is the list of files or
262 * directories DOM objects used when uploading a file. It is vital that this
263 * is kept separate from mValue so that it won't be possible to 'leak' the
264 * value from a text-input to a file-input. Additionally, the logic for this
265 * value is kept as simple as possible to avoid accidental errors where the
266 * wrong filename is used. Therefor the list of filenames is always owned by
267 * this member, never by the frame. Whenever the frame wants to change the
268 * filename it has to call SetFilesOrDirectories to update this member.
269 */
270 nsTArray<OwningFileOrDirectory> mFilesOrDirectories;
271
272 RefPtr<GetFilesHelper> mGetFilesRecursiveHelper;
273 RefPtr<GetFilesHelper> mGetFilesNonRecursiveHelper;
274
275 /**
276 * Hack for bug 1086684: Stash the .value when we're a file picker.
277 */
278 nsString mFirstFilePath;
279
280 RefPtr<FileList> mFileList;
281 Sequence<RefPtr<FileSystemEntry>> mEntries;
282
283 nsString mStaticDocFileList;
284
ClearGetFilesHelpersmozilla::dom::HTMLInputElement::FileData285 void ClearGetFilesHelpers() {
286 if (mGetFilesRecursiveHelper) {
287 mGetFilesRecursiveHelper->Unlink();
288 mGetFilesRecursiveHelper = nullptr;
289 }
290
291 if (mGetFilesNonRecursiveHelper) {
292 mGetFilesNonRecursiveHelper->Unlink();
293 mGetFilesNonRecursiveHelper = nullptr;
294 }
295 }
296
297 // Cycle Collection support.
Traversemozilla::dom::HTMLInputElement::FileData298 void Traverse(nsCycleCollectionTraversalCallback& cb) {
299 FileData* tmp = this;
300 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories)
301 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
302 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries)
303 if (mGetFilesRecursiveHelper) {
304 mGetFilesRecursiveHelper->Traverse(cb);
305 }
306
307 if (mGetFilesNonRecursiveHelper) {
308 mGetFilesNonRecursiveHelper->Traverse(cb);
309 }
310 }
311
Unlinkmozilla::dom::HTMLInputElement::FileData312 void Unlink() {
313 FileData* tmp = this;
314 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories)
315 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
316 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries)
317 ClearGetFilesHelpers();
318 }
319 };
320
nsFilePickerShownCallback(HTMLInputElement * aInput,nsIFilePicker * aFilePicker)321 HTMLInputElement::nsFilePickerShownCallback::nsFilePickerShownCallback(
322 HTMLInputElement* aInput, nsIFilePicker* aFilePicker)
323 : mFilePicker(aFilePicker), mInput(aInput) {}
324
NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback,nsIContentPrefCallback2)325 NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback, nsIContentPrefCallback2)
326
327 NS_IMETHODIMP
328 UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason) {
329 nsCOMPtr<nsIFile> localFile;
330 nsAutoString prefStr;
331
332 if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || !mResult) {
333 Preferences::GetString("dom.input.fallbackUploadDir", prefStr);
334 }
335
336 if (prefStr.IsEmpty() && mResult) {
337 nsCOMPtr<nsIVariant> pref;
338 mResult->GetValue(getter_AddRefs(pref));
339 pref->GetAsAString(prefStr);
340 }
341
342 if (!prefStr.IsEmpty()) {
343 localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
344 if (localFile && NS_WARN_IF(NS_FAILED(localFile->InitWithPath(prefStr)))) {
345 localFile = nullptr;
346 }
347 }
348
349 if (localFile) {
350 mFilePicker->SetDisplayDirectory(localFile);
351 } else {
352 // If no custom directory was set through the pref, default to
353 // "desktop" directory for each platform.
354 mFilePicker->SetDisplaySpecialDirectory(
355 NS_LITERAL_STRING_FROM_CSTRING(NS_OS_DESKTOP_DIR));
356 }
357
358 mFilePicker->Open(mFpCallback);
359 return NS_OK;
360 }
361
362 NS_IMETHODIMP
HandleResult(nsIContentPref * pref)363 UploadLastDir::ContentPrefCallback::HandleResult(nsIContentPref* pref) {
364 mResult = pref;
365 return NS_OK;
366 }
367
368 NS_IMETHODIMP
HandleError(nsresult error)369 UploadLastDir::ContentPrefCallback::HandleError(nsresult error) {
370 // HandleCompletion is always called (even with HandleError was called),
371 // so we don't need to do anything special here.
372 return NS_OK;
373 }
374
375 namespace {
376
377 /**
378 * This may return nullptr if the DOM File's implementation of
379 * File::mozFullPathInternal does not successfully return a non-empty
380 * string that is a valid path. This can happen on Firefox OS, for example,
381 * where the file picker can create Blobs.
382 */
LastUsedDirectory(const OwningFileOrDirectory & aData)383 static already_AddRefed<nsIFile> LastUsedDirectory(
384 const OwningFileOrDirectory& aData) {
385 if (aData.IsFile()) {
386 nsAutoString path;
387 ErrorResult error;
388 aData.GetAsFile()->GetMozFullPathInternal(path, error);
389 if (error.Failed() || path.IsEmpty()) {
390 error.SuppressException();
391 return nullptr;
392 }
393
394 nsCOMPtr<nsIFile> localFile;
395 nsresult rv = NS_NewLocalFile(path, true, getter_AddRefs(localFile));
396 if (NS_WARN_IF(NS_FAILED(rv))) {
397 return nullptr;
398 }
399
400 nsCOMPtr<nsIFile> parentFile;
401 rv = localFile->GetParent(getter_AddRefs(parentFile));
402 if (NS_WARN_IF(NS_FAILED(rv))) {
403 return nullptr;
404 }
405
406 return parentFile.forget();
407 }
408
409 MOZ_ASSERT(aData.IsDirectory());
410
411 nsCOMPtr<nsIFile> localFile = aData.GetAsDirectory()->GetInternalNsIFile();
412 MOZ_ASSERT(localFile);
413
414 return localFile.forget();
415 }
416
GetDOMFileOrDirectoryName(const OwningFileOrDirectory & aData,nsAString & aName)417 void GetDOMFileOrDirectoryName(const OwningFileOrDirectory& aData,
418 nsAString& aName) {
419 if (aData.IsFile()) {
420 aData.GetAsFile()->GetName(aName);
421 } else {
422 MOZ_ASSERT(aData.IsDirectory());
423 ErrorResult rv;
424 aData.GetAsDirectory()->GetName(aName, rv);
425 if (NS_WARN_IF(rv.Failed())) {
426 rv.SuppressException();
427 }
428 }
429 }
430
GetDOMFileOrDirectoryPath(const OwningFileOrDirectory & aData,nsAString & aPath,ErrorResult & aRv)431 void GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData,
432 nsAString& aPath, ErrorResult& aRv) {
433 if (aData.IsFile()) {
434 aData.GetAsFile()->GetMozFullPathInternal(aPath, aRv);
435 } else {
436 MOZ_ASSERT(aData.IsDirectory());
437 aData.GetAsDirectory()->GetFullRealPath(aPath);
438 }
439 }
440
441 } // namespace
442
443 NS_IMETHODIMP
Done(int16_t aResult)444 HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult) {
445 mInput->PickerClosed();
446
447 if (aResult == nsIFilePicker::returnCancel) {
448 RefPtr<HTMLInputElement> inputElement(mInput);
449 return nsContentUtils::DispatchTrustedEvent(
450 inputElement->OwnerDoc(), static_cast<Element*>(inputElement.get()),
451 u"cancel"_ns, CanBubble::eYes, Cancelable::eNo);
452 }
453
454 int16_t mode;
455 mFilePicker->GetMode(&mode);
456
457 // Collect new selected filenames
458 nsTArray<OwningFileOrDirectory> newFilesOrDirectories;
459 if (mode == static_cast<int16_t>(nsIFilePicker::modeOpenMultiple)) {
460 nsCOMPtr<nsISimpleEnumerator> iter;
461 nsresult rv =
462 mFilePicker->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter));
463 NS_ENSURE_SUCCESS(rv, rv);
464
465 if (!iter) {
466 return NS_OK;
467 }
468
469 nsCOMPtr<nsISupports> tmp;
470 bool hasMore = true;
471
472 while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
473 iter->GetNext(getter_AddRefs(tmp));
474 RefPtr<Blob> domBlob = do_QueryObject(tmp);
475 MOZ_ASSERT(domBlob,
476 "Null file object from FilePicker's file enumerator?");
477 if (!domBlob) {
478 continue;
479 }
480
481 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
482 element->SetAsFile() = domBlob->ToFile();
483 }
484 } else {
485 MOZ_ASSERT(mode == static_cast<int16_t>(nsIFilePicker::modeOpen) ||
486 mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder));
487 nsCOMPtr<nsISupports> tmp;
488 nsresult rv = mFilePicker->GetDomFileOrDirectory(getter_AddRefs(tmp));
489 NS_ENSURE_SUCCESS(rv, rv);
490
491 // Show a prompt to get user confirmation before allowing folder access.
492 // This is to prevent sites from tricking the user into uploading files.
493 // See Bug 1338637.
494 if (mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder)) {
495 nsCOMPtr<nsIPromptCollection> prompter =
496 do_GetService("@mozilla.org/embedcomp/prompt-collection;1");
497 if (!prompter) {
498 return NS_ERROR_NOT_AVAILABLE;
499 }
500
501 bool confirmed = false;
502 BrowsingContext* bc = mInput->OwnerDoc()->GetBrowsingContext();
503
504 // Get directory name
505 RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
506 nsAutoString directoryName;
507 ErrorResult error;
508 directory->GetName(directoryName, error);
509 if (NS_WARN_IF(error.Failed())) {
510 return error.StealNSResult();
511 }
512
513 rv = prompter->ConfirmFolderUpload(bc, directoryName, &confirmed);
514 NS_ENSURE_SUCCESS(rv, rv);
515 if (!confirmed) {
516 // User aborted upload
517 return NS_OK;
518 }
519 }
520
521 RefPtr<Blob> blob = do_QueryObject(tmp);
522 if (blob) {
523 RefPtr<File> file = blob->ToFile();
524 MOZ_ASSERT(file);
525
526 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
527 element->SetAsFile() = file;
528 } else if (tmp) {
529 RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
530 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
531 element->SetAsDirectory() = directory;
532 }
533 }
534
535 if (newFilesOrDirectories.IsEmpty()) {
536 return NS_OK;
537 }
538
539 // Store the last used directory using the content pref service:
540 nsCOMPtr<nsIFile> lastUsedDir = LastUsedDirectory(newFilesOrDirectories[0]);
541
542 if (lastUsedDir) {
543 HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(mInput->OwnerDoc(),
544 lastUsedDir);
545 }
546
547 // The text control frame (if there is one) isn't going to send a change
548 // event because it will think this is done by a script.
549 // So, we can safely send one by ourself.
550 mInput->SetFilesOrDirectories(newFilesOrDirectories, true);
551
552 // mInput(HTMLInputElement) has no scriptGlobalObject, don't create
553 // DispatchChangeEventCallback
554 if (!mInput->GetOwnerGlobal()) {
555 return NS_OK;
556 }
557 RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
558 new DispatchChangeEventCallback(mInput);
559
560 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
561 mInput->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
562 ErrorResult error;
563 GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
564 if (NS_WARN_IF(error.Failed())) {
565 return error.StealNSResult();
566 }
567
568 helper->AddCallback(dispatchChangeEventCallback);
569 return NS_OK;
570 }
571
572 return dispatchChangeEventCallback->DispatchEvents();
573 }
574
575 NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback,
576 nsIFilePickerShownCallback)
577
578 class nsColorPickerShownCallback final : public nsIColorPickerShownCallback {
579 ~nsColorPickerShownCallback() = default;
580
581 public:
nsColorPickerShownCallback(HTMLInputElement * aInput,nsIColorPicker * aColorPicker)582 nsColorPickerShownCallback(HTMLInputElement* aInput,
583 nsIColorPicker* aColorPicker)
584 : mInput(aInput), mColorPicker(aColorPicker), mValueChanged(false) {}
585
586 NS_DECL_ISUPPORTS
587
588 MOZ_CAN_RUN_SCRIPT_BOUNDARY
589 NS_IMETHOD Update(const nsAString& aColor) override;
590 MOZ_CAN_RUN_SCRIPT_BOUNDARY
591 NS_IMETHOD Done(const nsAString& aColor) override;
592
593 private:
594 /**
595 * Updates the internals of the object using aColor as the new value.
596 * If aTrustedUpdate is true, it will consider that aColor is a new value.
597 * Otherwise, it will check that aColor is different from the current value.
598 */
599 MOZ_CAN_RUN_SCRIPT
600 nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
601
602 RefPtr<HTMLInputElement> mInput;
603 nsCOMPtr<nsIColorPicker> mColorPicker;
604 bool mValueChanged;
605 };
606
UpdateInternal(const nsAString & aColor,bool aTrustedUpdate)607 nsresult nsColorPickerShownCallback::UpdateInternal(const nsAString& aColor,
608 bool aTrustedUpdate) {
609 bool valueChanged = false;
610
611 nsAutoString oldValue;
612 if (aTrustedUpdate) {
613 valueChanged = true;
614 } else {
615 mInput->GetValue(oldValue, CallerType::System);
616 }
617
618 mInput->SetValue(aColor, CallerType::System, IgnoreErrors());
619
620 if (!aTrustedUpdate) {
621 nsAutoString newValue;
622 mInput->GetValue(newValue, CallerType::System);
623 if (!oldValue.Equals(newValue)) {
624 valueChanged = true;
625 }
626 }
627
628 if (!valueChanged) {
629 return NS_OK;
630 }
631
632 mValueChanged = true;
633 RefPtr<HTMLInputElement> input(mInput);
634 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(input);
635 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
636 "Failed to dispatch input event");
637 return NS_OK;
638 }
639
640 NS_IMETHODIMP
Update(const nsAString & aColor)641 nsColorPickerShownCallback::Update(const nsAString& aColor) {
642 return UpdateInternal(aColor, true);
643 }
644
645 NS_IMETHODIMP
Done(const nsAString & aColor)646 nsColorPickerShownCallback::Done(const nsAString& aColor) {
647 /**
648 * When Done() is called, we might be at the end of a serie of Update() calls
649 * in which case mValueChanged is set to true and a change event will have to
650 * be fired but we might also be in a one shot Done() call situation in which
651 * case we should fire a change event iif the value actually changed.
652 * UpdateInternal(bool) is taking care of that logic for us.
653 */
654 nsresult rv = NS_OK;
655
656 mInput->PickerClosed();
657
658 if (!aColor.IsEmpty()) {
659 UpdateInternal(aColor, false);
660 }
661
662 if (mValueChanged) {
663 rv = nsContentUtils::DispatchTrustedEvent(
664 mInput->OwnerDoc(), static_cast<Element*>(mInput.get()), u"change"_ns,
665 CanBubble::eYes, Cancelable::eNo);
666 }
667
668 return rv;
669 }
670
NS_IMPL_ISUPPORTS(nsColorPickerShownCallback,nsIColorPickerShownCallback)671 NS_IMPL_ISUPPORTS(nsColorPickerShownCallback, nsIColorPickerShownCallback)
672
673 static bool IsPopupBlocked(Document* aDoc) {
674 if (aDoc->ConsumeTransientUserGestureActivation()) {
675 return false;
676 }
677
678 WindowContext* wc = aDoc->GetWindowContext();
679 if (wc && wc->CanShowPopup()) {
680 return false;
681 }
682
683 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, aDoc,
684 nsContentUtils::eDOM_PROPERTIES,
685 "InputPickerBlockedNoUserActivation");
686 return true;
687 }
688
InitColorPicker()689 nsresult HTMLInputElement::InitColorPicker() {
690 if (mPickerRunning) {
691 NS_WARNING("Just one nsIColorPicker is allowed");
692 return NS_ERROR_FAILURE;
693 }
694
695 nsCOMPtr<Document> doc = OwnerDoc();
696
697 nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
698 if (!win) {
699 return NS_ERROR_FAILURE;
700 }
701
702 if (IsPopupBlocked(doc)) {
703 return NS_OK;
704 }
705
706 // Get Loc title
707 nsAutoString title;
708 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
709 "ColorPicker", title);
710
711 nsCOMPtr<nsIColorPicker> colorPicker =
712 do_CreateInstance("@mozilla.org/colorpicker;1");
713 if (!colorPicker) {
714 return NS_ERROR_FAILURE;
715 }
716
717 nsAutoString initialValue;
718 GetNonFileValueInternal(initialValue);
719 nsresult rv = colorPicker->Init(win, title, initialValue);
720 NS_ENSURE_SUCCESS(rv, rv);
721
722 nsCOMPtr<nsIColorPickerShownCallback> callback =
723 new nsColorPickerShownCallback(this, colorPicker);
724
725 rv = colorPicker->Open(callback);
726 if (NS_SUCCEEDED(rv)) {
727 mPickerRunning = true;
728 }
729
730 return rv;
731 }
732
InitFilePicker(FilePickerType aType)733 nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
734 if (mPickerRunning) {
735 NS_WARNING("Just one nsIFilePicker is allowed");
736 return NS_ERROR_FAILURE;
737 }
738
739 // Get parent nsPIDOMWindow object.
740 nsCOMPtr<Document> doc = OwnerDoc();
741
742 nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
743 if (!win) {
744 return NS_ERROR_FAILURE;
745 }
746
747 if (IsPopupBlocked(doc)) {
748 return NS_OK;
749 }
750
751 // Get Loc title
752 nsAutoString title;
753 nsAutoString okButtonLabel;
754 if (aType == FILE_PICKER_DIRECTORY) {
755 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
756 "DirectoryUpload", OwnerDoc(),
757 title);
758
759 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
760 "DirectoryPickerOkButtonLabel",
761 OwnerDoc(), okButtonLabel);
762 } else {
763 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
764 "FileUpload", OwnerDoc(), title);
765 }
766
767 nsCOMPtr<nsIFilePicker> filePicker =
768 do_CreateInstance("@mozilla.org/filepicker;1");
769 if (!filePicker) return NS_ERROR_FAILURE;
770
771 int16_t mode;
772
773 if (aType == FILE_PICKER_DIRECTORY) {
774 mode = static_cast<int16_t>(nsIFilePicker::modeGetFolder);
775 } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
776 mode = static_cast<int16_t>(nsIFilePicker::modeOpenMultiple);
777 } else {
778 mode = static_cast<int16_t>(nsIFilePicker::modeOpen);
779 }
780
781 nsresult rv = filePicker->Init(win, title, mode);
782 NS_ENSURE_SUCCESS(rv, rv);
783
784 if (!okButtonLabel.IsEmpty()) {
785 filePicker->SetOkButtonLabel(okButtonLabel);
786 }
787
788 // Native directory pickers ignore file type filters, so we don't spend
789 // cycles adding them for FILE_PICKER_DIRECTORY.
790 if (HasAttr(kNameSpaceID_None, nsGkAtoms::accept) &&
791 aType != FILE_PICKER_DIRECTORY) {
792 SetFilePickerFiltersFromAccept(filePicker);
793
794 if (StaticPrefs::dom_capture_enabled()) {
795 const nsAttrValue* captureVal =
796 GetParsedAttr(nsGkAtoms::capture, kNameSpaceID_None);
797 if (captureVal) {
798 filePicker->SetCapture(captureVal->GetEnumValue());
799 }
800 }
801 } else {
802 filePicker->AppendFilters(nsIFilePicker::filterAll);
803 }
804
805 // Set default directory and filename
806 nsAutoString defaultName;
807
808 const nsTArray<OwningFileOrDirectory>& oldFiles =
809 GetFilesOrDirectoriesInternal();
810
811 nsCOMPtr<nsIFilePickerShownCallback> callback =
812 new HTMLInputElement::nsFilePickerShownCallback(this, filePicker);
813
814 if (!oldFiles.IsEmpty() && aType != FILE_PICKER_DIRECTORY) {
815 nsAutoString path;
816
817 nsCOMPtr<nsIFile> parentFile = LastUsedDirectory(oldFiles[0]);
818 if (parentFile) {
819 filePicker->SetDisplayDirectory(parentFile);
820 }
821
822 // Unfortunately nsIFilePicker doesn't allow multiple files to be
823 // default-selected, so only select something by default if exactly
824 // one file was selected before.
825 if (oldFiles.Length() == 1) {
826 nsAutoString leafName;
827 GetDOMFileOrDirectoryName(oldFiles[0], leafName);
828
829 if (!leafName.IsEmpty()) {
830 filePicker->SetDefaultString(leafName);
831 }
832 }
833
834 rv = filePicker->Open(callback);
835 if (NS_SUCCEEDED(rv)) {
836 mPickerRunning = true;
837 }
838
839 return rv;
840 }
841
842 HTMLInputElement::gUploadLastDir->FetchDirectoryAndDisplayPicker(
843 doc, filePicker, callback);
844 mPickerRunning = true;
845 return NS_OK;
846 }
847
848 #define CPS_PREF_NAME u"browser.upload.lastDir"_ns
849
NS_IMPL_ISUPPORTS(UploadLastDir,nsIObserver,nsISupportsWeakReference)850 NS_IMPL_ISUPPORTS(UploadLastDir, nsIObserver, nsISupportsWeakReference)
851
852 void HTMLInputElement::InitUploadLastDir() {
853 gUploadLastDir = new UploadLastDir();
854 NS_ADDREF(gUploadLastDir);
855
856 nsCOMPtr<nsIObserverService> observerService =
857 mozilla::services::GetObserverService();
858 if (observerService && gUploadLastDir) {
859 observerService->AddObserver(gUploadLastDir,
860 "browser:purge-session-history", true);
861 }
862 }
863
DestroyUploadLastDir()864 void HTMLInputElement::DestroyUploadLastDir() { NS_IF_RELEASE(gUploadLastDir); }
865
FetchDirectoryAndDisplayPicker(Document * aDoc,nsIFilePicker * aFilePicker,nsIFilePickerShownCallback * aFpCallback)866 nsresult UploadLastDir::FetchDirectoryAndDisplayPicker(
867 Document* aDoc, nsIFilePicker* aFilePicker,
868 nsIFilePickerShownCallback* aFpCallback) {
869 MOZ_ASSERT(aDoc, "aDoc is null");
870 MOZ_ASSERT(aFilePicker, "aFilePicker is null");
871 MOZ_ASSERT(aFpCallback, "aFpCallback is null");
872
873 nsIURI* docURI = aDoc->GetDocumentURI();
874 MOZ_ASSERT(docURI, "docURI is null");
875
876 nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
877 nsCOMPtr<nsIContentPrefCallback2> prefCallback =
878 new UploadLastDir::ContentPrefCallback(aFilePicker, aFpCallback);
879
880 // Attempt to get the CPS, if it's not present we'll fallback to use the
881 // Desktop folder
882 nsCOMPtr<nsIContentPrefService2> contentPrefService =
883 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
884 if (!contentPrefService) {
885 prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
886 return NS_OK;
887 }
888
889 nsAutoCString cstrSpec;
890 docURI->GetSpec(cstrSpec);
891 NS_ConvertUTF8toUTF16 spec(cstrSpec);
892
893 contentPrefService->GetByDomainAndName(spec, CPS_PREF_NAME, loadContext,
894 prefCallback);
895 return NS_OK;
896 }
897
StoreLastUsedDirectory(Document * aDoc,nsIFile * aDir)898 nsresult UploadLastDir::StoreLastUsedDirectory(Document* aDoc, nsIFile* aDir) {
899 MOZ_ASSERT(aDoc, "aDoc is null");
900 if (!aDir) {
901 return NS_OK;
902 }
903
904 nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
905 MOZ_ASSERT(docURI, "docURI is null");
906
907 // Attempt to get the CPS, if it's not present we'll just return
908 nsCOMPtr<nsIContentPrefService2> contentPrefService =
909 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
910 if (!contentPrefService) return NS_ERROR_NOT_AVAILABLE;
911
912 nsAutoCString cstrSpec;
913 docURI->GetSpec(cstrSpec);
914 NS_ConvertUTF8toUTF16 spec(cstrSpec);
915
916 // Find the parent of aFile, and store it
917 nsString unicodePath;
918 aDir->GetPath(unicodePath);
919 if (unicodePath.IsEmpty()) // nothing to do
920 return NS_OK;
921 RefPtr<nsVariantCC> prefValue = new nsVariantCC();
922 prefValue->SetAsAString(unicodePath);
923
924 // Use the document's current load context to ensure that the content pref
925 // service doesn't persistently store this directory for this domain if the
926 // user is using private browsing:
927 nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
928 return contentPrefService->Set(spec, CPS_PREF_NAME, prefValue, loadContext,
929 nullptr);
930 }
931
932 NS_IMETHODIMP
Observe(nsISupports * aSubject,char const * aTopic,char16_t const * aData)933 UploadLastDir::Observe(nsISupports* aSubject, char const* aTopic,
934 char16_t const* aData) {
935 if (strcmp(aTopic, "browser:purge-session-history") == 0) {
936 nsCOMPtr<nsIContentPrefService2> contentPrefService =
937 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
938 if (contentPrefService)
939 contentPrefService->RemoveByName(CPS_PREF_NAME, nullptr, nullptr);
940 }
941 return NS_OK;
942 }
943
944 #ifdef ACCESSIBILITY
945 // Helper method
946 static nsresult FireEventForAccessibility(HTMLInputElement* aTarget,
947 EventMessage aEventMessage);
948 #endif
949
950 //
951 // construction, destruction
952 //
953
HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo,FromParser aFromParser,FromClone aFromClone)954 HTMLInputElement::HTMLInputElement(
955 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
956 FromParser aFromParser, FromClone aFromClone)
957 : TextControlElement(std::move(aNodeInfo), aFromParser,
958 FormControlType(kInputDefaultType->value)),
959 mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown),
960 mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown),
961 mDisabledChanged(false),
962 mValueChanged(false),
963 mLastValueChangeWasInteractive(false),
964 mCheckedChanged(false),
965 mChecked(false),
966 mHandlingSelectEvent(false),
967 mShouldInitChecked(false),
968 mDoneCreating(aFromParser == NOT_FROM_PARSER &&
969 aFromClone == FromClone::no),
970 mInInternalActivate(false),
971 mCheckedIsToggled(false),
972 mIndeterminate(false),
973 mInhibitRestoration(aFromParser & FROM_PARSER_FRAGMENT),
974 mCanShowValidUI(true),
975 mCanShowInvalidUI(true),
976 mHasRange(false),
977 mIsDraggingRange(false),
978 mNumberControlSpinnerIsSpinning(false),
979 mNumberControlSpinnerSpinsUp(false),
980 mPickerRunning(false),
981 mIsPreviewEnabled(false),
982 mHasBeenTypePassword(false),
983 mHasPatternAttribute(false) {
984 // If size is above 512, mozjemalloc allocates 1kB, see
985 // memory/build/mozjemalloc.cpp
986 static_assert(sizeof(HTMLInputElement) <= 512,
987 "Keep the size of HTMLInputElement under 512 to avoid "
988 "performance regression!");
989
990 // We are in a type=text so we now we currenty need a TextControlState.
991 mInputData.mState = TextControlState::Construct(this);
992
993 void* memory = mInputTypeMem;
994 mInputType = InputType::Create(this, mType, memory);
995
996 if (!gUploadLastDir) HTMLInputElement::InitUploadLastDir();
997
998 // Set up our default state. By default we're enabled (since we're
999 // a control type that can be disabled but not actually disabled
1000 // right now), optional, and valid. We are NOT readwrite by default
1001 // until someone calls UpdateEditableState on us, apparently! Also
1002 // by default we don't have to show validity UI and so forth.
1003 AddStatesSilently(NS_EVENT_STATE_ENABLED | NS_EVENT_STATE_OPTIONAL |
1004 NS_EVENT_STATE_VALID);
1005 UpdateApzAwareFlag();
1006 }
1007
~HTMLInputElement()1008 HTMLInputElement::~HTMLInputElement() {
1009 if (mNumberControlSpinnerIsSpinning) {
1010 StopNumberControlSpinnerSpin(eDisallowDispatchingEvents);
1011 }
1012 nsImageLoadingContent::Destroy();
1013 FreeData();
1014 }
1015
FreeData()1016 void HTMLInputElement::FreeData() {
1017 if (!IsSingleLineTextControl(false)) {
1018 free(mInputData.mValue);
1019 mInputData.mValue = nullptr;
1020 } else {
1021 UnbindFromFrame(nullptr);
1022 mInputData.mState->Destroy();
1023 mInputData.mState = nullptr;
1024 }
1025
1026 if (mInputType) {
1027 mInputType->DropReference();
1028 mInputType = nullptr;
1029 }
1030 }
1031
GetEditorState() const1032 TextControlState* HTMLInputElement::GetEditorState() const {
1033 if (!IsSingleLineTextControl(false)) {
1034 return nullptr;
1035 }
1036
1037 MOZ_ASSERT(mInputData.mState,
1038 "Single line text controls need to have a state"
1039 " associated with them");
1040
1041 return mInputData.mState;
1042 }
1043
1044 // nsISupports
1045
1046 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement)
1047
1048 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
1049 TextControlElement)
1050 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
1051 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
1052 if (tmp->IsSingleLineTextControl(false)) {
1053 tmp->mInputData.mState->Traverse(cb);
1054 }
1055
1056 if (tmp->mFileData) {
1057 tmp->mFileData->Traverse(cb);
1058 }
1059 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1060
1061 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
1062 TextControlElement)
1063 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
1064 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
1065 if (tmp->IsSingleLineTextControl(false)) {
1066 tmp->mInputData.mState->Unlink();
1067 }
1068
1069 if (tmp->mFileData) {
1070 tmp->mFileData->Unlink();
1071 }
1072 // XXX should unlink more?
1073 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1074
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLInputElement,TextControlElement,imgINotificationObserver,nsIImageLoadingContent,nsIConstraintValidation)1075 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLInputElement,
1076 TextControlElement,
1077 imgINotificationObserver,
1078 nsIImageLoadingContent,
1079 nsIConstraintValidation)
1080
1081 // nsINode
1082
1083 nsresult HTMLInputElement::Clone(dom::NodeInfo* aNodeInfo,
1084 nsINode** aResult) const {
1085 *aResult = nullptr;
1086
1087 RefPtr<HTMLInputElement> it = new (aNodeInfo->NodeInfoManager())
1088 HTMLInputElement(do_AddRef(aNodeInfo), NOT_FROM_PARSER, FromClone::yes);
1089
1090 nsresult rv = const_cast<HTMLInputElement*>(this)->CopyInnerTo(it);
1091 NS_ENSURE_SUCCESS(rv, rv);
1092
1093 switch (GetValueMode()) {
1094 case VALUE_MODE_VALUE:
1095 if (mValueChanged) {
1096 // We don't have our default value anymore. Set our value on
1097 // the clone.
1098 nsAutoString value;
1099 GetNonFileValueInternal(value);
1100 // SetValueInternal handles setting the VALUE_CHANGED bit for us
1101 if (NS_WARN_IF(
1102 NS_FAILED(rv = it->SetValueInternal(
1103 value, {ValueSetterOption::SetValueChanged})))) {
1104 return rv;
1105 }
1106 }
1107 break;
1108 case VALUE_MODE_FILENAME:
1109 if (it->OwnerDoc()->IsStaticDocument()) {
1110 // We're going to be used in print preview. Since the doc is static
1111 // we can just grab the pretty string and use it as wallpaper
1112 GetDisplayFileName(it->mFileData->mStaticDocFileList);
1113 } else {
1114 it->mFileData->ClearGetFilesHelpers();
1115 it->mFileData->mFilesOrDirectories.Clear();
1116 it->mFileData->mFilesOrDirectories.AppendElements(
1117 mFileData->mFilesOrDirectories);
1118 }
1119 break;
1120 case VALUE_MODE_DEFAULT_ON:
1121 case VALUE_MODE_DEFAULT:
1122 break;
1123 }
1124
1125 if (mCheckedChanged) {
1126 // We no longer have our original checked state. Set our
1127 // checked state on the clone.
1128 it->DoSetChecked(mChecked, false, true);
1129 // Then tell DoneCreatingElement() not to overwrite:
1130 it->mShouldInitChecked = false;
1131 }
1132
1133 it->DoneCreatingElement();
1134
1135 it->SetLastValueChangeWasInteractive(mLastValueChangeWasInteractive);
1136 it.forget(aResult);
1137 return NS_OK;
1138 }
1139
BeforeSetAttr(int32_t aNameSpaceID,nsAtom * aName,const nsAttrValueOrString * aValue,bool aNotify)1140 nsresult HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1141 const nsAttrValueOrString* aValue,
1142 bool aNotify) {
1143 if (aNameSpaceID == kNameSpaceID_None) {
1144 if (aNotify && aName == nsGkAtoms::disabled) {
1145 mDisabledChanged = true;
1146 }
1147
1148 // When name or type changes, radio should be removed from radio group.
1149 // If we are not done creating the radio, we also should not do it.
1150 if (mType == FormControlType::InputRadio) {
1151 if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
1152 (mForm || mDoneCreating)) {
1153 WillRemoveFromRadioGroup();
1154 } else if (aName == nsGkAtoms::required) {
1155 nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
1156
1157 if (container && ((aValue && !HasAttr(aNameSpaceID, aName)) ||
1158 (!aValue && HasAttr(aNameSpaceID, aName)))) {
1159 nsAutoString name;
1160 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
1161 container->RadioRequiredWillChange(name, !!aValue);
1162 }
1163 }
1164 }
1165
1166 if (aName == nsGkAtoms::webkitdirectory) {
1167 Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED, true);
1168 }
1169 }
1170
1171 return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
1172 aValue, aNotify);
1173 }
1174
AfterSetAttr(int32_t aNameSpaceID,nsAtom * aName,const nsAttrValue * aValue,const nsAttrValue * aOldValue,nsIPrincipal * aSubjectPrincipal,bool aNotify)1175 nsresult HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1176 const nsAttrValue* aValue,
1177 const nsAttrValue* aOldValue,
1178 nsIPrincipal* aSubjectPrincipal,
1179 bool aNotify) {
1180 if (aNameSpaceID == kNameSpaceID_None) {
1181 if (aName == nsGkAtoms::src) {
1182 mSrcTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
1183 this, aValue ? aValue->GetStringValue() : EmptyString(),
1184 aSubjectPrincipal);
1185 if (aNotify && mType == FormControlType::InputImage) {
1186 if (aValue) {
1187 // Mark channel as urgent-start before load image if the image load is
1188 // initiated by a user interaction.
1189 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
1190
1191 LoadImage(aValue->GetStringValue(), true, aNotify,
1192 eImageLoadType_Normal, mSrcTriggeringPrincipal);
1193 } else {
1194 // Null value means the attr got unset; drop the image
1195 CancelImageRequests(aNotify);
1196 }
1197 }
1198 }
1199
1200 // If @value is changed and BF_VALUE_CHANGED is false, @value is the value
1201 // of the element so, if the value of the element is different than @value,
1202 // we have to re-set it. This is only the case when GetValueMode() returns
1203 // VALUE_MODE_VALUE.
1204 if (aName == nsGkAtoms::value) {
1205 if (!mValueChanged && GetValueMode() == VALUE_MODE_VALUE) {
1206 SetDefaultValueAsValue();
1207 }
1208 // GetStepBase() depends on the `value` attribute if `min` is not present,
1209 // even if the value doesn't change.
1210 UpdateStepMismatchValidityState();
1211 }
1212
1213 //
1214 // Checked must be set no matter what type of control it is, since
1215 // mChecked must reflect the new value
1216 if (aName == nsGkAtoms::checked && !mCheckedChanged) {
1217 // Delay setting checked if we are creating this element (wait
1218 // until everything is set)
1219 if (!mDoneCreating) {
1220 mShouldInitChecked = true;
1221 } else {
1222 DoSetChecked(DefaultChecked(), true, false);
1223 }
1224 }
1225
1226 if (aName == nsGkAtoms::type) {
1227 FormControlType newType;
1228 if (!aValue) {
1229 // We're now a text input.
1230 newType = FormControlType(kInputDefaultType->value);
1231 } else {
1232 newType = FormControlType(aValue->GetEnumValue());
1233 }
1234 if (newType != mType) {
1235 HandleTypeChange(newType, aNotify);
1236 }
1237 }
1238
1239 // When name or type changes, radio should be added to radio group.
1240 // If we are not done creating the radio, we also should not do it.
1241 if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
1242 mType == FormControlType::InputRadio && (mForm || mDoneCreating)) {
1243 AddedToRadioGroup();
1244 UpdateValueMissingValidityStateForRadio(false);
1245 }
1246
1247 if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
1248 aName == nsGkAtoms::readonly) {
1249 if (aName == nsGkAtoms::disabled) {
1250 // This *has* to be called *before* validity state check because
1251 // UpdateBarredFromConstraintValidation and
1252 // UpdateValueMissingValidityState depend on our disabled state.
1253 UpdateDisabledState(aNotify);
1254 }
1255
1256 if (aName == nsGkAtoms::required && DoesRequiredApply()) {
1257 // This *has* to be called *before* UpdateValueMissingValidityState
1258 // because UpdateValueMissingValidityState depends on our required
1259 // state.
1260 UpdateRequiredState(!!aValue, aNotify);
1261 }
1262
1263 UpdateValueMissingValidityState();
1264
1265 // This *has* to be called *after* validity has changed.
1266 if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
1267 UpdateBarredFromConstraintValidation();
1268 }
1269 } else if (aName == nsGkAtoms::maxlength) {
1270 UpdateTooLongValidityState();
1271 } else if (aName == nsGkAtoms::minlength) {
1272 UpdateTooShortValidityState();
1273 } else if (aName == nsGkAtoms::pattern) {
1274 // Although pattern attribute only applies to single line text controls,
1275 // we set this flag for all input types to save having to check the type
1276 // here.
1277 mHasPatternAttribute = !!aValue;
1278
1279 if (mDoneCreating) {
1280 UpdatePatternMismatchValidityState();
1281 }
1282 } else if (aName == nsGkAtoms::multiple) {
1283 UpdateTypeMismatchValidityState();
1284 } else if (aName == nsGkAtoms::max) {
1285 UpdateHasRange();
1286 nsresult rv = mInputType->MinMaxStepAttrChanged();
1287 NS_ENSURE_SUCCESS(rv, rv);
1288 // Validity state must be updated *after* the UpdateValueDueToAttrChange
1289 // call above or else the following assert will not be valid.
1290 // We don't assert the state of underflow during creation since
1291 // DoneCreatingElement sanitizes.
1292 UpdateRangeOverflowValidityState();
1293 MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1294 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1295 "HTML5 spec does not allow underflow for type=range");
1296 } else if (aName == nsGkAtoms::min) {
1297 UpdateHasRange();
1298 nsresult rv = mInputType->MinMaxStepAttrChanged();
1299 NS_ENSURE_SUCCESS(rv, rv);
1300 // See corresponding @max comment
1301 UpdateRangeUnderflowValidityState();
1302 UpdateStepMismatchValidityState();
1303 MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1304 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1305 "HTML5 spec does not allow underflow for type=range");
1306 } else if (aName == nsGkAtoms::step) {
1307 nsresult rv = mInputType->MinMaxStepAttrChanged();
1308 NS_ENSURE_SUCCESS(rv, rv);
1309 // See corresponding @max comment
1310 UpdateStepMismatchValidityState();
1311 MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1312 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1313 "HTML5 spec does not allow underflow for type=range");
1314 } else if (aName == nsGkAtoms::dir && aValue &&
1315 aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
1316 SetDirectionFromValue(aNotify);
1317 } else if (aName == nsGkAtoms::lang) {
1318 // FIXME(emilio, bug 1651070): This doesn't account for lang changes on
1319 // ancestors.
1320 if (mType == FormControlType::InputNumber) {
1321 // The validity of our value may have changed based on the locale.
1322 UpdateValidityState();
1323 }
1324 } else if (aName == nsGkAtoms::autocomplete) {
1325 // Clear the cached @autocomplete attribute and autocompleteInfo state.
1326 mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
1327 mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown;
1328 } else if (aName == nsGkAtoms::placeholder) {
1329 // Full addition / removals of the attribute reconstruct right now.
1330 if (nsTextControlFrame* f = do_QueryFrame(GetPrimaryFrame())) {
1331 f->PlaceholderChanged(aOldValue, aValue);
1332 }
1333 }
1334
1335 if (CreatesDateTimeWidget()) {
1336 if (aName == nsGkAtoms::value || aName == nsGkAtoms::readonly ||
1337 aName == nsGkAtoms::tabindex || aName == nsGkAtoms::required ||
1338 aName == nsGkAtoms::disabled) {
1339 // If original target is this and not the inner text control, we should
1340 // pass the focus to the inner text control.
1341 if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
1342 AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
1343 dateTimeBoxElement,
1344 aName == nsGkAtoms::value ? u"MozDateTimeValueChanged"_ns
1345 : u"MozDateTimeAttributeChanged"_ns,
1346 CanBubble::eNo, ChromeOnlyDispatch::eNo);
1347 dispatcher->RunDOMEventWhenSafe();
1348 }
1349 }
1350 }
1351 }
1352
1353 return nsGenericHTMLFormElementWithState::AfterSetAttr(
1354 aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
1355 }
1356
BeforeSetForm(bool aBindToTree)1357 void HTMLInputElement::BeforeSetForm(bool aBindToTree) {
1358 // No need to remove from radio group if we are just binding to tree.
1359 if (mType == FormControlType::InputRadio && !aBindToTree) {
1360 WillRemoveFromRadioGroup();
1361 }
1362 }
1363
AfterClearForm(bool aUnbindOrDelete)1364 void HTMLInputElement::AfterClearForm(bool aUnbindOrDelete) {
1365 MOZ_ASSERT(!mForm);
1366
1367 // Do not add back to radio group if we are releasing or unbinding from tree.
1368 if (mType == FormControlType::InputRadio && !aUnbindOrDelete) {
1369 AddedToRadioGroup();
1370 UpdateValueMissingValidityStateForRadio(false);
1371 }
1372 }
1373
ResultForDialogSubmit(nsAString & aResult)1374 void HTMLInputElement::ResultForDialogSubmit(nsAString& aResult) {
1375 if (mType == FormControlType::InputImage) {
1376 // Get a property set by the frame to find out where it was clicked.
1377 nsIntPoint* lastClickedPoint =
1378 static_cast<nsIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
1379 int32_t x, y;
1380 if (lastClickedPoint) {
1381 x = lastClickedPoint->x;
1382 y = lastClickedPoint->y;
1383 } else {
1384 x = y = 0;
1385 }
1386 aResult.AppendInt(x);
1387 aResult.AppendLiteral(",");
1388 aResult.AppendInt(y);
1389 } else {
1390 GetAttr(kNameSpaceID_None, nsGkAtoms::value, aResult);
1391 }
1392 }
1393
GetAutocomplete(nsAString & aValue)1394 void HTMLInputElement::GetAutocomplete(nsAString& aValue) {
1395 if (!DoesAutocompleteApply()) {
1396 return;
1397 }
1398
1399 aValue.Truncate();
1400 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1401
1402 mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(
1403 attributeVal, aValue, mAutocompleteAttrState);
1404 }
1405
GetAutocompleteInfo(Nullable<AutocompleteInfo> & aInfo)1406 void HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo) {
1407 if (!DoesAutocompleteApply()) {
1408 aInfo.SetNull();
1409 return;
1410 }
1411
1412 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1413 mAutocompleteInfoState = nsContentUtils::SerializeAutocompleteAttribute(
1414 attributeVal, aInfo.SetValue(), mAutocompleteInfoState, true);
1415 }
1416
GetCapture(nsAString & aValue)1417 void HTMLInputElement::GetCapture(nsAString& aValue) {
1418 GetEnumAttr(nsGkAtoms::capture, kCaptureDefault->tag, aValue);
1419 }
1420
GetFormEnctype(nsAString & aValue)1421 void HTMLInputElement::GetFormEnctype(nsAString& aValue) {
1422 GetEnumAttr(nsGkAtoms::formenctype, "", kFormDefaultEnctype->tag, aValue);
1423 }
1424
GetFormMethod(nsAString & aValue)1425 void HTMLInputElement::GetFormMethod(nsAString& aValue) {
1426 GetEnumAttr(nsGkAtoms::formmethod, "", kFormDefaultMethod->tag, aValue);
1427 }
1428
GetType(nsAString & aValue)1429 void HTMLInputElement::GetType(nsAString& aValue) {
1430 GetEnumAttr(nsGkAtoms::type, kInputDefaultType->tag, aValue);
1431 }
1432
TabIndexDefault()1433 int32_t HTMLInputElement::TabIndexDefault() { return 0; }
1434
Height()1435 uint32_t HTMLInputElement::Height() {
1436 if (mType != FormControlType::InputImage) {
1437 return 0;
1438 }
1439 return GetWidthHeightForImage().height;
1440 }
1441
SetIndeterminateInternal(bool aValue,bool aShouldInvalidate)1442 void HTMLInputElement::SetIndeterminateInternal(bool aValue,
1443 bool aShouldInvalidate) {
1444 mIndeterminate = aValue;
1445
1446 if (aShouldInvalidate) {
1447 // Repaint the frame
1448 nsIFrame* frame = GetPrimaryFrame();
1449 if (frame) frame->InvalidateFrameSubtree();
1450 }
1451
1452 UpdateState(true);
1453 }
1454
SetIndeterminate(bool aValue)1455 void HTMLInputElement::SetIndeterminate(bool aValue) {
1456 SetIndeterminateInternal(aValue, true);
1457 }
1458
Width()1459 uint32_t HTMLInputElement::Width() {
1460 if (mType != FormControlType::InputImage) {
1461 return 0;
1462 }
1463 return GetWidthHeightForImage().width;
1464 }
1465
SanitizesOnValueGetter() const1466 bool HTMLInputElement::SanitizesOnValueGetter() const {
1467 // Don't return non-sanitized value for datetime types or number.
1468 return mType == FormControlType::InputNumber || IsDateTimeInputType(mType);
1469 }
1470
GetValue(nsAString & aValue,CallerType aCallerType)1471 void HTMLInputElement::GetValue(nsAString& aValue, CallerType aCallerType) {
1472 GetValueInternal(aValue, aCallerType);
1473
1474 if (SanitizesOnValueGetter()) {
1475 SanitizeValue(aValue, ForValueGetter::Yes);
1476 }
1477 }
1478
GetValueInternal(nsAString & aValue,CallerType aCallerType) const1479 void HTMLInputElement::GetValueInternal(nsAString& aValue,
1480 CallerType aCallerType) const {
1481 if (mType != FormControlType::InputFile) {
1482 GetNonFileValueInternal(aValue);
1483 return;
1484 }
1485
1486 if (aCallerType == CallerType::System) {
1487 aValue.Assign(mFileData->mFirstFilePath);
1488 return;
1489 }
1490
1491 if (mFileData->mFilesOrDirectories.IsEmpty()) {
1492 aValue.Truncate();
1493 return;
1494 }
1495
1496 nsAutoString file;
1497 GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], file);
1498 if (file.IsEmpty()) {
1499 aValue.Truncate();
1500 return;
1501 }
1502
1503 aValue.AssignLiteral("C:\\fakepath\\");
1504 aValue.Append(file);
1505 }
1506
GetNonFileValueInternal(nsAString & aValue) const1507 void HTMLInputElement::GetNonFileValueInternal(nsAString& aValue) const {
1508 switch (GetValueMode()) {
1509 case VALUE_MODE_VALUE:
1510 if (IsSingleLineTextControl(false)) {
1511 mInputData.mState->GetValue(aValue, true);
1512 } else if (!aValue.Assign(mInputData.mValue, fallible)) {
1513 aValue.Truncate();
1514 }
1515 return;
1516
1517 case VALUE_MODE_FILENAME:
1518 MOZ_ASSERT_UNREACHABLE("Someone screwed up here");
1519 // We'll just return empty string if someone does screw up.
1520 aValue.Truncate();
1521 return;
1522
1523 case VALUE_MODE_DEFAULT:
1524 // Treat defaultValue as value.
1525 GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue);
1526 return;
1527
1528 case VALUE_MODE_DEFAULT_ON:
1529 // Treat default value as value and returns "on" if no value.
1530 if (!GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue)) {
1531 aValue.AssignLiteral("on");
1532 }
1533 return;
1534 }
1535 }
1536
IsValueEmpty() const1537 bool HTMLInputElement::IsValueEmpty() const {
1538 if (GetValueMode() == VALUE_MODE_VALUE && IsSingleLineTextControl(false)) {
1539 return !mInputData.mState->HasNonEmptyValue();
1540 }
1541
1542 nsAutoString value;
1543 GetNonFileValueInternal(value);
1544
1545 return value.IsEmpty();
1546 }
1547
ClearFiles(bool aSetValueChanged)1548 void HTMLInputElement::ClearFiles(bool aSetValueChanged) {
1549 nsTArray<OwningFileOrDirectory> data;
1550 SetFilesOrDirectories(data, aSetValueChanged);
1551 }
1552
MonthsSinceJan1970(uint32_t aYear,uint32_t aMonth) const1553 int32_t HTMLInputElement::MonthsSinceJan1970(uint32_t aYear,
1554 uint32_t aMonth) const {
1555 return (aYear - 1970) * 12 + aMonth - 1;
1556 }
1557
1558 /* static */
StringToDecimal(const nsAString & aValue)1559 Decimal HTMLInputElement::StringToDecimal(const nsAString& aValue) {
1560 if (!IsAscii(aValue)) {
1561 return Decimal::nan();
1562 }
1563 NS_LossyConvertUTF16toASCII asciiString(aValue);
1564 std::string stdString = asciiString.get();
1565 return Decimal::fromString(stdString);
1566 }
1567
GetValueAsDecimal() const1568 Decimal HTMLInputElement::GetValueAsDecimal() const {
1569 Decimal decimalValue;
1570 nsAutoString stringValue;
1571
1572 GetNonFileValueInternal(stringValue);
1573
1574 return !mInputType->ConvertStringToNumber(stringValue, decimalValue)
1575 ? Decimal::nan()
1576 : decimalValue;
1577 }
1578
SetValue(const nsAString & aValue,CallerType aCallerType,ErrorResult & aRv)1579 void HTMLInputElement::SetValue(const nsAString& aValue, CallerType aCallerType,
1580 ErrorResult& aRv) {
1581 // check security. Note that setting the value to the empty string is always
1582 // OK and gives pages a way to clear a file input if necessary.
1583 if (mType == FormControlType::InputFile) {
1584 if (!aValue.IsEmpty()) {
1585 if (aCallerType != CallerType::System) {
1586 // setting the value of a "FILE" input widget requires
1587 // chrome privilege
1588 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1589 return;
1590 }
1591 Sequence<nsString> list;
1592 if (!list.AppendElement(aValue, fallible)) {
1593 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1594 return;
1595 }
1596
1597 MozSetFileNameArray(list, aRv);
1598 return;
1599 }
1600 ClearFiles(true);
1601 } else {
1602 if (MayFireChangeOnBlur()) {
1603 // If the value has been set by a script, we basically want to keep the
1604 // current change event state. If the element is ready to fire a change
1605 // event, we should keep it that way. Otherwise, we should make sure the
1606 // element will not fire any event because of the script interaction.
1607 //
1608 // NOTE: this is currently quite expensive work (too much string
1609 // manipulation). We should probably optimize that.
1610 nsAutoString currentValue;
1611 GetValue(currentValue, aCallerType);
1612
1613 // Some types sanitize value, so GetValue doesn't return pure
1614 // previous value correctly.
1615 //
1616 // FIXME(emilio): Shouldn't above just use GetNonFileValueInternal() to
1617 // get the unsanitized value?
1618 nsresult rv = SetValueInternal(
1619 aValue, SanitizesOnValueGetter() ? nullptr : ¤tValue,
1620 {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
1621 ValueSetterOption::MoveCursorToEndIfValueChanged});
1622 if (NS_FAILED(rv)) {
1623 aRv.Throw(rv);
1624 return;
1625 }
1626
1627 if (mFocusedValue.Equals(currentValue)) {
1628 GetValue(mFocusedValue, aCallerType);
1629 }
1630 } else {
1631 nsresult rv = SetValueInternal(
1632 aValue,
1633 {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
1634 ValueSetterOption::MoveCursorToEndIfValueChanged});
1635 if (NS_FAILED(rv)) {
1636 aRv.Throw(rv);
1637 return;
1638 }
1639 }
1640 }
1641 }
1642
GetList() const1643 nsGenericHTMLElement* HTMLInputElement::GetList() const {
1644 nsAutoString dataListId;
1645 GetAttr(kNameSpaceID_None, nsGkAtoms::list_, dataListId);
1646 if (dataListId.IsEmpty()) {
1647 return nullptr;
1648 }
1649
1650 DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
1651 if (!docOrShadow) {
1652 return nullptr;
1653 }
1654
1655 Element* element = docOrShadow->GetElementById(dataListId);
1656 if (!element || !element->IsHTMLElement(nsGkAtoms::datalist)) {
1657 return nullptr;
1658 }
1659
1660 return static_cast<nsGenericHTMLElement*>(element);
1661 }
1662
SetValue(Decimal aValue,CallerType aCallerType)1663 void HTMLInputElement::SetValue(Decimal aValue, CallerType aCallerType) {
1664 MOZ_ASSERT(!aValue.isInfinity(), "aValue must not be Infinity!");
1665
1666 if (aValue.isNaN()) {
1667 SetValue(u""_ns, aCallerType, IgnoreErrors());
1668 return;
1669 }
1670
1671 nsAutoString value;
1672 mInputType->ConvertNumberToString(aValue, value);
1673 SetValue(value, aCallerType, IgnoreErrors());
1674 }
1675
GetValueAsDate(JSContext * aCx,JS::MutableHandle<JSObject * > aObject,ErrorResult & aRv)1676 void HTMLInputElement::GetValueAsDate(JSContext* aCx,
1677 JS::MutableHandle<JSObject*> aObject,
1678 ErrorResult& aRv) {
1679 aObject.set(nullptr);
1680 if (!IsDateTimeInputType(mType)) {
1681 return;
1682 }
1683
1684 Maybe<JS::ClippedTime> time;
1685
1686 switch (mType) {
1687 case FormControlType::InputDate: {
1688 uint32_t year, month, day;
1689 nsAutoString value;
1690 GetNonFileValueInternal(value);
1691 if (!ParseDate(value, &year, &month, &day)) {
1692 return;
1693 }
1694
1695 time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, day)));
1696 break;
1697 }
1698 case FormControlType::InputTime: {
1699 uint32_t millisecond;
1700 nsAutoString value;
1701 GetNonFileValueInternal(value);
1702 if (!ParseTime(value, &millisecond)) {
1703 return;
1704 }
1705
1706 time.emplace(JS::TimeClip(millisecond));
1707 MOZ_ASSERT(time->toDouble() == millisecond,
1708 "HTML times are restricted to the day after the epoch and "
1709 "never clip");
1710 break;
1711 }
1712 case FormControlType::InputMonth: {
1713 uint32_t year, month;
1714 nsAutoString value;
1715 GetNonFileValueInternal(value);
1716 if (!ParseMonth(value, &year, &month)) {
1717 return;
1718 }
1719
1720 time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, 1)));
1721 break;
1722 }
1723 case FormControlType::InputWeek: {
1724 uint32_t year, week;
1725 nsAutoString value;
1726 GetNonFileValueInternal(value);
1727 if (!ParseWeek(value, &year, &week)) {
1728 return;
1729 }
1730
1731 double days = DaysSinceEpochFromWeek(year, week);
1732 time.emplace(JS::TimeClip(days * kMsPerDay));
1733
1734 break;
1735 }
1736 case FormControlType::InputDatetimeLocal: {
1737 uint32_t year, month, day, timeInMs;
1738 nsAutoString value;
1739 GetNonFileValueInternal(value);
1740 if (!ParseDateTimeLocal(value, &year, &month, &day, &timeInMs)) {
1741 return;
1742 }
1743
1744 time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, day, timeInMs)));
1745 break;
1746 }
1747 default:
1748 break;
1749 }
1750
1751 if (time) {
1752 aObject.set(JS::NewDateObject(aCx, *time));
1753 if (!aObject) {
1754 aRv.NoteJSContextException(aCx);
1755 }
1756 return;
1757 }
1758
1759 MOZ_ASSERT(false, "Unrecognized input type");
1760 aRv.Throw(NS_ERROR_UNEXPECTED);
1761 }
1762
SetValueAsDate(JSContext * aCx,JS::Handle<JSObject * > aObj,ErrorResult & aRv)1763 void HTMLInputElement::SetValueAsDate(JSContext* aCx,
1764 JS::Handle<JSObject*> aObj,
1765 ErrorResult& aRv) {
1766 if (!IsDateTimeInputType(mType)) {
1767 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1768 return;
1769 }
1770
1771 if (aObj) {
1772 bool isDate;
1773 if (!JS::ObjectIsDate(aCx, aObj, &isDate)) {
1774 aRv.NoteJSContextException(aCx);
1775 return;
1776 }
1777 if (!isDate) {
1778 aRv.ThrowTypeError("Value being assigned is not a date.");
1779 return;
1780 }
1781 }
1782
1783 double milliseconds;
1784 if (aObj) {
1785 if (!js::DateGetMsecSinceEpoch(aCx, aObj, &milliseconds)) {
1786 aRv.NoteJSContextException(aCx);
1787 return;
1788 }
1789 } else {
1790 milliseconds = UnspecifiedNaN<double>();
1791 }
1792
1793 // At this point we know we're not a file input, so we can just pass "not
1794 // system" as the caller type, since the caller type only matters in the file
1795 // input case.
1796 if (IsNaN(milliseconds)) {
1797 SetValue(u""_ns, CallerType::NonSystem, aRv);
1798 return;
1799 }
1800
1801 if (mType != FormControlType::InputMonth) {
1802 SetValue(Decimal::fromDouble(milliseconds), CallerType::NonSystem);
1803 return;
1804 }
1805
1806 // type=month expects the value to be number of months.
1807 double year = JS::YearFromTime(milliseconds);
1808 double month = JS::MonthFromTime(milliseconds);
1809
1810 if (IsNaN(year) || IsNaN(month)) {
1811 SetValue(u""_ns, CallerType::NonSystem, aRv);
1812 return;
1813 }
1814
1815 int32_t months = MonthsSinceJan1970(year, month + 1);
1816 SetValue(Decimal(int32_t(months)), CallerType::NonSystem);
1817 }
1818
SetValueAsNumber(double aValueAsNumber,ErrorResult & aRv)1819 void HTMLInputElement::SetValueAsNumber(double aValueAsNumber,
1820 ErrorResult& aRv) {
1821 // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
1822 // bug 825197.
1823 if (IsInfinite(aValueAsNumber)) {
1824 aRv.Throw(NS_ERROR_INVALID_ARG);
1825 return;
1826 }
1827
1828 if (!DoesValueAsNumberApply()) {
1829 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1830 return;
1831 }
1832
1833 // At this point we know we're not a file input, so we can just pass "not
1834 // system" as the caller type, since the caller type only matters in the file
1835 // input case.
1836 SetValue(Decimal::fromDouble(aValueAsNumber), CallerType::NonSystem);
1837 }
1838
GetMinimum() const1839 Decimal HTMLInputElement::GetMinimum() const {
1840 MOZ_ASSERT(
1841 DoesValueAsNumberApply(),
1842 "GetMinimum() should only be used for types that allow .valueAsNumber");
1843
1844 // Only type=range has a default minimum
1845 Decimal defaultMinimum =
1846 mType == FormControlType::InputRange ? Decimal(0) : Decimal::nan();
1847
1848 if (!HasAttr(kNameSpaceID_None, nsGkAtoms::min)) {
1849 return defaultMinimum;
1850 }
1851
1852 nsAutoString minStr;
1853 GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
1854
1855 Decimal min;
1856 return mInputType->ConvertStringToNumber(minStr, min) ? min : defaultMinimum;
1857 }
1858
GetMaximum() const1859 Decimal HTMLInputElement::GetMaximum() const {
1860 MOZ_ASSERT(
1861 DoesValueAsNumberApply(),
1862 "GetMaximum() should only be used for types that allow .valueAsNumber");
1863
1864 // Only type=range has a default maximum
1865 Decimal defaultMaximum =
1866 mType == FormControlType::InputRange ? Decimal(100) : Decimal::nan();
1867
1868 if (!HasAttr(kNameSpaceID_None, nsGkAtoms::max)) {
1869 return defaultMaximum;
1870 }
1871
1872 nsAutoString maxStr;
1873 GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
1874
1875 Decimal max;
1876 return mInputType->ConvertStringToNumber(maxStr, max) ? max : defaultMaximum;
1877 }
1878
GetStepBase() const1879 Decimal HTMLInputElement::GetStepBase() const {
1880 MOZ_ASSERT(IsDateTimeInputType(mType) ||
1881 mType == FormControlType::InputNumber ||
1882 mType == FormControlType::InputRange,
1883 "Check that kDefaultStepBase is correct for this new type");
1884
1885 Decimal stepBase;
1886
1887 // Do NOT use GetMinimum here - the spec says to use "the min content
1888 // attribute", not "the minimum".
1889 nsAutoString minStr;
1890 if (GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr) &&
1891 mInputType->ConvertStringToNumber(minStr, stepBase)) {
1892 return stepBase;
1893 }
1894
1895 // If @min is not a double, we should use @value.
1896 nsAutoString valueStr;
1897 if (GetAttr(kNameSpaceID_None, nsGkAtoms::value, valueStr) &&
1898 mInputType->ConvertStringToNumber(valueStr, stepBase)) {
1899 return stepBase;
1900 }
1901
1902 if (mType == FormControlType::InputWeek) {
1903 return kDefaultStepBaseWeek;
1904 }
1905
1906 return kDefaultStepBase;
1907 }
1908
GetValueIfStepped(int32_t aStep,StepCallerType aCallerType,Decimal * aNextStep)1909 nsresult HTMLInputElement::GetValueIfStepped(int32_t aStep,
1910 StepCallerType aCallerType,
1911 Decimal* aNextStep) {
1912 if (!DoStepDownStepUpApply()) {
1913 return NS_ERROR_DOM_INVALID_STATE_ERR;
1914 }
1915
1916 Decimal stepBase = GetStepBase();
1917 Decimal step = GetStep();
1918 if (step == kStepAny) {
1919 if (aCallerType != CALLED_FOR_USER_EVENT) {
1920 return NS_ERROR_DOM_INVALID_STATE_ERR;
1921 }
1922 // Allow the spin buttons and up/down arrow keys to do something sensible:
1923 step = GetDefaultStep();
1924 }
1925
1926 Decimal minimum = GetMinimum();
1927 Decimal maximum = GetMaximum();
1928
1929 if (!maximum.isNaN()) {
1930 // "max - (max - stepBase) % step" is the nearest valid value to max.
1931 maximum = maximum - NS_floorModulo(maximum - stepBase, step);
1932 if (!minimum.isNaN()) {
1933 if (minimum > maximum) {
1934 // Either the minimum was greater than the maximum prior to our
1935 // adjustment to align maximum on a step, or else (if we adjusted
1936 // maximum) there is no valid step between minimum and the unadjusted
1937 // maximum.
1938 return NS_OK;
1939 }
1940 }
1941 }
1942
1943 Decimal value = GetValueAsDecimal();
1944 bool valueWasNaN = false;
1945 if (value.isNaN()) {
1946 value = Decimal(0);
1947 valueWasNaN = true;
1948 }
1949 Decimal valueBeforeStepping = value;
1950
1951 Decimal deltaFromStep = NS_floorModulo(value - stepBase, step);
1952
1953 if (deltaFromStep != Decimal(0)) {
1954 if (aStep > 0) {
1955 value += step - deltaFromStep; // partial step
1956 value += step * Decimal(aStep - 1); // then remaining steps
1957 } else if (aStep < 0) {
1958 value -= deltaFromStep; // partial step
1959 value += step * Decimal(aStep + 1); // then remaining steps
1960 }
1961 } else {
1962 value += step * Decimal(aStep);
1963 }
1964
1965 if (value < minimum) {
1966 value = minimum;
1967 deltaFromStep = NS_floorModulo(value - stepBase, step);
1968 if (deltaFromStep != Decimal(0)) {
1969 value += step - deltaFromStep;
1970 }
1971 }
1972 if (value > maximum) {
1973 value = maximum;
1974 deltaFromStep = NS_floorModulo(value - stepBase, step);
1975 if (deltaFromStep != Decimal(0)) {
1976 value -= deltaFromStep;
1977 }
1978 }
1979
1980 if (!valueWasNaN && // value="", resulting in us using "0"
1981 ((aStep > 0 && value < valueBeforeStepping) ||
1982 (aStep < 0 && value > valueBeforeStepping))) {
1983 // We don't want step-up to effectively step down, or step-down to
1984 // effectively step up, so return;
1985 return NS_OK;
1986 }
1987
1988 *aNextStep = value;
1989 return NS_OK;
1990 }
1991
ApplyStep(int32_t aStep)1992 nsresult HTMLInputElement::ApplyStep(int32_t aStep) {
1993 Decimal nextStep = Decimal::nan(); // unchanged if value will not change
1994
1995 nsresult rv = GetValueIfStepped(aStep, CALLED_FOR_SCRIPT, &nextStep);
1996
1997 if (NS_SUCCEEDED(rv) && nextStep.isFinite()) {
1998 // We know we're not a file input, so the caller type does not matter; just
1999 // pass "not system" to be safe.
2000 SetValue(nextStep, CallerType::NonSystem);
2001 }
2002
2003 return rv;
2004 }
2005
IsDateTimeInputType(FormControlType aType)2006 bool HTMLInputElement::IsDateTimeInputType(FormControlType aType) {
2007 switch (aType) {
2008 case FormControlType::InputDate:
2009 case FormControlType::InputTime:
2010 case FormControlType::InputMonth:
2011 case FormControlType::InputWeek:
2012 case FormControlType::InputDatetimeLocal:
2013 return true;
2014 default:
2015 return false;
2016 }
2017 }
2018
MozGetFileNameArray(nsTArray<nsString> & aArray,ErrorResult & aRv)2019 void HTMLInputElement::MozGetFileNameArray(nsTArray<nsString>& aArray,
2020 ErrorResult& aRv) {
2021 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2022 return;
2023 }
2024
2025 const nsTArray<OwningFileOrDirectory>& filesOrDirs =
2026 GetFilesOrDirectoriesInternal();
2027 for (uint32_t i = 0; i < filesOrDirs.Length(); i++) {
2028 nsAutoString str;
2029 GetDOMFileOrDirectoryPath(filesOrDirs[i], str, aRv);
2030 if (NS_WARN_IF(aRv.Failed())) {
2031 return;
2032 }
2033
2034 aArray.AppendElement(str);
2035 }
2036 }
2037
MozSetFileArray(const Sequence<OwningNonNull<File>> & aFiles)2038 void HTMLInputElement::MozSetFileArray(
2039 const Sequence<OwningNonNull<File>>& aFiles) {
2040 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2041 return;
2042 }
2043
2044 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2045 MOZ_ASSERT(global);
2046 if (!global) {
2047 return;
2048 }
2049
2050 nsTArray<OwningFileOrDirectory> files;
2051 for (uint32_t i = 0; i < aFiles.Length(); ++i) {
2052 RefPtr<File> file = File::Create(global, aFiles[i].get()->Impl());
2053 if (NS_WARN_IF(!file)) {
2054 return;
2055 }
2056
2057 OwningFileOrDirectory* element = files.AppendElement();
2058 element->SetAsFile() = file;
2059 }
2060
2061 SetFilesOrDirectories(files, true);
2062 }
2063
MozSetFileNameArray(const Sequence<nsString> & aFileNames,ErrorResult & aRv)2064 void HTMLInputElement::MozSetFileNameArray(const Sequence<nsString>& aFileNames,
2065 ErrorResult& aRv) {
2066 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2067 return;
2068 }
2069
2070 if (XRE_IsContentProcess()) {
2071 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
2072 return;
2073 }
2074
2075 nsTArray<OwningFileOrDirectory> files;
2076 for (uint32_t i = 0; i < aFileNames.Length(); ++i) {
2077 nsCOMPtr<nsIFile> file;
2078
2079 if (StringBeginsWith(aFileNames[i], u"file:"_ns,
2080 nsASCIICaseInsensitiveStringComparator)) {
2081 // Converts the URL string into the corresponding nsIFile if possible
2082 // A local file will be created if the URL string begins with file://
2083 NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames[i]),
2084 getter_AddRefs(file));
2085 }
2086
2087 if (!file) {
2088 // this is no "file://", try as local file
2089 NS_NewLocalFile(aFileNames[i], false, getter_AddRefs(file));
2090 }
2091
2092 if (!file) {
2093 continue; // Not much we can do if the file doesn't exist
2094 }
2095
2096 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2097 if (!global) {
2098 aRv.Throw(NS_ERROR_FAILURE);
2099 return;
2100 }
2101
2102 RefPtr<File> domFile = File::CreateFromFile(global, file);
2103 if (NS_WARN_IF(!domFile)) {
2104 aRv.Throw(NS_ERROR_FAILURE);
2105 return;
2106 }
2107
2108 OwningFileOrDirectory* element = files.AppendElement();
2109 element->SetAsFile() = domFile;
2110 }
2111
2112 SetFilesOrDirectories(files, true);
2113 }
2114
MozSetDirectory(const nsAString & aDirectoryPath,ErrorResult & aRv)2115 void HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
2116 ErrorResult& aRv) {
2117 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2118 return;
2119 }
2120
2121 nsCOMPtr<nsIFile> file;
2122 aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file));
2123 if (NS_WARN_IF(aRv.Failed())) {
2124 return;
2125 }
2126
2127 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
2128 if (NS_WARN_IF(!window)) {
2129 aRv.Throw(NS_ERROR_FAILURE);
2130 return;
2131 }
2132
2133 RefPtr<Directory> directory = Directory::Create(window->AsGlobal(), file);
2134 MOZ_ASSERT(directory);
2135
2136 nsTArray<OwningFileOrDirectory> array;
2137 OwningFileOrDirectory* element = array.AppendElement();
2138 element->SetAsDirectory() = directory;
2139
2140 SetFilesOrDirectories(array, true);
2141 }
2142
GetDateTimeInputBoxValue(DateTimeValue & aValue)2143 void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue) {
2144 if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
2145 return;
2146 }
2147
2148 aValue = *mDateTimeInputBoxValue;
2149 }
2150
GetDateTimeBoxElement()2151 Element* HTMLInputElement::GetDateTimeBoxElement() {
2152 if (!GetShadowRoot()) {
2153 return nullptr;
2154 }
2155
2156 // The datetimebox <div> is the only child of the UA Widget Shadow Root
2157 // if it is present.
2158 MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
2159 MOZ_ASSERT(1 >= GetShadowRoot()->GetChildCount());
2160 if (nsIContent* inputAreaContent = GetShadowRoot()->GetFirstChild()) {
2161 return inputAreaContent->AsElement();
2162 }
2163
2164 return nullptr;
2165 }
2166
OpenDateTimePicker(const DateTimeValue & aInitialValue)2167 void HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue) {
2168 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2169 return;
2170 }
2171
2172 mDateTimeInputBoxValue = MakeUnique<DateTimeValue>(aInitialValue);
2173 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2174 u"MozOpenDateTimePicker"_ns,
2175 CanBubble::eYes, Cancelable::eYes);
2176 }
2177
UpdateDateTimePicker(const DateTimeValue & aValue)2178 void HTMLInputElement::UpdateDateTimePicker(const DateTimeValue& aValue) {
2179 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2180 return;
2181 }
2182
2183 mDateTimeInputBoxValue = MakeUnique<DateTimeValue>(aValue);
2184 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2185 u"MozUpdateDateTimePicker"_ns,
2186 CanBubble::eYes, Cancelable::eYes);
2187 }
2188
CloseDateTimePicker()2189 void HTMLInputElement::CloseDateTimePicker() {
2190 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2191 return;
2192 }
2193
2194 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2195 u"MozCloseDateTimePicker"_ns,
2196 CanBubble::eYes, Cancelable::eYes);
2197 }
2198
SetFocusState(bool aIsFocused)2199 void HTMLInputElement::SetFocusState(bool aIsFocused) {
2200 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2201 return;
2202 }
2203
2204 EventStates focusStates = NS_EVENT_STATE_FOCUS | NS_EVENT_STATE_FOCUSRING;
2205 if (aIsFocused) {
2206 AddStates(focusStates);
2207 } else {
2208 RemoveStates(focusStates);
2209 }
2210 }
2211
UpdateValidityState()2212 void HTMLInputElement::UpdateValidityState() {
2213 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2214 return;
2215 }
2216
2217 // For now, datetime input box call this function only when the value may
2218 // become valid/invalid. For other validity states, they will be updated when
2219 // .value is actually changed.
2220 UpdateBadInputValidityState();
2221 UpdateState(true);
2222 }
2223
MozIsTextField(bool aExcludePassword)2224 bool HTMLInputElement::MozIsTextField(bool aExcludePassword) {
2225 // TODO: temporary until bug 888320 is fixed.
2226 //
2227 // FIXME: Historically we never returned true for `number`, we should consider
2228 // changing that now that it is similar to other inputs.
2229 if (IsDateTimeInputType(mType) || mType == FormControlType::InputNumber) {
2230 return false;
2231 }
2232
2233 return IsSingleLineTextControl(aExcludePassword);
2234 }
2235
SetUserInput(const nsAString & aValue,nsIPrincipal & aSubjectPrincipal)2236 void HTMLInputElement::SetUserInput(const nsAString& aValue,
2237 nsIPrincipal& aSubjectPrincipal) {
2238 AutoHandlingUserInputStatePusher inputStatePusher(true);
2239
2240 if (mType == FormControlType::InputFile &&
2241 !aSubjectPrincipal.IsSystemPrincipal()) {
2242 return;
2243 }
2244
2245 if (mType == FormControlType::InputFile) {
2246 Sequence<nsString> list;
2247 if (!list.AppendElement(aValue, fallible)) {
2248 return;
2249 }
2250
2251 MozSetFileNameArray(list, IgnoreErrors());
2252 return;
2253 }
2254
2255 bool isInputEventDispatchedByTextControlState =
2256 GetValueMode() == VALUE_MODE_VALUE && IsSingleLineTextControl(false);
2257
2258 nsresult rv = SetValueInternal(
2259 aValue,
2260 {ValueSetterOption::BySetUserInputAPI, ValueSetterOption::SetValueChanged,
2261 ValueSetterOption::MoveCursorToEndIfValueChanged});
2262 NS_ENSURE_SUCCESS_VOID(rv);
2263
2264 if (!isInputEventDispatchedByTextControlState) {
2265 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
2266 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2267 "Failed to dispatch input event");
2268 }
2269
2270 // If this element is not currently focused, it won't receive a change event
2271 // for this update through the normal channels. So fire a change event
2272 // immediately, instead.
2273 if (!ShouldBlur(this)) {
2274 FireChangeEventIfNeeded();
2275 }
2276 }
2277
GetEditorForBindings()2278 nsIEditor* HTMLInputElement::GetEditorForBindings() {
2279 if (!GetPrimaryFrame()) {
2280 // Ensure we construct frames (and thus an editor) if needed.
2281 GetPrimaryFrame(FlushType::Frames);
2282 }
2283 return GetTextEditorFromState();
2284 }
2285
HasEditor()2286 bool HTMLInputElement::HasEditor() { return !!GetTextEditorWithoutCreation(); }
2287
GetTextEditorFromState()2288 TextEditor* HTMLInputElement::GetTextEditorFromState() {
2289 TextControlState* state = GetEditorState();
2290 if (state) {
2291 return state->GetTextEditor();
2292 }
2293 return nullptr;
2294 }
2295
GetTextEditor()2296 TextEditor* HTMLInputElement::GetTextEditor() {
2297 return GetTextEditorFromState();
2298 }
2299
GetTextEditorWithoutCreation()2300 TextEditor* HTMLInputElement::GetTextEditorWithoutCreation() {
2301 TextControlState* state = GetEditorState();
2302 if (!state) {
2303 return nullptr;
2304 }
2305 return state->GetTextEditorWithoutCreation();
2306 }
2307
GetSelectionController()2308 nsISelectionController* HTMLInputElement::GetSelectionController() {
2309 TextControlState* state = GetEditorState();
2310 if (state) {
2311 return state->GetSelectionController();
2312 }
2313 return nullptr;
2314 }
2315
GetConstFrameSelection()2316 nsFrameSelection* HTMLInputElement::GetConstFrameSelection() {
2317 TextControlState* state = GetEditorState();
2318 if (state) {
2319 return state->GetConstFrameSelection();
2320 }
2321 return nullptr;
2322 }
2323
BindToFrame(nsTextControlFrame * aFrame)2324 nsresult HTMLInputElement::BindToFrame(nsTextControlFrame* aFrame) {
2325 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
2326 TextControlState* state = GetEditorState();
2327 if (state) {
2328 return state->BindToFrame(aFrame);
2329 }
2330 return NS_ERROR_FAILURE;
2331 }
2332
UnbindFromFrame(nsTextControlFrame * aFrame)2333 void HTMLInputElement::UnbindFromFrame(nsTextControlFrame* aFrame) {
2334 TextControlState* state = GetEditorState();
2335 if (state && aFrame) {
2336 state->UnbindFromFrame(aFrame);
2337 }
2338 }
2339
CreateEditor()2340 nsresult HTMLInputElement::CreateEditor() {
2341 TextControlState* state = GetEditorState();
2342 if (state) {
2343 return state->PrepareEditor();
2344 }
2345 return NS_ERROR_FAILURE;
2346 }
2347
SetPreviewValue(const nsAString & aValue)2348 void HTMLInputElement::SetPreviewValue(const nsAString& aValue) {
2349 TextControlState* state = GetEditorState();
2350 if (state) {
2351 state->SetPreviewText(aValue, true);
2352 }
2353 }
2354
GetPreviewValue(nsAString & aValue)2355 void HTMLInputElement::GetPreviewValue(nsAString& aValue) {
2356 TextControlState* state = GetEditorState();
2357 if (state) {
2358 state->GetPreviewText(aValue);
2359 }
2360 }
2361
EnablePreview()2362 void HTMLInputElement::EnablePreview() {
2363 if (mIsPreviewEnabled) {
2364 return;
2365 }
2366
2367 mIsPreviewEnabled = true;
2368 // Reconstruct the frame to append an anonymous preview node
2369 nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0},
2370 nsChangeHint_ReconstructFrame);
2371 }
2372
IsPreviewEnabled()2373 bool HTMLInputElement::IsPreviewEnabled() { return mIsPreviewEnabled; }
2374
GetDisplayFileName(nsAString & aValue) const2375 void HTMLInputElement::GetDisplayFileName(nsAString& aValue) const {
2376 MOZ_ASSERT(mFileData);
2377
2378 if (OwnerDoc()->IsStaticDocument()) {
2379 aValue = mFileData->mStaticDocFileList;
2380 return;
2381 }
2382
2383 if (mFileData->mFilesOrDirectories.Length() == 1) {
2384 GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], aValue);
2385 return;
2386 }
2387
2388 nsAutoString value;
2389
2390 if (mFileData->mFilesOrDirectories.IsEmpty()) {
2391 if ((StaticPrefs::dom_input_dirpicker() && Allowdirs()) ||
2392 (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2393 HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
2394 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2395 "NoDirSelected", OwnerDoc(),
2396 value);
2397 } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
2398 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2399 "NoFilesSelected", OwnerDoc(),
2400 value);
2401 } else {
2402 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2403 "NoFileSelected", OwnerDoc(),
2404 value);
2405 }
2406 } else {
2407 nsString count;
2408 count.AppendInt(int(mFileData->mFilesOrDirectories.Length()));
2409
2410 nsContentUtils::FormatMaybeLocalizedString(
2411 value, nsContentUtils::eFORMS_PROPERTIES, "XFilesSelected", OwnerDoc(),
2412 count);
2413 }
2414
2415 aValue = value;
2416 }
2417
2418 const nsTArray<OwningFileOrDirectory>&
GetFilesOrDirectoriesInternal() const2419 HTMLInputElement::GetFilesOrDirectoriesInternal() const {
2420 return mFileData->mFilesOrDirectories;
2421 }
2422
SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory> & aFilesOrDirectories,bool aSetValueChanged)2423 void HTMLInputElement::SetFilesOrDirectories(
2424 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
2425 bool aSetValueChanged) {
2426 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2427 return;
2428 }
2429
2430 MOZ_ASSERT(mFileData);
2431
2432 mFileData->ClearGetFilesHelpers();
2433
2434 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2435 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2436 mFileData->mEntries.Clear();
2437 }
2438
2439 mFileData->mFilesOrDirectories.Clear();
2440 mFileData->mFilesOrDirectories.AppendElements(aFilesOrDirectories);
2441
2442 AfterSetFilesOrDirectories(aSetValueChanged);
2443 }
2444
SetFiles(FileList * aFiles,bool aSetValueChanged)2445 void HTMLInputElement::SetFiles(FileList* aFiles, bool aSetValueChanged) {
2446 MOZ_ASSERT(mFileData);
2447
2448 mFileData->mFilesOrDirectories.Clear();
2449 mFileData->ClearGetFilesHelpers();
2450
2451 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2452 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2453 mFileData->mEntries.Clear();
2454 }
2455
2456 if (aFiles) {
2457 uint32_t listLength = aFiles->Length();
2458 for (uint32_t i = 0; i < listLength; i++) {
2459 OwningFileOrDirectory* element =
2460 mFileData->mFilesOrDirectories.AppendElement();
2461 element->SetAsFile() = aFiles->Item(i);
2462 }
2463 }
2464
2465 AfterSetFilesOrDirectories(aSetValueChanged);
2466 }
2467
2468 // This method is used for testing only.
MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory> & aFilesOrDirectories)2469 void HTMLInputElement::MozSetDndFilesAndDirectories(
2470 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories) {
2471 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2472 return;
2473 }
2474
2475 SetFilesOrDirectories(aFilesOrDirectories, true);
2476
2477 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2478 UpdateEntries(aFilesOrDirectories);
2479 }
2480
2481 RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
2482 new DispatchChangeEventCallback(this);
2483
2484 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2485 HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
2486 ErrorResult rv;
2487 GetFilesHelper* helper =
2488 GetOrCreateGetFilesHelper(true /* recursionFlag */, rv);
2489 if (NS_WARN_IF(rv.Failed())) {
2490 rv.SuppressException();
2491 return;
2492 }
2493
2494 helper->AddCallback(dispatchChangeEventCallback);
2495 } else {
2496 dispatchChangeEventCallback->DispatchEvents();
2497 }
2498 }
2499
AfterSetFilesOrDirectories(bool aSetValueChanged)2500 void HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged) {
2501 // No need to flush here, if there's no frame at this point we
2502 // don't need to force creation of one just to tell it about this
2503 // new value. We just want the display to update as needed.
2504 nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
2505 if (formControlFrame) {
2506 nsAutoString readableValue;
2507 GetDisplayFileName(readableValue);
2508 formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue);
2509 }
2510
2511 // Grab the full path here for any chrome callers who access our .value via a
2512 // CPOW. This path won't be called from a CPOW meaning the potential sync IPC
2513 // call under GetMozFullPath won't be rejected for not being urgent.
2514 // XXX Protected by the ifndef because the blob code doesn't allow us to send
2515 // this message in b2g.
2516 if (mFileData->mFilesOrDirectories.IsEmpty()) {
2517 mFileData->mFirstFilePath.Truncate();
2518 } else {
2519 ErrorResult rv;
2520 GetDOMFileOrDirectoryPath(mFileData->mFilesOrDirectories[0],
2521 mFileData->mFirstFilePath, rv);
2522 if (NS_WARN_IF(rv.Failed())) {
2523 rv.SuppressException();
2524 }
2525 }
2526
2527 UpdateFileList();
2528
2529 if (aSetValueChanged) {
2530 SetValueChanged(true);
2531 }
2532
2533 UpdateAllValidityStates(true);
2534 }
2535
FireChangeEventIfNeeded()2536 void HTMLInputElement::FireChangeEventIfNeeded() {
2537 // We're not exposing the GetValue return value anywhere here, so it's safe to
2538 // claim to be a system caller.
2539 nsAutoString value;
2540 GetValue(value, CallerType::System);
2541
2542 if (!MayFireChangeOnBlur() || mFocusedValue.Equals(value)) {
2543 return;
2544 }
2545
2546 // Dispatch the change event.
2547 mFocusedValue = value;
2548 nsContentUtils::DispatchTrustedEvent(
2549 OwnerDoc(), static_cast<nsIContent*>(this), u"change"_ns, CanBubble::eYes,
2550 Cancelable::eNo);
2551 }
2552
GetFiles()2553 FileList* HTMLInputElement::GetFiles() {
2554 if (mType != FormControlType::InputFile) {
2555 return nullptr;
2556 }
2557
2558 if (StaticPrefs::dom_input_dirpicker() && Allowdirs() &&
2559 (!StaticPrefs::dom_webkitBlink_dirPicker_enabled() ||
2560 !HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
2561 return nullptr;
2562 }
2563
2564 if (!mFileData->mFileList) {
2565 mFileData->mFileList = new FileList(static_cast<nsIContent*>(this));
2566 UpdateFileList();
2567 }
2568
2569 return mFileData->mFileList;
2570 }
2571
SetFiles(FileList * aFiles)2572 void HTMLInputElement::SetFiles(FileList* aFiles) {
2573 if (mType != FormControlType::InputFile || !aFiles) {
2574 return;
2575 }
2576
2577 // Clear |mFileData->mFileList| to omit |UpdateFileList|
2578 if (mFileData->mFileList) {
2579 mFileData->mFileList->Clear();
2580 mFileData->mFileList = nullptr;
2581 }
2582
2583 // Update |mFileData->mFilesOrDirectories|
2584 SetFiles(aFiles, true);
2585
2586 // Update |mFileData->mFileList| without copy
2587 mFileData->mFileList = aFiles;
2588 }
2589
2590 /* static */
HandleNumberControlSpin(void * aData)2591 void HTMLInputElement::HandleNumberControlSpin(void* aData) {
2592 RefPtr<HTMLInputElement> input = static_cast<HTMLInputElement*>(aData);
2593
2594 NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
2595 "Should have called nsRepeatService::Stop()");
2596
2597 nsNumberControlFrame* numberControlFrame =
2598 do_QueryFrame(input->GetPrimaryFrame());
2599 if (input->mType != FormControlType::InputNumber || !numberControlFrame) {
2600 // Type has changed (and possibly our frame type hasn't been updated yet)
2601 // or else we've lost our frame. Either way, stop the timer and don't do
2602 // anything else.
2603 input->StopNumberControlSpinnerSpin();
2604 } else {
2605 input->StepNumberControlForUserEvent(
2606 input->mNumberControlSpinnerSpinsUp ? 1 : -1);
2607 }
2608 }
2609
UpdateFileList()2610 void HTMLInputElement::UpdateFileList() {
2611 MOZ_ASSERT(mFileData);
2612
2613 if (mFileData->mFileList) {
2614 mFileData->mFileList->Clear();
2615
2616 const nsTArray<OwningFileOrDirectory>& array =
2617 GetFilesOrDirectoriesInternal();
2618
2619 for (uint32_t i = 0; i < array.Length(); ++i) {
2620 if (array[i].IsFile()) {
2621 mFileData->mFileList->Append(array[i].GetAsFile());
2622 }
2623 }
2624 }
2625 }
2626
SetValueInternal(const nsAString & aValue,const nsAString * aOldValue,const ValueSetterOptions & aOptions)2627 nsresult HTMLInputElement::SetValueInternal(
2628 const nsAString& aValue, const nsAString* aOldValue,
2629 const ValueSetterOptions& aOptions) {
2630 MOZ_ASSERT(GetValueMode() != VALUE_MODE_FILENAME,
2631 "Don't call SetValueInternal for file inputs");
2632
2633 // We want to remember if the SetValueInternal() call is being made for a XUL
2634 // element. We do that by looking at the parent node here, and if that node
2635 // is a XUL node, we consider our control a XUL control. XUL controls preserve
2636 // edit history across value setters.
2637 //
2638 // TODO(emilio): Rather than doing this maybe add an attribute instead and
2639 // read it only on chrome docs or something? That'd allow front-end code to
2640 // move away from xul without weird side-effects.
2641 const bool forcePreserveUndoHistory = mParent && mParent->IsXULElement();
2642
2643 switch (GetValueMode()) {
2644 case VALUE_MODE_VALUE: {
2645 // At the moment, only single line text control have to sanitize their
2646 // value Because we have to create a new string for that, we should
2647 // prevent doing it if it's useless.
2648 nsAutoString value(aValue);
2649
2650 if (mDoneCreating) {
2651 SanitizeValue(value);
2652 }
2653 // else DoneCreatingElement calls us again once mDoneCreating is true
2654
2655 const bool setValueChanged =
2656 aOptions.contains(ValueSetterOption::SetValueChanged);
2657 if (setValueChanged) {
2658 SetValueChanged(true);
2659 }
2660
2661 if (IsSingleLineTextControl(false)) {
2662 // Note that if aOptions includes
2663 // ValueSetterOption::BySetUserInputAPI, "input" event is automatically
2664 // dispatched by TextControlState::SetValue(). If you'd change condition
2665 // of calling this method, you need to maintain SetUserInput() too. FYI:
2666 // After calling SetValue(), the input type might have been
2667 // modified so that mInputData may not store TextControlState.
2668 if (!mInputData.mState->SetValue(
2669 value, aOldValue,
2670 forcePreserveUndoHistory
2671 ? aOptions + ValueSetterOption::PreserveUndoHistory
2672 : aOptions)) {
2673 return NS_ERROR_OUT_OF_MEMORY;
2674 }
2675 // If the caller won't dispatch "input" event via
2676 // nsContentUtils::DispatchInputEvent(), we need to modify
2677 // validationMessage value here.
2678 //
2679 // FIXME(emilio): ValueSetterOption::ByInternalAPI is not supposed to
2680 // change state, but maybe we could run this too?
2681 if (aOptions.contains(ValueSetterOption::ByContentAPI)) {
2682 MaybeUpdateAllValidityStates(!mDoneCreating);
2683 }
2684 } else {
2685 free(mInputData.mValue);
2686 mInputData.mValue = ToNewUnicode(value);
2687 if (setValueChanged) {
2688 SetValueChanged(true);
2689 }
2690 if (mType == FormControlType::InputRange) {
2691 nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
2692 if (frame) {
2693 frame->UpdateForValueChange();
2694 }
2695 } else if (CreatesDateTimeWidget() &&
2696 !aOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
2697 if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
2698 AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
2699 dateTimeBoxElement, u"MozDateTimeValueChanged"_ns,
2700 CanBubble::eNo, ChromeOnlyDispatch::eNo);
2701 dispatcher->RunDOMEventWhenSafe();
2702 }
2703 }
2704 if (mDoneCreating) {
2705 OnValueChanged(ValueChangeKind::Internal);
2706 }
2707 // else DoneCreatingElement calls us again once mDoneCreating is true
2708 }
2709
2710 if (mType == FormControlType::InputColor) {
2711 // Update color frame, to reflect color changes
2712 nsColorControlFrame* colorControlFrame =
2713 do_QueryFrame(GetPrimaryFrame());
2714 if (colorControlFrame) {
2715 colorControlFrame->UpdateColor();
2716 }
2717 }
2718
2719 // This call might be useless in some situations because if the element is
2720 // a single line text control, TextControlState::SetValue will call
2721 // nsHTMLInputElement::OnValueChanged which is going to call UpdateState()
2722 // if the element is focused. This bug 665547.
2723 if (PlaceholderApplies() && HasAttr(nsGkAtoms::placeholder)) {
2724 UpdateState(true);
2725 }
2726
2727 return NS_OK;
2728 }
2729
2730 case VALUE_MODE_DEFAULT:
2731 case VALUE_MODE_DEFAULT_ON:
2732 // If the value of a hidden input was changed, we mark it changed so that
2733 // we will know we need to save / restore the value. Yes, we are
2734 // overloading the meaning of ValueChanged just a teensy bit to save a
2735 // measly byte of storage space in HTMLInputElement. Yes, you are free to
2736 // make a new flag, NEED_TO_SAVE_VALUE, at such time as mBitField becomes
2737 // a 16-bit value.
2738 if (mType == FormControlType::InputHidden) {
2739 SetValueChanged(true);
2740 }
2741
2742 // Treat value == defaultValue for other input elements.
2743 return nsGenericHTMLFormElementWithState::SetAttr(
2744 kNameSpaceID_None, nsGkAtoms::value, aValue, true);
2745
2746 case VALUE_MODE_FILENAME:
2747 return NS_ERROR_UNEXPECTED;
2748 }
2749
2750 // This return statement is required for some compilers.
2751 return NS_OK;
2752 }
2753
SetValueChanged(bool aValueChanged)2754 nsresult HTMLInputElement::SetValueChanged(bool aValueChanged) {
2755 if (mValueChanged == aValueChanged) {
2756 return NS_OK;
2757 }
2758 mValueChanged = aValueChanged;
2759 UpdateTooLongValidityState();
2760 UpdateTooShortValidityState();
2761 // We need to do this unconditionally because the validity ui bits depend on
2762 // this.
2763 UpdateState(true);
2764 return NS_OK;
2765 }
2766
SetLastValueChangeWasInteractive(bool aWasInteractive)2767 void HTMLInputElement::SetLastValueChangeWasInteractive(bool aWasInteractive) {
2768 if (aWasInteractive == mLastValueChangeWasInteractive) {
2769 return;
2770 }
2771 mLastValueChangeWasInteractive = aWasInteractive;
2772 const bool wasValid = IsValid();
2773 UpdateTooLongValidityState();
2774 UpdateTooShortValidityState();
2775 if (wasValid != IsValid()) {
2776 UpdateState(true);
2777 }
2778 }
2779
SetCheckedChanged(bool aCheckedChanged)2780 void HTMLInputElement::SetCheckedChanged(bool aCheckedChanged) {
2781 DoSetCheckedChanged(aCheckedChanged, true);
2782 }
2783
DoSetCheckedChanged(bool aCheckedChanged,bool aNotify)2784 void HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged, bool aNotify) {
2785 if (mType == FormControlType::InputRadio) {
2786 if (mCheckedChanged != aCheckedChanged) {
2787 nsCOMPtr<nsIRadioVisitor> visitor =
2788 new nsRadioSetCheckedChangedVisitor(aCheckedChanged);
2789 VisitGroup(visitor);
2790 }
2791 } else {
2792 SetCheckedChangedInternal(aCheckedChanged);
2793 }
2794 }
2795
SetCheckedChangedInternal(bool aCheckedChanged)2796 void HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged) {
2797 bool checkedChangedBefore = mCheckedChanged;
2798
2799 mCheckedChanged = aCheckedChanged;
2800
2801 // This method can't be called when we are not authorized to notify
2802 // so we do not need a aNotify parameter.
2803 if (checkedChangedBefore != aCheckedChanged) {
2804 UpdateState(true);
2805 }
2806 }
2807
SetChecked(bool aChecked)2808 void HTMLInputElement::SetChecked(bool aChecked) {
2809 DoSetChecked(aChecked, true, true);
2810 }
2811
DoSetChecked(bool aChecked,bool aNotify,bool aSetValueChanged)2812 void HTMLInputElement::DoSetChecked(bool aChecked, bool aNotify,
2813 bool aSetValueChanged) {
2814 // If the user or JS attempts to set checked, whether it actually changes the
2815 // value or not, we say the value was changed so that defaultValue don't
2816 // affect it no more.
2817 if (aSetValueChanged) {
2818 DoSetCheckedChanged(true, aNotify);
2819 }
2820
2821 // Don't do anything if we're not changing whether it's checked (it would
2822 // screw up state actually, especially when you are setting radio button to
2823 // false)
2824 if (mChecked == aChecked) {
2825 return;
2826 }
2827
2828 // Set checked
2829 if (mType != FormControlType::InputRadio) {
2830 SetCheckedInternal(aChecked, aNotify);
2831 return;
2832 }
2833
2834 // For radio button, we need to do some extra fun stuff
2835 if (aChecked) {
2836 RadioSetChecked(aNotify);
2837 return;
2838 }
2839
2840 nsIRadioGroupContainer* container = GetRadioGroupContainer();
2841 if (container) {
2842 nsAutoString name;
2843 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
2844 container->SetCurrentRadioButton(name, nullptr);
2845 }
2846 // SetCheckedInternal is going to ask all radios to update their
2847 // validity state. We have to be sure the radio group container knows
2848 // the currently selected radio.
2849 SetCheckedInternal(false, aNotify);
2850 }
2851
RadioSetChecked(bool aNotify)2852 void HTMLInputElement::RadioSetChecked(bool aNotify) {
2853 // Find the selected radio button so we can deselect it
2854 HTMLInputElement* currentlySelected = GetSelectedRadioButton();
2855
2856 // Deselect the currently selected radio button
2857 if (currentlySelected) {
2858 // Pass true for the aNotify parameter since the currently selected
2859 // button is already in the document.
2860 currentlySelected->SetCheckedInternal(false, true);
2861 }
2862
2863 // Let the group know that we are now the One True Radio Button
2864 nsIRadioGroupContainer* container = GetRadioGroupContainer();
2865 if (container) {
2866 nsAutoString name;
2867 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
2868 container->SetCurrentRadioButton(name, this);
2869 }
2870
2871 // SetCheckedInternal is going to ask all radios to update their
2872 // validity state.
2873 SetCheckedInternal(true, aNotify);
2874 }
2875
GetRadioGroupContainer() const2876 nsIRadioGroupContainer* HTMLInputElement::GetRadioGroupContainer() const {
2877 NS_ASSERTION(
2878 mType == FormControlType::InputRadio,
2879 "GetRadioGroupContainer should only be called when type='radio'");
2880
2881 nsAutoString name;
2882 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
2883
2884 if (name.IsEmpty()) {
2885 return nullptr;
2886 }
2887
2888 if (mForm) {
2889 return mForm;
2890 }
2891
2892 if (IsInNativeAnonymousSubtree()) {
2893 return nullptr;
2894 }
2895
2896 DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
2897 if (!docOrShadow) {
2898 return nullptr;
2899 }
2900
2901 nsCOMPtr<nsIRadioGroupContainer> container =
2902 do_QueryInterface(&(docOrShadow->AsNode()));
2903 return container;
2904 }
2905
GetSelectedRadioButton() const2906 HTMLInputElement* HTMLInputElement::GetSelectedRadioButton() const {
2907 nsIRadioGroupContainer* container = GetRadioGroupContainer();
2908 if (!container) {
2909 return nullptr;
2910 }
2911
2912 nsAutoString name;
2913 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
2914
2915 HTMLInputElement* selected = container->GetCurrentRadioButton(name);
2916 return selected;
2917 }
2918
MaybeSubmitForm(nsPresContext * aPresContext)2919 nsresult HTMLInputElement::MaybeSubmitForm(nsPresContext* aPresContext) {
2920 if (!mForm) {
2921 // Nothing to do here.
2922 return NS_OK;
2923 }
2924
2925 RefPtr<PresShell> presShell = aPresContext->GetPresShell();
2926 if (!presShell) {
2927 return NS_OK;
2928 }
2929
2930 // Get the default submit element
2931 nsIFormControl* submitControl = mForm->GetDefaultSubmitElement();
2932 if (submitControl) {
2933 nsCOMPtr<nsIContent> submitContent = do_QueryInterface(submitControl);
2934 NS_ASSERTION(submitContent, "Form control not implementing nsIContent?!");
2935 // Fire the button's onclick handler and let the button handle
2936 // submitting the form.
2937 WidgetMouseEvent event(true, eMouseClick, nullptr, WidgetMouseEvent::eReal);
2938 nsEventStatus status = nsEventStatus_eIgnore;
2939 presShell->HandleDOMEventWithTarget(submitContent, &event, &status);
2940 } else if (!mForm->ImplicitSubmissionIsDisabled()) {
2941 // If there's only one text control, just submit the form
2942 // Hold strong ref across the event
2943 RefPtr<mozilla::dom::HTMLFormElement> form(mForm);
2944 form->MaybeSubmit(nullptr);
2945 }
2946
2947 return NS_OK;
2948 }
2949
SetCheckedInternal(bool aChecked,bool aNotify)2950 void HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify) {
2951 // Set the value
2952 mChecked = aChecked;
2953
2954 // Notify the frame
2955 if (mType == FormControlType::InputCheckbox ||
2956 mType == FormControlType::InputRadio) {
2957 nsIFrame* frame = GetPrimaryFrame();
2958 if (frame) {
2959 frame->InvalidateFrameSubtree();
2960 }
2961 }
2962
2963 // No need to update element state, since we're about to call
2964 // UpdateState anyway.
2965 UpdateAllValidityStatesButNotElementState();
2966
2967 // Notify the document that the CSS :checked pseudoclass for this element
2968 // has changed state.
2969 UpdateState(aNotify);
2970
2971 // Notify all radios in the group that value has changed, this is to let
2972 // radios to have the chance to update its states, e.g., :indeterminate.
2973 if (mType == FormControlType::InputRadio) {
2974 nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
2975 VisitGroup(visitor);
2976 }
2977 }
2978
Blur(ErrorResult & aError)2979 void HTMLInputElement::Blur(ErrorResult& aError) {
2980 if (CreatesDateTimeWidget()) {
2981 if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
2982 AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
2983 dateTimeBoxElement, u"MozBlurInnerTextBox"_ns, CanBubble::eNo,
2984 ChromeOnlyDispatch::eNo);
2985 dispatcher->RunDOMEventWhenSafe();
2986 return;
2987 }
2988 }
2989
2990 nsGenericHTMLElement::Blur(aError);
2991 }
2992
Focus(const FocusOptions & aOptions,CallerType aCallerType,ErrorResult & aError)2993 void HTMLInputElement::Focus(const FocusOptions& aOptions,
2994 CallerType aCallerType, ErrorResult& aError) {
2995 if (CreatesDateTimeWidget()) {
2996 if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
2997 AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
2998 dateTimeBoxElement, u"MozFocusInnerTextBox"_ns, CanBubble::eNo,
2999 ChromeOnlyDispatch::eNo);
3000 dispatcher->RunDOMEventWhenSafe();
3001 return;
3002 }
3003 }
3004
3005 nsGenericHTMLElement::Focus(aOptions, aCallerType, aError);
3006 }
3007
3008 #if !defined(ANDROID) && !defined(XP_MACOSX)
IsNodeApzAwareInternal() const3009 bool HTMLInputElement::IsNodeApzAwareInternal() const {
3010 // Tell APZC we may handle mouse wheel event and do preventDefault when input
3011 // type is number.
3012 return mType == FormControlType::InputNumber ||
3013 mType == FormControlType::InputRange ||
3014 nsINode::IsNodeApzAwareInternal();
3015 }
3016 #endif
3017
IsInteractiveHTMLContent() const3018 bool HTMLInputElement::IsInteractiveHTMLContent() const {
3019 return mType != FormControlType::InputHidden ||
3020 nsGenericHTMLFormElementWithState::IsInteractiveHTMLContent();
3021 }
3022
AsyncEventRunning(AsyncEventDispatcher * aEvent)3023 void HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
3024 nsImageLoadingContent::AsyncEventRunning(aEvent);
3025 }
3026
Select()3027 void HTMLInputElement::Select() {
3028 if (!IsSingleLineTextControl(false)) {
3029 return;
3030 }
3031
3032 TextControlState* state = GetEditorState();
3033 MOZ_ASSERT(state, "Single line text controls are expected to have a state");
3034
3035 if (FocusState() != eUnfocusable) {
3036 RefPtr<nsFrameSelection> fs = state->GetConstFrameSelection();
3037 if (fs && fs->MouseDownRecorded()) {
3038 // This means that we're being called while the frame selection has a
3039 // mouse down event recorded to adjust the caret during the mouse up
3040 // event. We are probably called from the focus event handler. We should
3041 // override the delayed caret data in this case to ensure that this
3042 // select() call takes effect.
3043 fs->SetDelayedCaretData(nullptr);
3044 }
3045
3046 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
3047 fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
3048
3049 // A focus event handler may change the type attribute, which will destroy
3050 // the previous state object.
3051 state = GetEditorState();
3052 if (!state) {
3053 return;
3054 }
3055 }
3056 }
3057
3058 // Directly call TextControlState::SetSelectionRange because
3059 // HTMLInputElement::SetSelectionRange only applies to fewer types
3060 state->SetSelectionRange(0, UINT32_MAX, Optional<nsAString>(), IgnoreErrors(),
3061 TextControlState::ScrollAfterSelection::No);
3062 }
3063
DispatchSelectEvent(nsPresContext * aPresContext)3064 void HTMLInputElement::DispatchSelectEvent(nsPresContext* aPresContext) {
3065 // If already handling select event, don't dispatch a second.
3066 if (!mHandlingSelectEvent) {
3067 WidgetEvent event(true, eFormSelect);
3068
3069 mHandlingSelectEvent = true;
3070 EventDispatcher::Dispatch(static_cast<nsIContent*>(this), aPresContext,
3071 &event);
3072 mHandlingSelectEvent = false;
3073 }
3074 }
3075
SelectAll(nsPresContext * aPresContext)3076 void HTMLInputElement::SelectAll(nsPresContext* aPresContext) {
3077 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
3078
3079 if (formControlFrame) {
3080 formControlFrame->SetFormProperty(nsGkAtoms::select, u""_ns);
3081 }
3082 }
3083
NeedToInitializeEditorForEvent(EventChainPreVisitor & aVisitor) const3084 bool HTMLInputElement::NeedToInitializeEditorForEvent(
3085 EventChainPreVisitor& aVisitor) const {
3086 // We only need to initialize the editor for single line input controls
3087 // because they are lazily initialized. We don't need to initialize the
3088 // control for certain types of events, because we know that those events are
3089 // safe to be handled without the editor being initialized. These events
3090 // include: mousein/move/out, overflow/underflow, DOM mutation, and void
3091 // events. Void events are dispatched frequently by async keyboard scrolling
3092 // to focused elements, so it's important to handle them to prevent excessive
3093 // DOM mutations.
3094 if (!IsSingleLineTextControl(false) ||
3095 aVisitor.mEvent->mClass == eMutationEventClass) {
3096 return false;
3097 }
3098
3099 switch (aVisitor.mEvent->mMessage) {
3100 case eVoidEvent:
3101 case eMouseMove:
3102 case eMouseEnterIntoWidget:
3103 case eMouseExitFromWidget:
3104 case eMouseOver:
3105 case eMouseOut:
3106 case eScrollPortUnderflow:
3107 case eScrollPortOverflow:
3108 return false;
3109 default:
3110 return true;
3111 }
3112 }
3113
IsDisabledForEvents(WidgetEvent * aEvent)3114 bool HTMLInputElement::IsDisabledForEvents(WidgetEvent* aEvent) {
3115 return IsElementDisabledForEvents(aEvent, GetPrimaryFrame());
3116 }
3117
GetEventTargetParent(EventChainPreVisitor & aVisitor)3118 void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
3119 // Do not process any DOM events if the element is disabled
3120 aVisitor.mCanHandle = false;
3121 if (IsDisabledForEvents(aVisitor.mEvent)) {
3122 return;
3123 }
3124
3125 // Initialize the editor if needed.
3126 if (NeedToInitializeEditorForEvent(aVisitor)) {
3127 nsITextControlFrame* textControlFrame = do_QueryFrame(GetPrimaryFrame());
3128 if (textControlFrame) textControlFrame->EnsureEditorInitialized();
3129 }
3130
3131 //
3132 // Web pages expect the value of a radio button or checkbox to be set
3133 // *before* onclick and DOMActivate fire, and they expect that if they set
3134 // the value explicitly during onclick or DOMActivate it will not be toggled
3135 // or any such nonsense.
3136 // In order to support that (bug 57137 and 58460 are examples) we toggle
3137 // the checked attribute *first*, and then fire onclick. If the user
3138 // returns false, we reset the control to the old checked value. Otherwise,
3139 // we dispatch DOMActivate. If DOMActivate is cancelled, we also reset
3140 // the control to the old checked value. We need to keep track of whether
3141 // we've already toggled the state from onclick since the user could
3142 // explicitly dispatch DOMActivate on the element.
3143 //
3144 // These are compatibility hacks and are defined as legacy-pre-activation
3145 // and legacy-canceled-activation behavior in HTML.
3146 //
3147
3148 // Track whether we're in the outermost Dispatch invocation that will
3149 // cause activation of the input. That is, if we're a click event, or a
3150 // DOMActivate that was dispatched directly, this will be set, but if we're
3151 // a DOMActivate dispatched from click handling, it will not be set.
3152 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3153 bool outerActivateEvent = ((mouseEvent && mouseEvent->IsLeftClickEvent()) ||
3154 (aVisitor.mEvent->mMessage == eLegacyDOMActivate &&
3155 !mInInternalActivate));
3156
3157 if (outerActivateEvent) {
3158 aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
3159 }
3160
3161 bool originalCheckedValue = false;
3162
3163 if (outerActivateEvent) {
3164 mCheckedIsToggled = false;
3165
3166 switch (mType) {
3167 case FormControlType::InputCheckbox: {
3168 if (mIndeterminate) {
3169 // indeterminate is always set to FALSE when the checkbox is toggled
3170 SetIndeterminateInternal(false, false);
3171 aVisitor.mItemFlags |= NS_ORIGINAL_INDETERMINATE_VALUE;
3172 }
3173
3174 originalCheckedValue = Checked();
3175 DoSetChecked(!originalCheckedValue, true, true);
3176 mCheckedIsToggled = true;
3177 } break;
3178
3179 case FormControlType::InputRadio: {
3180 HTMLInputElement* selectedRadioButton = GetSelectedRadioButton();
3181 aVisitor.mItemData = static_cast<Element*>(selectedRadioButton);
3182
3183 originalCheckedValue = mChecked;
3184 if (!originalCheckedValue) {
3185 DoSetChecked(true, true, true);
3186 mCheckedIsToggled = true;
3187 }
3188 } break;
3189
3190 case FormControlType::InputSubmit:
3191 case FormControlType::InputImage:
3192 if (mForm && !aVisitor.mEvent->mFlags.mMultiplePreActionsPrevented) {
3193 // Make sure other submit elements don't try to trigger submission.
3194 aVisitor.mEvent->mFlags.mMultiplePreActionsPrevented = true;
3195 aVisitor.mItemFlags |= NS_IN_SUBMIT_CLICK;
3196 // tell the form that we are about to enter a click handler.
3197 // that means that if there are scripted submissions, the
3198 // latest one will be deferred until after the exit point of the
3199 // handler.
3200 mForm->OnSubmitClickBegin(this);
3201 }
3202 break;
3203
3204 default:
3205 break;
3206 }
3207 }
3208
3209 if (originalCheckedValue) {
3210 aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE;
3211 }
3212
3213 // We must cache type because mType may change during JS event (bug 2369)
3214 aVisitor.mItemFlags |= uint8_t(mType);
3215
3216 if (aVisitor.mEvent->mMessage == eFocus && aVisitor.mEvent->IsTrusted() &&
3217 MayFireChangeOnBlur() &&
3218 // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before
3219 // we get the 'focus' event.
3220 !mIsDraggingRange) {
3221 GetValue(mFocusedValue, CallerType::System);
3222 }
3223
3224 // Fire onchange (if necessary), before we do the blur, bug 357684.
3225 if (aVisitor.mEvent->mMessage == eBlur) {
3226 // We set NS_PRE_HANDLE_BLUR_EVENT here and handle it in PreHandleEvent to
3227 // prevent breaking event target chain creation.
3228 aVisitor.mWantsPreHandleEvent = true;
3229 aVisitor.mItemFlags |= NS_PRE_HANDLE_BLUR_EVENT;
3230 }
3231
3232 if (mType == FormControlType::InputRange &&
3233 (aVisitor.mEvent->mMessage == eFocus ||
3234 aVisitor.mEvent->mMessage == eBlur)) {
3235 // Just as nsGenericHTMLFormElementWithState::GetEventTargetParent calls
3236 // nsIFormControlFrame::SetFocus, we handle focus here.
3237 nsIFrame* frame = GetPrimaryFrame();
3238 if (frame) {
3239 frame->InvalidateFrameSubtree();
3240 }
3241 }
3242
3243 if (CreatesDateTimeWidget() && aVisitor.mEvent->mMessage == eFocus &&
3244 aVisitor.mEvent->mOriginalTarget == this) {
3245 // If original target is this and not the inner text control, we should
3246 // pass the focus to the inner text control.
3247 if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
3248 AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
3249 dateTimeBoxElement, u"MozFocusInnerTextBox"_ns, CanBubble::eNo,
3250 ChromeOnlyDispatch::eNo);
3251 dispatcher->RunDOMEventWhenSafe();
3252 }
3253 }
3254
3255 if (mType == FormControlType::InputNumber && aVisitor.mEvent->IsTrusted()) {
3256 if (mNumberControlSpinnerIsSpinning) {
3257 // If the timer is running the user has depressed the mouse on one of the
3258 // spin buttons. If the mouse exits the button we either want to reverse
3259 // the direction of spin if it has moved over the other button, or else
3260 // we want to end the spin. We do this here (rather than in
3261 // PostHandleEvent) because we don't want to let content preventDefault()
3262 // the end of the spin.
3263 if (aVisitor.mEvent->mMessage == eMouseMove) {
3264 // Be aggressive about stopping the spin:
3265 bool stopSpin = true;
3266 nsNumberControlFrame* numberControlFrame =
3267 do_QueryFrame(GetPrimaryFrame());
3268 if (numberControlFrame) {
3269 bool oldNumberControlSpinTimerSpinsUpValue =
3270 mNumberControlSpinnerSpinsUp;
3271 switch (numberControlFrame->GetSpinButtonForPointerEvent(
3272 aVisitor.mEvent->AsMouseEvent())) {
3273 case nsNumberControlFrame::eSpinButtonUp:
3274 mNumberControlSpinnerSpinsUp = true;
3275 stopSpin = false;
3276 break;
3277 case nsNumberControlFrame::eSpinButtonDown:
3278 mNumberControlSpinnerSpinsUp = false;
3279 stopSpin = false;
3280 break;
3281 }
3282 if (mNumberControlSpinnerSpinsUp !=
3283 oldNumberControlSpinTimerSpinsUpValue) {
3284 nsNumberControlFrame* numberControlFrame =
3285 do_QueryFrame(GetPrimaryFrame());
3286 if (numberControlFrame) {
3287 numberControlFrame->SpinnerStateChanged();
3288 }
3289 }
3290 }
3291 if (stopSpin) {
3292 StopNumberControlSpinnerSpin();
3293 }
3294 } else if (aVisitor.mEvent->mMessage == eMouseUp) {
3295 StopNumberControlSpinnerSpin();
3296 }
3297 }
3298 }
3299
3300 nsGenericHTMLFormElementWithState::GetEventTargetParent(aVisitor);
3301
3302 // Stop the event if the related target's first non-native ancestor is the
3303 // same as the original target's first non-native ancestor (we are moving
3304 // inside of the same element).
3305 //
3306 // FIXME(emilio): Is this still needed now that we use Shadow DOM for this?
3307 if (CreatesDateTimeWidget() && aVisitor.mEvent->IsTrusted() &&
3308 (aVisitor.mEvent->mMessage == eFocus ||
3309 aVisitor.mEvent->mMessage == eFocusIn ||
3310 aVisitor.mEvent->mMessage == eFocusOut ||
3311 aVisitor.mEvent->mMessage == eBlur)) {
3312 nsCOMPtr<nsIContent> originalTarget =
3313 do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mOriginalTarget);
3314 nsCOMPtr<nsIContent> relatedTarget =
3315 do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
3316
3317 if (originalTarget && relatedTarget &&
3318 originalTarget->FindFirstNonChromeOnlyAccessContent() ==
3319 relatedTarget->FindFirstNonChromeOnlyAccessContent()) {
3320 aVisitor.mCanHandle = false;
3321 }
3322 }
3323 }
3324
PreHandleEvent(EventChainVisitor & aVisitor)3325 nsresult HTMLInputElement::PreHandleEvent(EventChainVisitor& aVisitor) {
3326 if (aVisitor.mItemFlags & NS_PRE_HANDLE_BLUR_EVENT) {
3327 MOZ_ASSERT(aVisitor.mEvent->mMessage == eBlur);
3328 FireChangeEventIfNeeded();
3329 }
3330 return nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
3331 }
3332
StartRangeThumbDrag(WidgetGUIEvent * aEvent)3333 void HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent* aEvent) {
3334 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
3335 if (!rangeFrame) {
3336 return;
3337 }
3338
3339 mIsDraggingRange = true;
3340 mRangeThumbDragStartValue = GetValueAsDecimal();
3341 // Don't use CaptureFlags::RetargetToElement, as that breaks pseudo-class
3342 // styling of the thumb.
3343 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
3344
3345 // Before we change the value, record the current value so that we'll
3346 // correctly send a 'change' event if appropriate. We need to do this here
3347 // because the 'focus' event is handled after the 'mousedown' event that
3348 // we're being called for (i.e. too late to update mFocusedValue, since we'll
3349 // have changed it by then).
3350 GetValue(mFocusedValue, CallerType::System);
3351
3352 SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
3353 }
3354
FinishRangeThumbDrag(WidgetGUIEvent * aEvent)3355 void HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent* aEvent) {
3356 MOZ_ASSERT(mIsDraggingRange);
3357
3358 if (PresShell::GetCapturingContent() == this) {
3359 PresShell::ReleaseCapturingContent();
3360 }
3361 if (aEvent) {
3362 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
3363 SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
3364 }
3365 mIsDraggingRange = false;
3366 FireChangeEventIfNeeded();
3367 }
3368
CancelRangeThumbDrag(bool aIsForUserEvent)3369 void HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent) {
3370 MOZ_ASSERT(mIsDraggingRange);
3371
3372 mIsDraggingRange = false;
3373 if (PresShell::GetCapturingContent() == this) {
3374 PresShell::ReleaseCapturingContent();
3375 }
3376 if (aIsForUserEvent) {
3377 SetValueOfRangeForUserEvent(mRangeThumbDragStartValue);
3378 } else {
3379 // Don't dispatch an 'input' event - at least not using
3380 // DispatchTrustedEvent.
3381 // TODO: decide what we should do here - bug 851782.
3382 nsAutoString val;
3383 mInputType->ConvertNumberToString(mRangeThumbDragStartValue, val);
3384 // TODO: What should we do if SetValueInternal fails? (The allocation
3385 // is small, so we should be fine here.)
3386 SetValueInternal(val, {ValueSetterOption::BySetUserInputAPI,
3387 ValueSetterOption::SetValueChanged});
3388 nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
3389 if (frame) {
3390 frame->UpdateForValueChange();
3391 }
3392 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
3393 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3394 "Failed to dispatch input event");
3395 }
3396 }
3397
SetValueOfRangeForUserEvent(Decimal aValue)3398 void HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue) {
3399 MOZ_ASSERT(aValue.isFinite());
3400
3401 Decimal oldValue = GetValueAsDecimal();
3402
3403 nsAutoString val;
3404 mInputType->ConvertNumberToString(aValue, val);
3405 // TODO: What should we do if SetValueInternal fails? (The allocation
3406 // is small, so we should be fine here.)
3407 SetValueInternal(val, {ValueSetterOption::BySetUserInputAPI,
3408 ValueSetterOption::SetValueChanged});
3409 nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
3410 if (frame) {
3411 frame->UpdateForValueChange();
3412 }
3413
3414 if (GetValueAsDecimal() != oldValue) {
3415 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
3416 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3417 "Failed to dispatch input event");
3418 }
3419 }
3420
StartNumberControlSpinnerSpin()3421 void HTMLInputElement::StartNumberControlSpinnerSpin() {
3422 MOZ_ASSERT(!mNumberControlSpinnerIsSpinning);
3423
3424 mNumberControlSpinnerIsSpinning = true;
3425
3426 nsRepeatService::GetInstance()->Start(
3427 HandleNumberControlSpin, this, OwnerDoc(), "HandleNumberControlSpin"_ns);
3428
3429 // Capture the mouse so that we can tell if the pointer moves from one
3430 // spin button to the other, or to some other element:
3431 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
3432
3433 nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
3434 if (numberControlFrame) {
3435 numberControlFrame->SpinnerStateChanged();
3436 }
3437 }
3438
StopNumberControlSpinnerSpin(SpinnerStopState aState)3439 void HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState) {
3440 if (mNumberControlSpinnerIsSpinning) {
3441 if (PresShell::GetCapturingContent() == this) {
3442 PresShell::ReleaseCapturingContent();
3443 }
3444
3445 nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
3446
3447 mNumberControlSpinnerIsSpinning = false;
3448
3449 if (aState == eAllowDispatchingEvents) {
3450 FireChangeEventIfNeeded();
3451 }
3452
3453 nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
3454 if (numberControlFrame) {
3455 MOZ_ASSERT(aState == eAllowDispatchingEvents,
3456 "Shouldn't have primary frame for the element when we're not "
3457 "allowed to dispatch events to it anymore.");
3458 numberControlFrame->SpinnerStateChanged();
3459 }
3460 }
3461 }
3462
StepNumberControlForUserEvent(int32_t aDirection)3463 void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) {
3464 // We can't use GetValidityState here because the validity state is not set
3465 // if the user hasn't previously taken an action to set or change the value,
3466 // according to the specs.
3467 if (HasBadInput()) {
3468 // If the user has typed a value into the control and inadvertently made a
3469 // mistake (e.g. put a thousand separator at the wrong point) we do not
3470 // want to wipe out what they typed if they try to increment/decrement the
3471 // value. Better is to highlight the value as being invalid so that they
3472 // can correct what they typed.
3473 // We only do this if there actually is a value typed in by/displayed to
3474 // the user. (IsValid() can return false if the 'required' attribute is
3475 // set and the value is the empty string.)
3476 if (!IsValueEmpty()) {
3477 // We pass 'true' for UpdateValidityUIBits' aIsFocused argument
3478 // regardless because we need the UI to update _now_ or the user will
3479 // wonder why the step behavior isn't functioning.
3480 UpdateValidityUIBits(true);
3481 UpdateState(true);
3482 return;
3483 }
3484 }
3485
3486 Decimal newValue = Decimal::nan(); // unchanged if value will not change
3487
3488 nsresult rv = GetValueIfStepped(aDirection, CALLED_FOR_USER_EVENT, &newValue);
3489
3490 if (NS_FAILED(rv) || !newValue.isFinite()) {
3491 return; // value should not or will not change
3492 }
3493
3494 nsAutoString newVal;
3495 mInputType->ConvertNumberToString(newValue, newVal);
3496 // TODO: What should we do if SetValueInternal fails? (The allocation
3497 // is small, so we should be fine here.)
3498 SetValueInternal(newVal, {ValueSetterOption::BySetUserInputAPI,
3499 ValueSetterOption::SetValueChanged});
3500 }
3501
SelectTextFieldOnFocus()3502 static bool SelectTextFieldOnFocus() {
3503 if (!gSelectTextFieldOnFocus) {
3504 int32_t selectTextfieldsOnKeyFocus = -1;
3505 nsresult rv =
3506 LookAndFeel::GetInt(LookAndFeel::IntID::SelectTextfieldsOnKeyFocus,
3507 &selectTextfieldsOnKeyFocus);
3508 if (NS_FAILED(rv)) {
3509 gSelectTextFieldOnFocus = -1;
3510 } else {
3511 gSelectTextFieldOnFocus = selectTextfieldsOnKeyFocus != 0 ? 1 : -1;
3512 }
3513 }
3514
3515 return gSelectTextFieldOnFocus == 1;
3516 }
3517
ShouldPreventDOMActivateDispatch(EventTarget * aOriginalTarget)3518 bool HTMLInputElement::ShouldPreventDOMActivateDispatch(
3519 EventTarget* aOriginalTarget) {
3520 /*
3521 * For the moment, there is only one situation where we actually want to
3522 * prevent firing a DOMActivate event:
3523 * - we are a <input type='file'> that just got a click event,
3524 * - the event was targeted to our button which should have sent a
3525 * DOMActivate event.
3526 */
3527
3528 if (mType != FormControlType::InputFile) {
3529 return false;
3530 }
3531
3532 nsCOMPtr<Element> target = do_QueryInterface(aOriginalTarget);
3533 if (!target) {
3534 return false;
3535 }
3536
3537 return target->GetParent() == this &&
3538 target->IsRootOfNativeAnonymousSubtree() &&
3539 target->IsHTMLElement(nsGkAtoms::button);
3540 }
3541
MaybeInitPickers(EventChainPostVisitor & aVisitor)3542 nsresult HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor) {
3543 // Open a file picker when we receive a click on a <input type='file'>, or
3544 // open a color picker when we receive a click on a <input type='color'>.
3545 // A click is handled in the following cases:
3546 // - preventDefault() has not been called (or something similar);
3547 // - it's the left mouse button.
3548 // We do not prevent non-trusted click because authors can already use
3549 // .click(). However, the pickers will follow the rules of popup-blocking.
3550 if (aVisitor.mEvent->DefaultPrevented()) {
3551 return NS_OK;
3552 }
3553 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3554 if (!(mouseEvent && mouseEvent->IsLeftClickEvent())) {
3555 return NS_OK;
3556 }
3557 if (mType == FormControlType::InputFile) {
3558 // If the user clicked on the "Choose folder..." button we open the
3559 // directory picker, else we open the file picker.
3560 FilePickerType type = FILE_PICKER_FILE;
3561 nsCOMPtr<nsIContent> target =
3562 do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
3563 if (target && target->FindFirstNonChromeOnlyAccessContent() == this &&
3564 ((StaticPrefs::dom_input_dirpicker() && Allowdirs()) ||
3565 (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
3566 HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)))) {
3567 type = FILE_PICKER_DIRECTORY;
3568 }
3569 return InitFilePicker(type);
3570 }
3571 if (mType == FormControlType::InputColor) {
3572 return InitColorPicker();
3573 }
3574
3575 return NS_OK;
3576 }
3577
3578 /**
3579 * Return true if the input event should be ignored because of its modifiers.
3580 * Control is treated specially, since sometimes we ignore it, and sometimes
3581 * we don't (for webcompat reasons).
3582 */
IgnoreInputEventWithModifier(const WidgetInputEvent & aEvent,bool ignoreControl)3583 static bool IgnoreInputEventWithModifier(const WidgetInputEvent& aEvent,
3584 bool ignoreControl) {
3585 return (ignoreControl && aEvent.IsControl()) || aEvent.IsAltGraph() ||
3586 aEvent.IsFn() || aEvent.IsOS();
3587 }
3588
StepsInputValue(const WidgetKeyboardEvent & aEvent) const3589 bool HTMLInputElement::StepsInputValue(
3590 const WidgetKeyboardEvent& aEvent) const {
3591 if (mType != FormControlType::InputNumber) {
3592 return false;
3593 }
3594 if (aEvent.mMessage != eKeyPress) {
3595 return false;
3596 }
3597 if (!aEvent.IsTrusted()) {
3598 return false;
3599 }
3600 if (aEvent.mKeyCode != NS_VK_UP && aEvent.mKeyCode != NS_VK_DOWN) {
3601 return false;
3602 }
3603 if (IgnoreInputEventWithModifier(aEvent, false)) {
3604 return false;
3605 }
3606 if (aEvent.DefaultPrevented()) {
3607 return false;
3608 }
3609 if (!IsMutable()) {
3610 return false;
3611 }
3612 return true;
3613 }
3614
ActivatesWithKeyboard(FormControlType aType)3615 static bool ActivatesWithKeyboard(FormControlType aType) {
3616 switch (aType) {
3617 case FormControlType::InputCheckbox:
3618 case FormControlType::InputRadio:
3619 case FormControlType::InputButton:
3620 case FormControlType::InputReset:
3621 case FormControlType::InputSubmit:
3622 case FormControlType::InputFile:
3623 case FormControlType::InputImage: // Bug 34418
3624 case FormControlType::InputColor:
3625 return true;
3626 default:
3627 return false;
3628 }
3629 }
3630
PostHandleEvent(EventChainPostVisitor & aVisitor)3631 nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
3632 if (aVisitor.mEvent->mMessage == eFocus ||
3633 aVisitor.mEvent->mMessage == eBlur) {
3634 if (aVisitor.mEvent->mMessage == eBlur) {
3635 if (mIsDraggingRange) {
3636 FinishRangeThumbDrag();
3637 } else if (mNumberControlSpinnerIsSpinning) {
3638 StopNumberControlSpinnerSpin();
3639 }
3640 }
3641
3642 UpdateValidityUIBits(aVisitor.mEvent->mMessage == eFocus);
3643
3644 UpdateState(true);
3645 }
3646
3647 nsresult rv = NS_OK;
3648 bool outerActivateEvent = !!(aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT);
3649 bool originalCheckedValue =
3650 !!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE);
3651 auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
3652
3653 // Ideally we would make the default action for click and space just dispatch
3654 // DOMActivate, and the default action for DOMActivate flip the checkbox/
3655 // radio state and fire onchange. However, for backwards compatibility, we
3656 // need to flip the state before firing click, and we need to fire click
3657 // when space is pressed. So, we just nest the firing of DOMActivate inside
3658 // the click event handling, and allow cancellation of DOMActivate to cancel
3659 // the click.
3660 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault &&
3661 !IsSingleLineTextControl(true) && mType != FormControlType::InputNumber) {
3662 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3663 if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
3664 !ShouldPreventDOMActivateDispatch(aVisitor.mEvent->mOriginalTarget)) {
3665 // DOMActive event should be trusted since the activation is actually
3666 // occurred even if the cause is an untrusted click event.
3667 InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
3668 actEvent.mDetail = 1;
3669
3670 if (RefPtr<PresShell> presShell =
3671 aVisitor.mPresContext ? aVisitor.mPresContext->GetPresShell()
3672 : nullptr) {
3673 nsEventStatus status = nsEventStatus_eIgnore;
3674 mInInternalActivate = true;
3675 rv = presShell->HandleDOMEventWithTarget(this, &actEvent, &status);
3676 mInInternalActivate = false;
3677
3678 // If activate is cancelled, we must do the same as when click is
3679 // cancelled (revert the checkbox to its original value).
3680 if (status == nsEventStatus_eConsumeNoDefault) {
3681 aVisitor.mEventStatus = status;
3682 }
3683 }
3684 }
3685 }
3686
3687 if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) && mForm) {
3688 switch (oldType) {
3689 case FormControlType::InputSubmit:
3690 case FormControlType::InputImage:
3691 // tell the form that we are about to exit a click handler
3692 // so the form knows not to defer subsequent submissions
3693 // the pending ones that were created during the handler
3694 // will be flushed or forgotten.
3695 mForm->OnSubmitClickEnd();
3696 break;
3697 default:
3698 break;
3699 }
3700 }
3701
3702 bool preventDefault =
3703 aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault;
3704 if (IsDisabled() && oldType != FormControlType::InputCheckbox &&
3705 oldType != FormControlType::InputRadio) {
3706 // Behave as if defaultPrevented when the element becomes disabled by event
3707 // listeners. Checkboxes and radio buttons should still process clicks for
3708 // web compat. See:
3709 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
3710 preventDefault = true;
3711 }
3712
3713 // now check to see if the event was canceled
3714 if (mCheckedIsToggled && outerActivateEvent) {
3715 if (preventDefault) {
3716 // if it was canceled and a radio button, then set the old
3717 // selected btn to TRUE. if it is a checkbox then set it to its
3718 // original value (legacy-canceled-activation)
3719 if (oldType == FormControlType::InputRadio) {
3720 nsCOMPtr<nsIContent> content = do_QueryInterface(aVisitor.mItemData);
3721 HTMLInputElement* selectedRadioButton =
3722 HTMLInputElement::FromNodeOrNull(content);
3723 if (selectedRadioButton) {
3724 selectedRadioButton->SetChecked(true);
3725 }
3726 // If there was no checked radio button or this one is no longer a
3727 // radio button we must reset it back to false to cancel the action.
3728 // See how the web of hack grows?
3729 if (!selectedRadioButton || mType != FormControlType::InputRadio) {
3730 DoSetChecked(false, true, true);
3731 }
3732 } else if (oldType == FormControlType::InputCheckbox) {
3733 bool originalIndeterminateValue =
3734 !!(aVisitor.mItemFlags & NS_ORIGINAL_INDETERMINATE_VALUE);
3735 SetIndeterminateInternal(originalIndeterminateValue, false);
3736 DoSetChecked(originalCheckedValue, true, true);
3737 }
3738 } else {
3739 // Fire input event and then change event.
3740 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
3741 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3742 "Failed to dispatch input event");
3743
3744 nsContentUtils::DispatchTrustedEvent<WidgetEvent>(
3745 OwnerDoc(), static_cast<Element*>(this), eFormChange, CanBubble::eYes,
3746 Cancelable::eNo);
3747 #ifdef ACCESSIBILITY
3748 // Fire an event to notify accessibility
3749 if (mType == FormControlType::InputCheckbox) {
3750 FireEventForAccessibility(this, eFormCheckboxStateChange);
3751 } else {
3752 FireEventForAccessibility(this, eFormRadioStateChange);
3753 // Fire event for the previous selected radio.
3754 nsCOMPtr<nsIContent> content = do_QueryInterface(aVisitor.mItemData);
3755 HTMLInputElement* previous = HTMLInputElement::FromNodeOrNull(content);
3756 if (previous) {
3757 FireEventForAccessibility(previous, eFormRadioStateChange);
3758 }
3759 }
3760 #endif
3761 }
3762 }
3763
3764 if (NS_SUCCEEDED(rv)) {
3765 WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
3766 if (keyEvent && StepsInputValue(*keyEvent)) {
3767 StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
3768 FireChangeEventIfNeeded();
3769 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3770 } else if (!preventDefault) {
3771 // Checkbox and Radio try to submit on Enter press
3772 if (aVisitor.mEvent->mMessage == eKeyPress &&
3773 (mType == FormControlType::InputCheckbox ||
3774 mType == FormControlType::InputRadio) &&
3775 keyEvent->mKeyCode == NS_VK_RETURN && aVisitor.mPresContext) {
3776 MaybeSubmitForm(aVisitor.mPresContext);
3777 } else if (ActivatesWithKeyboard(mType)) {
3778 // Otherwise we maybe dispatch a synthesized click.
3779 HandleKeyboardActivation(aVisitor);
3780 }
3781
3782 switch (aVisitor.mEvent->mMessage) {
3783 case eFocus: {
3784 // see if we should select the contents of the textbox. This happens
3785 // for text and password fields when the field was focused by the
3786 // keyboard or a navigation, the platform allows it, and it wasn't
3787 // just because we raised a window.
3788 //
3789 // While it'd usually make sense, we don't do this for JS callers
3790 // because it causes some compat issues, see bug 1712724 for example.
3791 nsFocusManager* fm = nsFocusManager::GetFocusManager();
3792 if (fm && IsSingleLineTextControl(false) &&
3793 !aVisitor.mEvent->AsFocusEvent()->mFromRaise &&
3794 SelectTextFieldOnFocus()) {
3795 if (Document* document = GetComposedDoc()) {
3796 uint32_t lastFocusMethod;
3797 fm->GetLastFocusMethod(document->GetWindow(), &lastFocusMethod);
3798 if (lastFocusMethod & (nsIFocusManager::FLAG_BYKEY |
3799 nsIFocusManager::FLAG_BYMOVEFOCUS) &&
3800 !(lastFocusMethod & nsIFocusManager::FLAG_BYJS)) {
3801 RefPtr<nsPresContext> presContext =
3802 GetPresContext(eForComposedDoc);
3803 DispatchSelectEvent(presContext);
3804 SelectAll(presContext);
3805 }
3806 }
3807 }
3808 break;
3809 }
3810 case eKeyPress: {
3811 if (mType == FormControlType::InputRadio && !keyEvent->IsAlt() &&
3812 !keyEvent->IsControl() && !keyEvent->IsMeta()) {
3813 bool isMovingBack = false;
3814 switch (keyEvent->mKeyCode) {
3815 case NS_VK_UP:
3816 case NS_VK_LEFT:
3817 isMovingBack = true;
3818 [[fallthrough]];
3819 case NS_VK_DOWN:
3820 case NS_VK_RIGHT:
3821 // Arrow key pressed, focus+select prev/next radio button
3822 nsIRadioGroupContainer* container = GetRadioGroupContainer();
3823 if (container) {
3824 nsAutoString name;
3825 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
3826 RefPtr<HTMLInputElement> selectedRadioButton;
3827 container->GetNextRadioButton(
3828 name, isMovingBack, this,
3829 getter_AddRefs(selectedRadioButton));
3830 if (selectedRadioButton) {
3831 FocusOptions options;
3832 ErrorResult error;
3833
3834 selectedRadioButton->Focus(options, CallerType::System,
3835 error);
3836 rv = error.StealNSResult();
3837 if (NS_SUCCEEDED(rv)) {
3838 rv = DispatchSimulatedClick(selectedRadioButton,
3839 aVisitor.mEvent->IsTrusted(),
3840 aVisitor.mPresContext);
3841 if (NS_SUCCEEDED(rv)) {
3842 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3843 }
3844 }
3845 }
3846 }
3847 }
3848 }
3849
3850 /*
3851 * For some input types, if the user hits enter, the form is
3852 * submitted.
3853 *
3854 * Bug 99920, bug 109463 and bug 147850:
3855 * (a) if there is a submit control in the form, click the first
3856 * submit control in the form.
3857 * (b) if there is just one text control in the form, submit by
3858 * sending a submit event directly to the form
3859 * (c) if there is more than one text input and no submit buttons, do
3860 * not submit, period.
3861 */
3862
3863 if (keyEvent->mKeyCode == NS_VK_RETURN &&
3864 (IsSingleLineTextControl(false, mType) ||
3865 mType == FormControlType::InputNumber ||
3866 IsDateTimeInputType(mType))) {
3867 FireChangeEventIfNeeded();
3868 if (aVisitor.mPresContext) {
3869 rv = MaybeSubmitForm(aVisitor.mPresContext);
3870 NS_ENSURE_SUCCESS(rv, rv);
3871 }
3872 }
3873
3874 if (mType == FormControlType::InputRange && !keyEvent->IsAlt() &&
3875 !keyEvent->IsControl() && !keyEvent->IsMeta() &&
3876 (keyEvent->mKeyCode == NS_VK_LEFT ||
3877 keyEvent->mKeyCode == NS_VK_RIGHT ||
3878 keyEvent->mKeyCode == NS_VK_UP ||
3879 keyEvent->mKeyCode == NS_VK_DOWN ||
3880 keyEvent->mKeyCode == NS_VK_PAGE_UP ||
3881 keyEvent->mKeyCode == NS_VK_PAGE_DOWN ||
3882 keyEvent->mKeyCode == NS_VK_HOME ||
3883 keyEvent->mKeyCode == NS_VK_END)) {
3884 Decimal minimum = GetMinimum();
3885 Decimal maximum = GetMaximum();
3886 MOZ_ASSERT(minimum.isFinite() && maximum.isFinite());
3887 if (minimum < maximum) { // else the value is locked to the minimum
3888 Decimal value = GetValueAsDecimal();
3889 Decimal step = GetStep();
3890 if (step == kStepAny) {
3891 step = GetDefaultStep();
3892 }
3893 MOZ_ASSERT(value.isFinite() && step.isFinite());
3894 Decimal newValue;
3895 switch (keyEvent->mKeyCode) {
3896 case NS_VK_LEFT:
3897 newValue =
3898 value +
3899 (GetComputedDirectionality() == eDir_RTL ? step : -step);
3900 break;
3901 case NS_VK_RIGHT:
3902 newValue =
3903 value +
3904 (GetComputedDirectionality() == eDir_RTL ? -step : step);
3905 break;
3906 case NS_VK_UP:
3907 // Even for horizontal range, "up" means "increase"
3908 newValue = value + step;
3909 break;
3910 case NS_VK_DOWN:
3911 // Even for horizontal range, "down" means "decrease"
3912 newValue = value - step;
3913 break;
3914 case NS_VK_HOME:
3915 newValue = minimum;
3916 break;
3917 case NS_VK_END:
3918 newValue = maximum;
3919 break;
3920 case NS_VK_PAGE_UP:
3921 // For PgUp/PgDn we jump 10% of the total range, unless step
3922 // requires us to jump more.
3923 newValue =
3924 value + std::max(step, (maximum - minimum) / Decimal(10));
3925 break;
3926 case NS_VK_PAGE_DOWN:
3927 newValue =
3928 value - std::max(step, (maximum - minimum) / Decimal(10));
3929 break;
3930 }
3931 SetValueOfRangeForUserEvent(newValue);
3932 FireChangeEventIfNeeded();
3933 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3934 }
3935 }
3936
3937 } break; // eKeyPress
3938
3939 case eMouseDown:
3940 case eMouseUp:
3941 case eMouseDoubleClick: {
3942 // cancel all of these events for buttons
3943 // XXXsmaug Why?
3944 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3945 if (mouseEvent->mButton == MouseButton::eMiddle ||
3946 mouseEvent->mButton == MouseButton::eSecondary) {
3947 if (mType == FormControlType::InputButton ||
3948 mType == FormControlType::InputReset ||
3949 mType == FormControlType::InputSubmit) {
3950 if (aVisitor.mDOMEvent) {
3951 aVisitor.mDOMEvent->StopPropagation();
3952 } else {
3953 rv = NS_ERROR_FAILURE;
3954 }
3955 }
3956 }
3957 if (mType == FormControlType::InputNumber &&
3958 aVisitor.mEvent->IsTrusted()) {
3959 if (mouseEvent->mButton == MouseButton::ePrimary &&
3960 !IgnoreInputEventWithModifier(*mouseEvent, false)) {
3961 nsNumberControlFrame* numberControlFrame =
3962 do_QueryFrame(GetPrimaryFrame());
3963 if (numberControlFrame) {
3964 if (aVisitor.mEvent->mMessage == eMouseDown && IsMutable()) {
3965 switch (numberControlFrame->GetSpinButtonForPointerEvent(
3966 aVisitor.mEvent->AsMouseEvent())) {
3967 case nsNumberControlFrame::eSpinButtonUp:
3968 StepNumberControlForUserEvent(1);
3969 mNumberControlSpinnerSpinsUp = true;
3970 StartNumberControlSpinnerSpin();
3971 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3972 break;
3973 case nsNumberControlFrame::eSpinButtonDown:
3974 StepNumberControlForUserEvent(-1);
3975 mNumberControlSpinnerSpinsUp = false;
3976 StartNumberControlSpinnerSpin();
3977 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3978 break;
3979 }
3980 }
3981 }
3982 }
3983 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
3984 // We didn't handle this to step up/down. Whatever this was, be
3985 // aggressive about stopping the spin. (And don't set
3986 // nsEventStatus_eConsumeNoDefault after doing so, since that
3987 // might prevent, say, the context menu from opening.)
3988 StopNumberControlSpinnerSpin();
3989 }
3990 }
3991 break;
3992 }
3993 #if !defined(ANDROID) && !defined(XP_MACOSX)
3994 case eWheel: {
3995 // Handle wheel events as increasing / decreasing the input element's
3996 // value when it's focused and it's type is number or range.
3997 WidgetWheelEvent* wheelEvent = aVisitor.mEvent->AsWheelEvent();
3998 if (!aVisitor.mEvent->DefaultPrevented() &&
3999 aVisitor.mEvent->IsTrusted() && IsMutable() && wheelEvent &&
4000 wheelEvent->mDeltaY != 0 &&
4001 wheelEvent->mDeltaMode != WheelEvent_Binding::DOM_DELTA_PIXEL) {
4002 if (mType == FormControlType::InputNumber) {
4003 if (nsContentUtils::IsFocusedContent(this)) {
4004 StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
4005 FireChangeEventIfNeeded();
4006 aVisitor.mEvent->PreventDefault();
4007 }
4008 } else if (mType == FormControlType::InputRange &&
4009 nsContentUtils::IsFocusedContent(this) &&
4010 GetMinimum() < GetMaximum()) {
4011 Decimal value = GetValueAsDecimal();
4012 Decimal step = GetStep();
4013 if (step == kStepAny) {
4014 step = GetDefaultStep();
4015 }
4016 MOZ_ASSERT(value.isFinite() && step.isFinite());
4017 SetValueOfRangeForUserEvent(
4018 wheelEvent->mDeltaY < 0 ? value + step : value - step);
4019 FireChangeEventIfNeeded();
4020 aVisitor.mEvent->PreventDefault();
4021 }
4022 }
4023 break;
4024 }
4025 #endif
4026 case eMouseClick: {
4027 if (!aVisitor.mEvent->DefaultPrevented() &&
4028 aVisitor.mEvent->IsTrusted() &&
4029 mType == FormControlType::InputSearch &&
4030 aVisitor.mEvent->AsMouseEvent()->mButton ==
4031 MouseButton::ePrimary) {
4032 if (nsSearchControlFrame* searchControlFrame =
4033 do_QueryFrame(GetPrimaryFrame())) {
4034 Element* clearButton = searchControlFrame->GetAnonClearButton();
4035 if (clearButton &&
4036 aVisitor.mEvent->mOriginalTarget == clearButton) {
4037 SetUserInput(EmptyString(),
4038 *nsContentUtils::GetSystemPrincipal());
4039 }
4040 }
4041 }
4042 break;
4043 }
4044 default:
4045 break;
4046 }
4047
4048 if (outerActivateEvent) {
4049 if (mForm && (oldType == FormControlType::InputSubmit ||
4050 oldType == FormControlType::InputImage)) {
4051 if (mType != FormControlType::InputSubmit &&
4052 mType != FormControlType::InputImage &&
4053 aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) {
4054 // If the type has changed to a non-submit type, then we want to
4055 // flush the stored submission if there is one (as if the submit()
4056 // was allowed to succeed)
4057 mForm->FlushPendingSubmission();
4058 }
4059 }
4060 switch (mType) {
4061 case FormControlType::InputReset:
4062 case FormControlType::InputSubmit:
4063 case FormControlType::InputImage:
4064 if (mForm) {
4065 // Hold a strong ref while dispatching
4066 RefPtr<mozilla::dom::HTMLFormElement> form(mForm);
4067 if (mType == FormControlType::InputReset) {
4068 form->MaybeReset(this);
4069 } else {
4070 form->MaybeSubmit(this);
4071 }
4072 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4073 }
4074 break;
4075
4076 default:
4077 break;
4078 } // switch
4079 } // click or outer activate event
4080 } else if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) &&
4081 (oldType == FormControlType::InputSubmit ||
4082 oldType == FormControlType::InputImage) &&
4083 mForm) {
4084 // tell the form to flush a possible pending submission.
4085 // the reason is that the script returned false (the event was
4086 // not ignored) so if there is a stored submission, it needs to
4087 // be submitted immediately.
4088 mForm->FlushPendingSubmission();
4089 }
4090 } // if
4091
4092 if (NS_SUCCEEDED(rv) && mType == FormControlType::InputRange) {
4093 PostHandleEventForRangeThumb(aVisitor);
4094 }
4095
4096 return MaybeInitPickers(aVisitor);
4097 }
4098
PostHandleEventForRangeThumb(EventChainPostVisitor & aVisitor)4099 void HTMLInputElement::PostHandleEventForRangeThumb(
4100 EventChainPostVisitor& aVisitor) {
4101 MOZ_ASSERT(mType == FormControlType::InputRange);
4102
4103 if (nsEventStatus_eConsumeNoDefault == aVisitor.mEventStatus ||
4104 !(aVisitor.mEvent->mClass == eMouseEventClass ||
4105 aVisitor.mEvent->mClass == eTouchEventClass ||
4106 aVisitor.mEvent->mClass == eKeyboardEventClass)) {
4107 return;
4108 }
4109
4110 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4111 if (!rangeFrame && mIsDraggingRange) {
4112 CancelRangeThumbDrag();
4113 return;
4114 }
4115
4116 switch (aVisitor.mEvent->mMessage) {
4117 case eMouseDown:
4118 case eTouchStart: {
4119 if (mIsDraggingRange) {
4120 break;
4121 }
4122 if (PresShell::GetCapturingContent()) {
4123 break; // don't start drag if someone else is already capturing
4124 }
4125 WidgetInputEvent* inputEvent = aVisitor.mEvent->AsInputEvent();
4126 if (IgnoreInputEventWithModifier(*inputEvent, true)) {
4127 break; // ignore
4128 }
4129 if (aVisitor.mEvent->mMessage == eMouseDown) {
4130 if (aVisitor.mEvent->AsMouseEvent()->mButtons ==
4131 MouseButtonsFlag::ePrimaryFlag) {
4132 StartRangeThumbDrag(inputEvent);
4133 } else if (mIsDraggingRange) {
4134 CancelRangeThumbDrag();
4135 }
4136 } else {
4137 if (aVisitor.mEvent->AsTouchEvent()->mTouches.Length() == 1) {
4138 StartRangeThumbDrag(inputEvent);
4139 } else if (mIsDraggingRange) {
4140 CancelRangeThumbDrag();
4141 }
4142 }
4143 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4144 } break;
4145
4146 case eMouseMove:
4147 case eTouchMove:
4148 if (!mIsDraggingRange) {
4149 break;
4150 }
4151 if (PresShell::GetCapturingContent() != this) {
4152 // Someone else grabbed capture.
4153 CancelRangeThumbDrag();
4154 break;
4155 }
4156 SetValueOfRangeForUserEvent(
4157 rangeFrame->GetValueAtEventPoint(aVisitor.mEvent->AsInputEvent()));
4158 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4159 break;
4160
4161 case eMouseUp:
4162 case eTouchEnd:
4163 if (!mIsDraggingRange) {
4164 break;
4165 }
4166 // We don't check to see whether we are the capturing content here and
4167 // call CancelRangeThumbDrag() if that is the case. We just finish off
4168 // the drag and set our final value (unless someone has called
4169 // preventDefault() and prevents us getting here).
4170 FinishRangeThumbDrag(aVisitor.mEvent->AsInputEvent());
4171 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4172 break;
4173
4174 case eKeyPress:
4175 if (mIsDraggingRange &&
4176 aVisitor.mEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
4177 CancelRangeThumbDrag();
4178 }
4179 break;
4180
4181 case eTouchCancel:
4182 if (mIsDraggingRange) {
4183 CancelRangeThumbDrag();
4184 }
4185 break;
4186
4187 default:
4188 break;
4189 }
4190 }
4191
MaybeLoadImage()4192 void HTMLInputElement::MaybeLoadImage() {
4193 // Our base URI may have changed; claim that our URI changed, and the
4194 // nsImageLoadingContent will decide whether a new image load is warranted.
4195 nsAutoString uri;
4196 if (mType == FormControlType::InputImage &&
4197 GetAttr(kNameSpaceID_None, nsGkAtoms::src, uri) &&
4198 (NS_FAILED(LoadImage(uri, false, true, eImageLoadType_Normal,
4199 mSrcTriggeringPrincipal)) ||
4200 !LoadingEnabled())) {
4201 CancelImageRequests(true);
4202 }
4203 }
4204
BindToTree(BindContext & aContext,nsINode & aParent)4205 nsresult HTMLInputElement::BindToTree(BindContext& aContext, nsINode& aParent) {
4206 nsresult rv =
4207 nsGenericHTMLFormElementWithState::BindToTree(aContext, aParent);
4208 NS_ENSURE_SUCCESS(rv, rv);
4209
4210 nsImageLoadingContent::BindToTree(aContext, aParent);
4211
4212 if (mType == FormControlType::InputImage) {
4213 // Our base URI may have changed; claim that our URI changed, and the
4214 // nsImageLoadingContent will decide whether a new image load is warranted.
4215 if (HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
4216 // Mark channel as urgent-start before load image if the image load is
4217 // initaiated by a user interaction.
4218 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
4219
4220 nsContentUtils::AddScriptRunner(
4221 NewRunnableMethod("dom::HTMLInputElement::MaybeLoadImage", this,
4222 &HTMLInputElement::MaybeLoadImage));
4223 }
4224 }
4225
4226 // Add radio to document if we don't have a form already (if we do it's
4227 // already been added into that group)
4228 if (!mForm && mType == FormControlType::InputRadio &&
4229 GetUncomposedDocOrConnectedShadowRoot()) {
4230 AddedToRadioGroup();
4231 }
4232
4233 // Set direction based on value if dir=auto
4234 if (HasDirAuto()) {
4235 SetDirectionFromValue(false);
4236 }
4237
4238 // An element can't suffer from value missing if it is not in a document.
4239 // We have to check if we suffer from that as we are now in a document.
4240 UpdateValueMissingValidityState();
4241
4242 // If there is a disabled fieldset in the parent chain, the element is now
4243 // barred from constraint validation and can't suffer from value missing
4244 // (call done before).
4245 UpdateBarredFromConstraintValidation();
4246
4247 // And now make sure our state is up to date
4248 UpdateState(false);
4249
4250 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4251 // Construct Shadow Root so web content can be hidden in the DOM.
4252 AttachAndSetUAShadowRoot();
4253 }
4254
4255 if (mType == FormControlType::InputPassword) {
4256 if (IsInComposedDoc()) {
4257 AsyncEventDispatcher* dispatcher =
4258 new AsyncEventDispatcher(this, u"DOMInputPasswordAdded"_ns,
4259 CanBubble::eYes, ChromeOnlyDispatch::eYes);
4260 dispatcher->PostDOMEvent();
4261 }
4262
4263 #ifdef EARLY_BETA_OR_EARLIER
4264 Telemetry::Accumulate(Telemetry::PWMGR_PASSWORD_INPUT_IN_FORM, !!mForm);
4265 #endif
4266 }
4267
4268 return rv;
4269 }
4270
UnbindFromTree(bool aNullParent)4271 void HTMLInputElement::UnbindFromTree(bool aNullParent) {
4272 if (mType == FormControlType::InputPassword) {
4273 MaybeFireInputPasswordRemoved();
4274 }
4275
4276 // If we have a form and are unbound from it,
4277 // nsGenericHTMLFormElementWithState::UnbindFromTree() will unset the form and
4278 // that takes care of form's WillRemove so we just have to take care
4279 // of the case where we're removing from the document and we don't
4280 // have a form
4281 if (!mForm && mType == FormControlType::InputRadio) {
4282 WillRemoveFromRadioGroup();
4283 }
4284
4285 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4286 NotifyUAWidgetTeardown();
4287 }
4288
4289 nsImageLoadingContent::UnbindFromTree(aNullParent);
4290 nsGenericHTMLFormElementWithState::UnbindFromTree(aNullParent);
4291
4292 // GetCurrentDoc is returning nullptr so we can update the value
4293 // missing validity state to reflect we are no longer into a doc.
4294 UpdateValueMissingValidityState();
4295 // We might be no longer disabled because of parent chain changed.
4296 UpdateBarredFromConstraintValidation();
4297
4298 // And now make sure our state is up to date
4299 UpdateState(false);
4300 }
4301
4302 namespace {
4303 class TypeChangeSelectionRangeFlagDeterminer {
4304 public:
4305 using ValueSetterOption = TextControlState::ValueSetterOption;
4306 using ValueSetterOptions = TextControlState::ValueSetterOptions;
4307
4308 // @param aOldType InputElementTypes
4309 // @param aNewType InputElementTypes
TypeChangeSelectionRangeFlagDeterminer(FormControlType aOldType,FormControlType aNewType)4310 TypeChangeSelectionRangeFlagDeterminer(FormControlType aOldType,
4311 FormControlType aNewType)
4312 : mOldType(aOldType), mNewType(aNewType) {}
4313
4314 // @return TextControlState::ValueSetterOptions
GetValueSetterOptions() const4315 ValueSetterOptions GetValueSetterOptions() const {
4316 const bool previouslySelectable = DoesSetRangeTextApply(mOldType);
4317 const bool nowSelectable = DoesSetRangeTextApply(mNewType);
4318 const bool moveCursorToBeginAndSetDirectionForward =
4319 !previouslySelectable && nowSelectable;
4320 if (moveCursorToBeginAndSetDirectionForward) {
4321 return {ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward};
4322 }
4323 return {};
4324 }
4325
4326 private:
4327 /**
4328 * @param aType InputElementTypes
4329 * @return true, iff SetRangeText applies to aType as specified at
4330 * https://html.spec.whatwg.org/multipage/input.html#concept-input-apply.
4331 */
DoesSetRangeTextApply(FormControlType aType)4332 static bool DoesSetRangeTextApply(FormControlType aType) {
4333 return aType == FormControlType::InputText ||
4334 aType == FormControlType::InputSearch ||
4335 aType == FormControlType::InputUrl ||
4336 aType == FormControlType::InputTel ||
4337 aType == FormControlType::InputPassword;
4338 }
4339
4340 const FormControlType mOldType;
4341 const FormControlType mNewType;
4342 };
4343 } // anonymous namespace
4344
HandleTypeChange(FormControlType aNewType,bool aNotify)4345 void HTMLInputElement::HandleTypeChange(FormControlType aNewType,
4346 bool aNotify) {
4347 FormControlType oldType = mType;
4348 MOZ_ASSERT(oldType != aNewType);
4349
4350 mHasBeenTypePassword =
4351 mHasBeenTypePassword || aNewType == FormControlType::InputPassword;
4352
4353 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
4354 // Input element can represent very different kinds of UIs, and we may
4355 // need to flush styling even when focusing the already focused input
4356 // element.
4357 fm->NeedsFlushBeforeEventHandling(this);
4358 }
4359
4360 if (aNewType == FormControlType::InputFile ||
4361 oldType == FormControlType::InputFile) {
4362 if (aNewType == FormControlType::InputFile) {
4363 mFileData.reset(new FileData());
4364 } else {
4365 mFileData->Unlink();
4366 mFileData = nullptr;
4367 }
4368 }
4369
4370 if (oldType == FormControlType::InputRange && mIsDraggingRange) {
4371 CancelRangeThumbDrag(false);
4372 }
4373
4374 ValueModeType aOldValueMode = GetValueMode();
4375 nsAutoString aOldValue;
4376
4377 if (aOldValueMode == VALUE_MODE_VALUE) {
4378 // Doesn't matter what caller type we pass here, since we know we're not a
4379 // file input anyway.
4380 GetValue(aOldValue, CallerType::NonSystem);
4381 }
4382
4383 TextControlState::SelectionProperties sp;
4384
4385 if (GetEditorState()) {
4386 mInputData.mState->SyncUpSelectionPropertiesBeforeDestruction();
4387 sp = mInputData.mState->GetSelectionProperties();
4388 }
4389
4390 // We already have a copy of the value, lets free it and changes the type.
4391 FreeData();
4392 mType = aNewType;
4393 void* memory = mInputTypeMem;
4394 mInputType = InputType::Create(this, mType, memory);
4395
4396 if (IsSingleLineTextControl()) {
4397 mInputData.mState = TextControlState::Construct(this);
4398 if (!sp.IsDefault()) {
4399 mInputData.mState->SetSelectionProperties(sp);
4400 }
4401 }
4402
4403 /**
4404 * The following code is trying to reproduce the algorithm described here:
4405 * http://www.whatwg.org/specs/web-apps/current-work/complete.html#input-type-change
4406 */
4407 switch (GetValueMode()) {
4408 case VALUE_MODE_DEFAULT:
4409 case VALUE_MODE_DEFAULT_ON:
4410 // If the previous value mode was value, we need to set the value content
4411 // attribute to the previous value.
4412 // There is no value sanitizing algorithm for elements in this mode.
4413 if (aOldValueMode == VALUE_MODE_VALUE && !aOldValue.IsEmpty()) {
4414 SetAttr(kNameSpaceID_None, nsGkAtoms::value, aOldValue, true);
4415 }
4416 break;
4417 case VALUE_MODE_VALUE:
4418 // If the previous value mode wasn't value, we have to set the value to
4419 // the value content attribute.
4420 // SetValueInternal is going to sanitize the value.
4421 {
4422 nsAutoString value;
4423 if (aOldValueMode != VALUE_MODE_VALUE) {
4424 GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
4425 } else {
4426 value = aOldValue;
4427 }
4428
4429 const TypeChangeSelectionRangeFlagDeterminer flagDeterminer(oldType,
4430 mType);
4431 ValueSetterOptions options(flagDeterminer.GetValueSetterOptions());
4432 options += ValueSetterOption::ByInternalAPI;
4433
4434 // TODO: What should we do if SetValueInternal fails? (The allocation
4435 // may potentially be big, but most likely we've failed to allocate
4436 // before the type change.)
4437 SetValueInternal(value, options);
4438 }
4439 break;
4440 case VALUE_MODE_FILENAME:
4441 default:
4442 // We don't care about the value.
4443 // There is no value sanitizing algorithm for elements in this mode.
4444 break;
4445 }
4446
4447 // Updating mFocusedValue in consequence:
4448 // If the new type fires a change event on blur, but the previous type
4449 // doesn't, we should set mFocusedValue to the current value.
4450 // Otherwise, if the new type doesn't fire a change event on blur, but the
4451 // previous type does, we should clear out mFocusedValue.
4452 if (MayFireChangeOnBlur(mType) && !MayFireChangeOnBlur(oldType)) {
4453 GetValue(mFocusedValue, CallerType::System);
4454 } else if (!IsSingleLineTextControl(false, mType) &&
4455 IsSingleLineTextControl(false, oldType)) {
4456 mFocusedValue.Truncate();
4457 }
4458
4459 // Update or clear our required states since we may have changed from a
4460 // required input type to a non-required input type or viceversa.
4461 if (DoesRequiredApply()) {
4462 bool isRequired = HasAttr(kNameSpaceID_None, nsGkAtoms::required);
4463 UpdateRequiredState(isRequired, aNotify);
4464 } else if (aNotify) {
4465 RemoveStates(REQUIRED_STATES);
4466 } else {
4467 RemoveStatesSilently(REQUIRED_STATES);
4468 }
4469
4470 UpdateHasRange();
4471
4472 // Update validity states, but not element state. We'll update
4473 // element state later, as part of this attribute change.
4474 UpdateAllValidityStatesButNotElementState();
4475
4476 UpdateApzAwareFlag();
4477
4478 UpdateBarredFromConstraintValidation();
4479
4480 if (oldType == FormControlType::InputImage) {
4481 // We're no longer an image input. Cancel our image requests, if we have
4482 // any.
4483 CancelImageRequests(aNotify);
4484
4485 // And we should update our mapped attribute mapping function.
4486 mAttrs.UpdateMappedAttrRuleMapper(*this);
4487 } else if (mType == FormControlType::InputImage) {
4488 if (aNotify) {
4489 // We just got switched to be an image input; we should see
4490 // whether we have an image to load;
4491 nsAutoString src;
4492 if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
4493 // Mark channel as urgent-start before load image if the image load is
4494 // initaiated by a user interaction.
4495 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
4496
4497 LoadImage(src, false, aNotify, eImageLoadType_Normal,
4498 mSrcTriggeringPrincipal);
4499 }
4500 }
4501
4502 // And we should update our mapped attribute mapping function.
4503 mAttrs.UpdateMappedAttrRuleMapper(*this);
4504 }
4505
4506 if (mType == FormControlType::InputPassword && IsInComposedDoc()) {
4507 AsyncEventDispatcher* dispatcher =
4508 new AsyncEventDispatcher(this, u"DOMInputPasswordAdded"_ns,
4509 CanBubble::eYes, ChromeOnlyDispatch::eYes);
4510 dispatcher->PostDOMEvent();
4511 }
4512
4513 if (IsInComposedDoc()) {
4514 if (CreatesDateTimeWidget(oldType)) {
4515 if (!CreatesDateTimeWidget()) {
4516 // Switch away from date/time type.
4517 NotifyUAWidgetTeardown();
4518 } else {
4519 // Switch between date and time.
4520 NotifyUAWidgetSetupOrChange();
4521 }
4522 } else if (CreatesDateTimeWidget()) {
4523 // Switch to date/time type.
4524 AttachAndSetUAShadowRoot();
4525 }
4526 }
4527 }
4528
SanitizeValue(nsAString & aValue,ForValueGetter aForGetter)4529 void HTMLInputElement::SanitizeValue(nsAString& aValue,
4530 ForValueGetter aForGetter) {
4531 NS_ASSERTION(mDoneCreating, "The element creation should be finished!");
4532
4533 switch (mType) {
4534 case FormControlType::InputText:
4535 case FormControlType::InputSearch:
4536 case FormControlType::InputTel:
4537 case FormControlType::InputPassword: {
4538 aValue.StripCRLF();
4539 } break;
4540 case FormControlType::InputEmail:
4541 case FormControlType::InputUrl: {
4542 aValue.StripCRLF();
4543
4544 aValue = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
4545 aValue);
4546 } break;
4547 case FormControlType::InputNumber: {
4548 Decimal value;
4549 bool ok = mInputType->ConvertStringToNumber(aValue, value);
4550 if (!ok) {
4551 aValue.Truncate();
4552 return;
4553 }
4554
4555 nsAutoString sanitizedValue;
4556 if (aForGetter == ForValueGetter::Yes) {
4557 // If the default non-localized algorithm parses the value, then we're
4558 // done, don't un-localize it, to avoid precision loss, and to preserve
4559 // scientific notation as well for example.
4560 //
4561 // FIXME(emilio, bug 1622808): Localization should ideally be more
4562 // input-preserving.
4563 if (StringToDecimal(aValue).isFinite()) {
4564 return;
4565 }
4566 // For the <input type=number> value getter, we return the unlocalized
4567 // value if it doesn't parse as StringToDecimal, for compat with other
4568 // browsers.
4569 char buf[32];
4570 DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
4571 sanitizedValue.AssignASCII(buf);
4572 MOZ_ASSERT(ok, "buf not big enough");
4573 } else {
4574 mInputType->ConvertNumberToString(value, sanitizedValue);
4575 // Otherwise we localize as needed, but if both the localized and
4576 // unlocalized version parse, we just use the unlocalized one, to
4577 // preserve the input as much as possible.
4578 //
4579 // FIXME(emilio, bug 1622808): Localization should ideally be more
4580 // input-preserving.
4581 if (StringToDecimal(sanitizedValue).isFinite()) {
4582 return;
4583 }
4584 }
4585 aValue.Assign(sanitizedValue);
4586 } break;
4587 case FormControlType::InputRange: {
4588 Decimal minimum = GetMinimum();
4589 Decimal maximum = GetMaximum();
4590 MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
4591 "type=range should have a default maximum/minimum");
4592
4593 // We use this to avoid modifying the string unnecessarily, since that
4594 // may introduce rounding. This is set to true only if the value we
4595 // parse out from aValue needs to be sanitized.
4596 bool needSanitization = false;
4597
4598 Decimal value;
4599 bool ok = mInputType->ConvertStringToNumber(aValue, value);
4600 if (!ok) {
4601 needSanitization = true;
4602 // Set value to midway between minimum and maximum.
4603 value = maximum <= minimum ? minimum
4604 : minimum + (maximum - minimum) / Decimal(2);
4605 } else if (value < minimum || maximum < minimum) {
4606 needSanitization = true;
4607 value = minimum;
4608 } else if (value > maximum) {
4609 needSanitization = true;
4610 value = maximum;
4611 }
4612
4613 Decimal step = GetStep();
4614 if (step != kStepAny) {
4615 Decimal stepBase = GetStepBase();
4616 // There could be rounding issues below when dealing with fractional
4617 // numbers, but let's ignore that until ECMAScript supplies us with a
4618 // decimal number type.
4619 Decimal deltaToStep = NS_floorModulo(value - stepBase, step);
4620 if (deltaToStep != Decimal(0)) {
4621 // "suffering from a step mismatch"
4622 // Round the element's value to the nearest number for which the
4623 // element would not suffer from a step mismatch, and which is
4624 // greater than or equal to the minimum, and, if the maximum is not
4625 // less than the minimum, which is less than or equal to the
4626 // maximum, if there is a number that matches these constraints:
4627 MOZ_ASSERT(deltaToStep > Decimal(0),
4628 "stepBelow/stepAbove will be wrong");
4629 Decimal stepBelow = value - deltaToStep;
4630 Decimal stepAbove = value - deltaToStep + step;
4631 Decimal halfStep = step / Decimal(2);
4632 bool stepAboveIsClosest = (stepAbove - value) <= halfStep;
4633 bool stepAboveInRange = stepAbove >= minimum && stepAbove <= maximum;
4634 bool stepBelowInRange = stepBelow >= minimum && stepBelow <= maximum;
4635
4636 if ((stepAboveIsClosest || !stepBelowInRange) && stepAboveInRange) {
4637 needSanitization = true;
4638 value = stepAbove;
4639 } else if ((!stepAboveIsClosest || !stepAboveInRange) &&
4640 stepBelowInRange) {
4641 needSanitization = true;
4642 value = stepBelow;
4643 }
4644 }
4645 }
4646
4647 if (needSanitization) {
4648 char buf[32];
4649 DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
4650 aValue.AssignASCII(buf);
4651 MOZ_ASSERT(ok, "buf not big enough");
4652 }
4653 } break;
4654 case FormControlType::InputDate: {
4655 if (!aValue.IsEmpty() && !IsValidDate(aValue)) {
4656 aValue.Truncate();
4657 }
4658 } break;
4659 case FormControlType::InputTime: {
4660 if (!aValue.IsEmpty() && !IsValidTime(aValue)) {
4661 aValue.Truncate();
4662 }
4663 } break;
4664 case FormControlType::InputMonth: {
4665 if (!aValue.IsEmpty() && !IsValidMonth(aValue)) {
4666 aValue.Truncate();
4667 }
4668 } break;
4669 case FormControlType::InputWeek: {
4670 if (!aValue.IsEmpty() && !IsValidWeek(aValue)) {
4671 aValue.Truncate();
4672 }
4673 } break;
4674 case FormControlType::InputDatetimeLocal: {
4675 if (!aValue.IsEmpty() && !IsValidDateTimeLocal(aValue)) {
4676 aValue.Truncate();
4677 } else {
4678 NormalizeDateTimeLocal(aValue);
4679 }
4680 } break;
4681 case FormControlType::InputColor: {
4682 if (IsValidSimpleColor(aValue)) {
4683 ToLowerCase(aValue);
4684 } else {
4685 // Set default (black) color, if aValue wasn't parsed correctly.
4686 aValue.AssignLiteral("#000000");
4687 }
4688 } break;
4689 default:
4690 break;
4691 }
4692 }
4693
IsValidSimpleColor(const nsAString & aValue) const4694 bool HTMLInputElement::IsValidSimpleColor(const nsAString& aValue) const {
4695 if (aValue.Length() != 7 || aValue.First() != '#') {
4696 return false;
4697 }
4698
4699 for (int i = 1; i < 7; ++i) {
4700 if (!IsAsciiDigit(aValue[i]) && !(aValue[i] >= 'a' && aValue[i] <= 'f') &&
4701 !(aValue[i] >= 'A' && aValue[i] <= 'F')) {
4702 return false;
4703 }
4704 }
4705 return true;
4706 }
4707
IsLeapYear(uint32_t aYear) const4708 bool HTMLInputElement::IsLeapYear(uint32_t aYear) const {
4709 if ((aYear % 4 == 0 && aYear % 100 != 0) || (aYear % 400 == 0)) {
4710 return true;
4711 }
4712 return false;
4713 }
4714
DayOfWeek(uint32_t aYear,uint32_t aMonth,uint32_t aDay,bool isoWeek) const4715 uint32_t HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth,
4716 uint32_t aDay, bool isoWeek) const {
4717 // Tomohiko Sakamoto algorithm.
4718 int monthTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
4719 aYear -= aMonth < 3;
4720
4721 uint32_t day = (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
4722 monthTable[aMonth - 1] + aDay) %
4723 7;
4724
4725 if (isoWeek) {
4726 return ((day + 6) % 7) + 1;
4727 }
4728
4729 return day;
4730 }
4731
MaximumWeekInYear(uint32_t aYear) const4732 uint32_t HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const {
4733 int day = DayOfWeek(aYear, 1, 1, true); // January 1.
4734 // A year starting on Thursday or a leap year starting on Wednesday has 53
4735 // weeks. All other years have 52 weeks.
4736 return day == 4 || (day == 3 && IsLeapYear(aYear)) ? kMaximumWeekInYear
4737 : kMaximumWeekInYear - 1;
4738 }
4739
IsValidWeek(const nsAString & aValue) const4740 bool HTMLInputElement::IsValidWeek(const nsAString& aValue) const {
4741 uint32_t year, week;
4742 return ParseWeek(aValue, &year, &week);
4743 }
4744
IsValidMonth(const nsAString & aValue) const4745 bool HTMLInputElement::IsValidMonth(const nsAString& aValue) const {
4746 uint32_t year, month;
4747 return ParseMonth(aValue, &year, &month);
4748 }
4749
IsValidDate(const nsAString & aValue) const4750 bool HTMLInputElement::IsValidDate(const nsAString& aValue) const {
4751 uint32_t year, month, day;
4752 return ParseDate(aValue, &year, &month, &day);
4753 }
4754
IsValidDateTimeLocal(const nsAString & aValue) const4755 bool HTMLInputElement::IsValidDateTimeLocal(const nsAString& aValue) const {
4756 uint32_t year, month, day, time;
4757 return ParseDateTimeLocal(aValue, &year, &month, &day, &time);
4758 }
4759
ParseYear(const nsAString & aValue,uint32_t * aYear) const4760 bool HTMLInputElement::ParseYear(const nsAString& aValue,
4761 uint32_t* aYear) const {
4762 if (aValue.Length() < 4) {
4763 return false;
4764 }
4765
4766 return DigitSubStringToNumber(aValue, 0, aValue.Length(), aYear) &&
4767 *aYear > 0;
4768 }
4769
ParseMonth(const nsAString & aValue,uint32_t * aYear,uint32_t * aMonth) const4770 bool HTMLInputElement::ParseMonth(const nsAString& aValue, uint32_t* aYear,
4771 uint32_t* aMonth) const {
4772 // Parse the year, month values out a string formatted as 'yyyy-mm'.
4773 if (aValue.Length() < 7) {
4774 return false;
4775 }
4776
4777 uint32_t endOfYearOffset = aValue.Length() - 3;
4778 if (aValue[endOfYearOffset] != '-') {
4779 return false;
4780 }
4781
4782 const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
4783 if (!ParseYear(yearStr, aYear)) {
4784 return false;
4785 }
4786
4787 return DigitSubStringToNumber(aValue, endOfYearOffset + 1, 2, aMonth) &&
4788 *aMonth > 0 && *aMonth <= 12;
4789 }
4790
ParseWeek(const nsAString & aValue,uint32_t * aYear,uint32_t * aWeek) const4791 bool HTMLInputElement::ParseWeek(const nsAString& aValue, uint32_t* aYear,
4792 uint32_t* aWeek) const {
4793 // Parse the year, month values out a string formatted as 'yyyy-Www'.
4794 if (aValue.Length() < 8) {
4795 return false;
4796 }
4797
4798 uint32_t endOfYearOffset = aValue.Length() - 4;
4799 if (aValue[endOfYearOffset] != '-') {
4800 return false;
4801 }
4802
4803 if (aValue[endOfYearOffset + 1] != 'W') {
4804 return false;
4805 }
4806
4807 const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
4808 if (!ParseYear(yearStr, aYear)) {
4809 return false;
4810 }
4811
4812 return DigitSubStringToNumber(aValue, endOfYearOffset + 2, 2, aWeek) &&
4813 *aWeek > 0 && *aWeek <= MaximumWeekInYear(*aYear);
4814 }
4815
ParseDate(const nsAString & aValue,uint32_t * aYear,uint32_t * aMonth,uint32_t * aDay) const4816 bool HTMLInputElement::ParseDate(const nsAString& aValue, uint32_t* aYear,
4817 uint32_t* aMonth, uint32_t* aDay) const {
4818 /*
4819 * Parse the year, month, day values out a date string formatted as
4820 * yyyy-mm-dd. -The year must be 4 or more digits long, and year > 0 -The
4821 * month must be exactly 2 digits long, and 01 <= month <= 12 -The day must be
4822 * exactly 2 digit long, and 01 <= day <= maxday Where maxday is the number of
4823 * days in the month 'month' and year 'year'
4824 */
4825 if (aValue.Length() < 10) {
4826 return false;
4827 }
4828
4829 uint32_t endOfMonthOffset = aValue.Length() - 3;
4830 if (aValue[endOfMonthOffset] != '-') {
4831 return false;
4832 }
4833
4834 const nsAString& yearMonthStr = Substring(aValue, 0, endOfMonthOffset);
4835 if (!ParseMonth(yearMonthStr, aYear, aMonth)) {
4836 return false;
4837 }
4838
4839 return DigitSubStringToNumber(aValue, endOfMonthOffset + 1, 2, aDay) &&
4840 *aDay > 0 && *aDay <= NumberOfDaysInMonth(*aMonth, *aYear);
4841 }
4842
ParseDateTimeLocal(const nsAString & aValue,uint32_t * aYear,uint32_t * aMonth,uint32_t * aDay,uint32_t * aTime) const4843 bool HTMLInputElement::ParseDateTimeLocal(const nsAString& aValue,
4844 uint32_t* aYear, uint32_t* aMonth,
4845 uint32_t* aDay,
4846 uint32_t* aTime) const {
4847 // Parse the year, month, day and time values out a string formatted as
4848 // 'yyyy-mm-ddThh:mm[:ss.s] or 'yyyy-mm-dd hh:mm[:ss.s]', where fractions of
4849 // seconds can be 1 to 3 digits.
4850 // The minimum length allowed is 16, which is of the form 'yyyy-mm-ddThh:mm'
4851 // or 'yyyy-mm-dd hh:mm'.
4852 if (aValue.Length() < 16) {
4853 return false;
4854 }
4855
4856 int32_t sepIndex = aValue.FindChar('T');
4857 if (sepIndex == -1) {
4858 sepIndex = aValue.FindChar(' ');
4859
4860 if (sepIndex == -1) {
4861 return false;
4862 }
4863 }
4864
4865 const nsAString& dateStr = Substring(aValue, 0, sepIndex);
4866 if (!ParseDate(dateStr, aYear, aMonth, aDay)) {
4867 return false;
4868 }
4869
4870 const nsAString& timeStr =
4871 Substring(aValue, sepIndex + 1, aValue.Length() - sepIndex + 1);
4872 if (!ParseTime(timeStr, aTime)) {
4873 return false;
4874 }
4875
4876 return true;
4877 }
4878
NormalizeDateTimeLocal(nsAString & aValue) const4879 void HTMLInputElement::NormalizeDateTimeLocal(nsAString& aValue) const {
4880 if (aValue.IsEmpty()) {
4881 return;
4882 }
4883
4884 // Use 'T' as the separator between date string and time string.
4885 int32_t sepIndex = aValue.FindChar(' ');
4886 if (sepIndex != -1) {
4887 aValue.ReplaceLiteral(sepIndex, 1, u"T");
4888 } else {
4889 sepIndex = aValue.FindChar('T');
4890 }
4891
4892 // Time expressed as the shortest possible string, which is hh:mm.
4893 if ((aValue.Length() - sepIndex) == 6) {
4894 return;
4895 }
4896
4897 // Fractions of seconds part is optional, ommit it if it's 0.
4898 if ((aValue.Length() - sepIndex) > 9) {
4899 const uint32_t millisecSepIndex = sepIndex + 9;
4900 uint32_t milliseconds;
4901 if (!DigitSubStringToNumber(aValue, millisecSepIndex + 1,
4902 aValue.Length() - (millisecSepIndex + 1),
4903 &milliseconds)) {
4904 return;
4905 }
4906
4907 if (milliseconds != 0) {
4908 return;
4909 }
4910
4911 aValue.Cut(millisecSepIndex, aValue.Length() - millisecSepIndex);
4912 }
4913
4914 // Seconds part is optional, ommit it if it's 0.
4915 const uint32_t secondSepIndex = sepIndex + 6;
4916 uint32_t seconds;
4917 if (!DigitSubStringToNumber(aValue, secondSepIndex + 1,
4918 aValue.Length() - (secondSepIndex + 1),
4919 &seconds)) {
4920 return;
4921 }
4922
4923 if (seconds != 0) {
4924 return;
4925 }
4926
4927 aValue.Cut(secondSepIndex, aValue.Length() - secondSepIndex);
4928 }
4929
DaysSinceEpochFromWeek(uint32_t aYear,uint32_t aWeek) const4930 double HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear,
4931 uint32_t aWeek) const {
4932 double days = JS::DayFromYear(aYear) + (aWeek - 1) * 7;
4933 uint32_t dayOneIsoWeekday = DayOfWeek(aYear, 1, 1, true);
4934
4935 // If day one of that year is on/before Thursday, we should subtract the
4936 // days that belong to last year in our first week, otherwise, our first
4937 // days belong to last year's last week, and we should add those days
4938 // back.
4939 if (dayOneIsoWeekday <= 4) {
4940 days -= (dayOneIsoWeekday - 1);
4941 } else {
4942 days += (7 - dayOneIsoWeekday + 1);
4943 }
4944
4945 return days;
4946 }
4947
NumberOfDaysInMonth(uint32_t aMonth,uint32_t aYear) const4948 uint32_t HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth,
4949 uint32_t aYear) const {
4950 /*
4951 * Returns the number of days in a month.
4952 * Months that are |longMonths| always have 31 days.
4953 * Months that are not |longMonths| have 30 days except February (month 2).
4954 * February has 29 days during leap years which are years that are divisible
4955 * by 400. or divisible by 100 and 4. February has 28 days otherwise.
4956 */
4957
4958 static const bool longMonths[] = {true, false, true, false, true, false,
4959 true, true, false, true, false, true};
4960 MOZ_ASSERT(aMonth <= 12 && aMonth > 0);
4961
4962 if (longMonths[aMonth - 1]) {
4963 return 31;
4964 }
4965
4966 if (aMonth != 2) {
4967 return 30;
4968 }
4969
4970 return IsLeapYear(aYear) ? 29 : 28;
4971 }
4972
4973 /* static */
DigitSubStringToNumber(const nsAString & aStr,uint32_t aStart,uint32_t aLen,uint32_t * aRetVal)4974 bool HTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
4975 uint32_t aStart, uint32_t aLen,
4976 uint32_t* aRetVal) {
4977 MOZ_ASSERT(aStr.Length() > (aStart + aLen - 1));
4978
4979 for (uint32_t offset = 0; offset < aLen; ++offset) {
4980 if (!IsAsciiDigit(aStr[aStart + offset])) {
4981 return false;
4982 }
4983 }
4984
4985 nsresult ec;
4986 *aRetVal = static_cast<uint32_t>(
4987 PromiseFlatString(Substring(aStr, aStart, aLen)).ToInteger(&ec));
4988
4989 return NS_SUCCEEDED(ec);
4990 }
4991
IsValidTime(const nsAString & aValue) const4992 bool HTMLInputElement::IsValidTime(const nsAString& aValue) const {
4993 return ParseTime(aValue, nullptr);
4994 }
4995
4996 /* static */
ParseTime(const nsAString & aValue,uint32_t * aResult)4997 bool HTMLInputElement::ParseTime(const nsAString& aValue, uint32_t* aResult) {
4998 /* The string must have the following parts:
4999 * - HOURS: two digits, value being in [0, 23];
5000 * - Colon (:);
5001 * - MINUTES: two digits, value being in [0, 59];
5002 * - Optional:
5003 * - Colon (:);
5004 * - SECONDS: two digits, value being in [0, 59];
5005 * - Optional:
5006 * - DOT (.);
5007 * - FRACTIONAL SECONDS: one to three digits, no value range.
5008 */
5009
5010 // The following format is the shorter one allowed: "HH:MM".
5011 if (aValue.Length() < 5) {
5012 return false;
5013 }
5014
5015 uint32_t hours;
5016 if (!DigitSubStringToNumber(aValue, 0, 2, &hours) || hours > 23) {
5017 return false;
5018 }
5019
5020 // Hours/minutes separator.
5021 if (aValue[2] != ':') {
5022 return false;
5023 }
5024
5025 uint32_t minutes;
5026 if (!DigitSubStringToNumber(aValue, 3, 2, &minutes) || minutes > 59) {
5027 return false;
5028 }
5029
5030 if (aValue.Length() == 5) {
5031 if (aResult) {
5032 *aResult = ((hours * 60) + minutes) * 60000;
5033 }
5034 return true;
5035 }
5036
5037 // The following format is the next shorter one: "HH:MM:SS".
5038 if (aValue.Length() < 8 || aValue[5] != ':') {
5039 return false;
5040 }
5041
5042 uint32_t seconds;
5043 if (!DigitSubStringToNumber(aValue, 6, 2, &seconds) || seconds > 59) {
5044 return false;
5045 }
5046
5047 if (aValue.Length() == 8) {
5048 if (aResult) {
5049 *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000;
5050 }
5051 return true;
5052 }
5053
5054 // The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
5055 // There can be 1 to 3 digits for the fractions of seconds.
5056 if (aValue.Length() == 9 || aValue.Length() > 12 || aValue[8] != '.') {
5057 return false;
5058 }
5059
5060 uint32_t fractionsSeconds;
5061 if (!DigitSubStringToNumber(aValue, 9, aValue.Length() - 9,
5062 &fractionsSeconds)) {
5063 return false;
5064 }
5065
5066 if (aResult) {
5067 *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000 +
5068 // NOTE: there is 10.0 instead of 10 and static_cast<int> because
5069 // some old [and stupid] compilers can't just do the right thing.
5070 fractionsSeconds *
5071 pow(10.0, static_cast<int>(3 - (aValue.Length() - 9)));
5072 }
5073
5074 return true;
5075 }
5076
5077 /* static */
IsDateTimeTypeSupported(FormControlType aDateTimeInputType)5078 bool HTMLInputElement::IsDateTimeTypeSupported(
5079 FormControlType aDateTimeInputType) {
5080 switch (aDateTimeInputType) {
5081 case FormControlType::InputDate:
5082 case FormControlType::InputTime:
5083 return true;
5084 case FormControlType::InputDatetimeLocal:
5085 return StaticPrefs::dom_forms_datetime_local();
5086 case FormControlType::InputMonth:
5087 case FormControlType::InputWeek:
5088 return StaticPrefs::dom_forms_datetime_others();
5089 default:
5090 return false;
5091 }
5092 }
5093
ParseAttribute(int32_t aNamespaceID,nsAtom * aAttribute,const nsAString & aValue,nsIPrincipal * aMaybeScriptedPrincipal,nsAttrValue & aResult)5094 bool HTMLInputElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
5095 const nsAString& aValue,
5096 nsIPrincipal* aMaybeScriptedPrincipal,
5097 nsAttrValue& aResult) {
5098 // We can't make these static_asserts because kInputDefaultType and
5099 // kInputTypeTable aren't constexpr.
5100 MOZ_ASSERT(
5101 FormControlType(kInputDefaultType->value) == FormControlType::InputText,
5102 "Someone forgot to update kInputDefaultType when adding a new "
5103 "input type.");
5104 MOZ_ASSERT(kInputTypeTable[ArrayLength(kInputTypeTable) - 1].tag == nullptr,
5105 "Last entry in the table must be the nullptr guard");
5106 MOZ_ASSERT(FormControlType(
5107 kInputTypeTable[ArrayLength(kInputTypeTable) - 2].value) ==
5108 FormControlType::InputText,
5109 "Next to last entry in the table must be the \"text\" entry");
5110
5111 if (aNamespaceID == kNameSpaceID_None) {
5112 if (aAttribute == nsGkAtoms::type) {
5113 aResult.ParseEnumValue(aValue, kInputTypeTable, false, kInputDefaultType);
5114 auto newType = FormControlType(aResult.GetEnumValue());
5115 if (IsDateTimeInputType(newType) && !IsDateTimeTypeSupported(newType)) {
5116 // There's no public way to set an nsAttrValue to an enum value, but we
5117 // can just re-parse with a table that doesn't have any types other than
5118 // "text" in it.
5119 aResult.ParseEnumValue(aValue, kInputDefaultType, false,
5120 kInputDefaultType);
5121 }
5122
5123 return true;
5124 }
5125 if (aAttribute == nsGkAtoms::width) {
5126 return aResult.ParseHTMLDimension(aValue);
5127 }
5128 if (aAttribute == nsGkAtoms::height) {
5129 return aResult.ParseHTMLDimension(aValue);
5130 }
5131 if (aAttribute == nsGkAtoms::maxlength) {
5132 return aResult.ParseNonNegativeIntValue(aValue);
5133 }
5134 if (aAttribute == nsGkAtoms::minlength) {
5135 return aResult.ParseNonNegativeIntValue(aValue);
5136 }
5137 if (aAttribute == nsGkAtoms::size) {
5138 return aResult.ParsePositiveIntValue(aValue);
5139 }
5140 if (aAttribute == nsGkAtoms::align) {
5141 return ParseAlignValue(aValue, aResult);
5142 }
5143 if (aAttribute == nsGkAtoms::formmethod) {
5144 if (StaticPrefs::dom_dialog_element_enabled() || IsInChromeDocument()) {
5145 return aResult.ParseEnumValue(aValue, kFormMethodTableDialogEnabled,
5146 false);
5147 }
5148 return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
5149 }
5150 if (aAttribute == nsGkAtoms::formenctype) {
5151 return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
5152 }
5153 if (aAttribute == nsGkAtoms::autocomplete) {
5154 aResult.ParseAtomArray(aValue);
5155 return true;
5156 }
5157 if (aAttribute == nsGkAtoms::capture) {
5158 return aResult.ParseEnumValue(aValue, kCaptureTable, false,
5159 kCaptureDefault);
5160 }
5161 if (ParseImageAttribute(aAttribute, aValue, aResult)) {
5162 // We have to call |ParseImageAttribute| unconditionally since we
5163 // don't know if we're going to have a type="image" attribute yet,
5164 // (or could have it set dynamically in the future). See bug
5165 // 214077.
5166 return true;
5167 }
5168 }
5169
5170 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
5171 aMaybeScriptedPrincipal, aResult);
5172 }
5173
ImageInputMapAttributesIntoRule(const nsMappedAttributes * aAttributes,MappedDeclarations & aDecls)5174 void HTMLInputElement::ImageInputMapAttributesIntoRule(
5175 const nsMappedAttributes* aAttributes, MappedDeclarations& aDecls) {
5176 nsGenericHTMLFormElementWithState::MapImageBorderAttributeInto(aAttributes,
5177 aDecls);
5178 nsGenericHTMLFormElementWithState::MapImageMarginAttributeInto(aAttributes,
5179 aDecls);
5180 nsGenericHTMLFormElementWithState::MapImageSizeAttributesInto(
5181 aAttributes, aDecls, MapAspectRatio::Yes);
5182 // Images treat align as "float"
5183 nsGenericHTMLFormElementWithState::MapImageAlignAttributeInto(aAttributes,
5184 aDecls);
5185
5186 nsGenericHTMLFormElementWithState::MapCommonAttributesInto(aAttributes,
5187 aDecls);
5188 }
5189
GetAttributeChangeHint(const nsAtom * aAttribute,int32_t aModType) const5190 nsChangeHint HTMLInputElement::GetAttributeChangeHint(const nsAtom* aAttribute,
5191 int32_t aModType) const {
5192 nsChangeHint retval =
5193 nsGenericHTMLFormElementWithState::GetAttributeChangeHint(aAttribute,
5194 aModType);
5195
5196 const bool isAdditionOrRemoval =
5197 aModType == MutationEvent_Binding::ADDITION ||
5198 aModType == MutationEvent_Binding::REMOVAL;
5199
5200 const bool reconstruct = [&] {
5201 if (aAttribute == nsGkAtoms::type) {
5202 return true;
5203 }
5204
5205 if (PlaceholderApplies() && aAttribute == nsGkAtoms::placeholder &&
5206 isAdditionOrRemoval) {
5207 // We need to re-create our placeholder text.
5208 return true;
5209 }
5210
5211 if (mType == FormControlType::InputFile &&
5212 (aAttribute == nsGkAtoms::allowdirs ||
5213 aAttribute == nsGkAtoms::webkitdirectory)) {
5214 // The presence or absence of the 'directory' attribute determines what
5215 // value we show in the file label when empty, via GetDisplayFileName.
5216 return true;
5217 }
5218
5219 if (mType == FormControlType::InputImage && isAdditionOrRemoval &&
5220 (aAttribute == nsGkAtoms::alt || aAttribute == nsGkAtoms::value)) {
5221 // We might need to rebuild our alt text. Just go ahead and
5222 // reconstruct our frame. This should be quite rare..
5223 return true;
5224 }
5225 return false;
5226 }();
5227
5228 if (reconstruct) {
5229 retval |= nsChangeHint_ReconstructFrame;
5230 } else if (aAttribute == nsGkAtoms::value) {
5231 retval |= NS_STYLE_HINT_REFLOW;
5232 } else if (aAttribute == nsGkAtoms::size && IsSingleLineTextControl(false)) {
5233 retval |= NS_STYLE_HINT_REFLOW;
5234 }
5235
5236 return retval;
5237 }
5238
NS_IMETHODIMP_(bool)5239 NS_IMETHODIMP_(bool)
5240 HTMLInputElement::IsAttributeMapped(const nsAtom* aAttribute) const {
5241 static const MappedAttributeEntry attributes[] = {
5242 {nsGkAtoms::align},
5243 {nullptr},
5244 };
5245
5246 static const MappedAttributeEntry* const map[] = {
5247 attributes,
5248 sCommonAttributeMap,
5249 sImageMarginSizeAttributeMap,
5250 sImageBorderAttributeMap,
5251 };
5252
5253 return FindAttributeDependence(aAttribute, map);
5254 }
5255
GetAttributeMappingFunction() const5256 nsMapRuleToAttributesFunc HTMLInputElement::GetAttributeMappingFunction()
5257 const {
5258 // GetAttributeChangeHint guarantees that changes to mType will trigger a
5259 // reframe, and we update the mapping function in our mapped attrs when our
5260 // type changes, so it's safe to condition our attribute mapping function on
5261 // mType.
5262 if (mType == FormControlType::InputImage) {
5263 return &ImageInputMapAttributesIntoRule;
5264 }
5265
5266 return &MapCommonAttributesInto;
5267 }
5268
5269 // Directory picking methods:
5270
IsFilesAndDirectoriesSupported() const5271 bool HTMLInputElement::IsFilesAndDirectoriesSupported() const {
5272 // This method is supposed to return true if a file and directory picker
5273 // supports the selection of both files and directories *at the same time*.
5274 // Only Mac currently supports that. We could implement it for Mac, but
5275 // currently we do not.
5276 return false;
5277 }
5278
ChooseDirectory(ErrorResult & aRv)5279 void HTMLInputElement::ChooseDirectory(ErrorResult& aRv) {
5280 if (mType != FormControlType::InputFile) {
5281 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5282 return;
5283 }
5284 // Script can call this method directly, so even though we don't show the
5285 // "Pick Folder..." button on platforms that don't have a directory picker
5286 // we have to redirect to the file picker here.
5287 InitFilePicker(
5288 #if defined(ANDROID)
5289 // No native directory picker - redirect to plain file picker
5290 FILE_PICKER_FILE
5291 #else
5292 FILE_PICKER_DIRECTORY
5293 #endif
5294 );
5295 }
5296
GetFilesAndDirectories(ErrorResult & aRv)5297 already_AddRefed<Promise> HTMLInputElement::GetFilesAndDirectories(
5298 ErrorResult& aRv) {
5299 if (mType != FormControlType::InputFile) {
5300 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5301 return nullptr;
5302 }
5303
5304 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
5305 MOZ_ASSERT(global);
5306 if (!global) {
5307 return nullptr;
5308 }
5309
5310 RefPtr<Promise> p = Promise::Create(global, aRv);
5311 if (aRv.Failed()) {
5312 return nullptr;
5313 }
5314
5315 const nsTArray<OwningFileOrDirectory>& filesAndDirs =
5316 GetFilesOrDirectoriesInternal();
5317
5318 Sequence<OwningFileOrDirectory> filesAndDirsSeq;
5319
5320 if (!filesAndDirsSeq.SetLength(filesAndDirs.Length(),
5321 mozilla::fallible_t())) {
5322 p->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
5323 return p.forget();
5324 }
5325
5326 for (uint32_t i = 0; i < filesAndDirs.Length(); ++i) {
5327 if (filesAndDirs[i].IsDirectory()) {
5328 RefPtr<Directory> directory = filesAndDirs[i].GetAsDirectory();
5329
5330 // In future we could refactor SetFilePickerFiltersFromAccept to return a
5331 // semicolon separated list of file extensions and include that in the
5332 // filter string passed here.
5333 directory->SetContentFilters(u"filter-out-sensitive"_ns);
5334 filesAndDirsSeq[i].SetAsDirectory() = directory;
5335 } else {
5336 MOZ_ASSERT(filesAndDirs[i].IsFile());
5337
5338 // This file was directly selected by the user, so don't filter it.
5339 filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i].GetAsFile();
5340 }
5341 }
5342
5343 p->MaybeResolve(filesAndDirsSeq);
5344 return p.forget();
5345 }
5346
GetFiles(bool aRecursiveFlag,ErrorResult & aRv)5347 already_AddRefed<Promise> HTMLInputElement::GetFiles(bool aRecursiveFlag,
5348 ErrorResult& aRv) {
5349 if (mType != FormControlType::InputFile) {
5350 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5351 return nullptr;
5352 }
5353
5354 GetFilesHelper* helper = GetOrCreateGetFilesHelper(aRecursiveFlag, aRv);
5355 if (NS_WARN_IF(aRv.Failed())) {
5356 return nullptr;
5357 }
5358 MOZ_ASSERT(helper);
5359
5360 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
5361 MOZ_ASSERT(global);
5362 if (!global) {
5363 return nullptr;
5364 }
5365
5366 RefPtr<Promise> p = Promise::Create(global, aRv);
5367 if (aRv.Failed()) {
5368 return nullptr;
5369 }
5370
5371 helper->AddPromise(p);
5372 return p.forget();
5373 }
5374
5375 // Controllers Methods
5376
GetControllers(ErrorResult & aRv)5377 nsIControllers* HTMLInputElement::GetControllers(ErrorResult& aRv) {
5378 // XXX: what about type "file"?
5379 if (IsSingleLineTextControl(false)) {
5380 if (!mControllers) {
5381 mControllers = new nsXULControllers();
5382 if (!mControllers) {
5383 aRv.Throw(NS_ERROR_FAILURE);
5384 return nullptr;
5385 }
5386
5387 RefPtr<nsBaseCommandController> commandController =
5388 nsBaseCommandController::CreateEditorController();
5389 if (!commandController) {
5390 aRv.Throw(NS_ERROR_FAILURE);
5391 return nullptr;
5392 }
5393
5394 mControllers->AppendController(commandController);
5395
5396 commandController = nsBaseCommandController::CreateEditingController();
5397 if (!commandController) {
5398 aRv.Throw(NS_ERROR_FAILURE);
5399 return nullptr;
5400 }
5401
5402 mControllers->AppendController(commandController);
5403 }
5404 }
5405
5406 return mControllers;
5407 }
5408
GetControllers(nsIControllers ** aResult)5409 nsresult HTMLInputElement::GetControllers(nsIControllers** aResult) {
5410 NS_ENSURE_ARG_POINTER(aResult);
5411
5412 ErrorResult rv;
5413 RefPtr<nsIControllers> controller = GetControllers(rv);
5414 controller.forget(aResult);
5415 return rv.StealNSResult();
5416 }
5417
InputTextLength(CallerType aCallerType)5418 int32_t HTMLInputElement::InputTextLength(CallerType aCallerType) {
5419 nsAutoString val;
5420 GetValue(val, aCallerType);
5421 return val.Length();
5422 }
5423
SetSelectionRange(uint32_t aSelectionStart,uint32_t aSelectionEnd,const Optional<nsAString> & aDirection,ErrorResult & aRv)5424 void HTMLInputElement::SetSelectionRange(uint32_t aSelectionStart,
5425 uint32_t aSelectionEnd,
5426 const Optional<nsAString>& aDirection,
5427 ErrorResult& aRv) {
5428 if (!SupportsTextSelection()) {
5429 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5430 return;
5431 }
5432
5433 TextControlState* state = GetEditorState();
5434 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5435 state->SetSelectionRange(aSelectionStart, aSelectionEnd, aDirection, aRv);
5436 }
5437
SetRangeText(const nsAString & aReplacement,ErrorResult & aRv)5438 void HTMLInputElement::SetRangeText(const nsAString& aReplacement,
5439 ErrorResult& aRv) {
5440 if (!SupportsTextSelection()) {
5441 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5442 return;
5443 }
5444
5445 TextControlState* state = GetEditorState();
5446 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5447 state->SetRangeText(aReplacement, aRv);
5448 }
5449
SetRangeText(const nsAString & aReplacement,uint32_t aStart,uint32_t aEnd,SelectionMode aSelectMode,ErrorResult & aRv)5450 void HTMLInputElement::SetRangeText(const nsAString& aReplacement,
5451 uint32_t aStart, uint32_t aEnd,
5452 SelectionMode aSelectMode,
5453 ErrorResult& aRv) {
5454 if (!SupportsTextSelection()) {
5455 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5456 return;
5457 }
5458
5459 TextControlState* state = GetEditorState();
5460 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5461 state->SetRangeText(aReplacement, aStart, aEnd, aSelectMode, aRv);
5462 }
5463
GetValueFromSetRangeText(nsAString & aValue)5464 void HTMLInputElement::GetValueFromSetRangeText(nsAString& aValue) {
5465 GetNonFileValueInternal(aValue);
5466 }
5467
SetValueFromSetRangeText(const nsAString & aValue)5468 nsresult HTMLInputElement::SetValueFromSetRangeText(const nsAString& aValue) {
5469 return SetValueInternal(aValue, {ValueSetterOption::ByContentAPI,
5470 ValueSetterOption::BySetRangeTextAPI,
5471 ValueSetterOption::SetValueChanged});
5472 }
5473
GetSelectionStart(ErrorResult & aRv)5474 Nullable<uint32_t> HTMLInputElement::GetSelectionStart(ErrorResult& aRv) {
5475 if (!SupportsTextSelection()) {
5476 return Nullable<uint32_t>();
5477 }
5478
5479 uint32_t selStart = GetSelectionStartIgnoringType(aRv);
5480 if (aRv.Failed()) {
5481 return Nullable<uint32_t>();
5482 }
5483
5484 return Nullable<uint32_t>(selStart);
5485 }
5486
GetSelectionStartIgnoringType(ErrorResult & aRv)5487 uint32_t HTMLInputElement::GetSelectionStartIgnoringType(ErrorResult& aRv) {
5488 uint32_t selEnd, selStart;
5489 GetSelectionRange(&selStart, &selEnd, aRv);
5490 return selStart;
5491 }
5492
SetSelectionStart(const Nullable<uint32_t> & aSelectionStart,ErrorResult & aRv)5493 void HTMLInputElement::SetSelectionStart(
5494 const Nullable<uint32_t>& aSelectionStart, ErrorResult& aRv) {
5495 if (!SupportsTextSelection()) {
5496 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5497 return;
5498 }
5499
5500 TextControlState* state = GetEditorState();
5501 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5502 state->SetSelectionStart(aSelectionStart, aRv);
5503 }
5504
GetSelectionEnd(ErrorResult & aRv)5505 Nullable<uint32_t> HTMLInputElement::GetSelectionEnd(ErrorResult& aRv) {
5506 if (!SupportsTextSelection()) {
5507 return Nullable<uint32_t>();
5508 }
5509
5510 uint32_t selEnd = GetSelectionEndIgnoringType(aRv);
5511 if (aRv.Failed()) {
5512 return Nullable<uint32_t>();
5513 }
5514
5515 return Nullable<uint32_t>(selEnd);
5516 }
5517
GetSelectionEndIgnoringType(ErrorResult & aRv)5518 uint32_t HTMLInputElement::GetSelectionEndIgnoringType(ErrorResult& aRv) {
5519 uint32_t selEnd, selStart;
5520 GetSelectionRange(&selStart, &selEnd, aRv);
5521 return selEnd;
5522 }
5523
SetSelectionEnd(const Nullable<uint32_t> & aSelectionEnd,ErrorResult & aRv)5524 void HTMLInputElement::SetSelectionEnd(const Nullable<uint32_t>& aSelectionEnd,
5525 ErrorResult& aRv) {
5526 if (!SupportsTextSelection()) {
5527 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5528 return;
5529 }
5530
5531 TextControlState* state = GetEditorState();
5532 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5533 state->SetSelectionEnd(aSelectionEnd, aRv);
5534 }
5535
GetSelectionRange(uint32_t * aSelectionStart,uint32_t * aSelectionEnd,ErrorResult & aRv)5536 void HTMLInputElement::GetSelectionRange(uint32_t* aSelectionStart,
5537 uint32_t* aSelectionEnd,
5538 ErrorResult& aRv) {
5539 TextControlState* state = GetEditorState();
5540 if (!state) {
5541 // Not a text control.
5542 aRv.Throw(NS_ERROR_UNEXPECTED);
5543 return;
5544 }
5545
5546 state->GetSelectionRange(aSelectionStart, aSelectionEnd, aRv);
5547 }
5548
GetSelectionDirection(nsAString & aDirection,ErrorResult & aRv)5549 void HTMLInputElement::GetSelectionDirection(nsAString& aDirection,
5550 ErrorResult& aRv) {
5551 if (!SupportsTextSelection()) {
5552 aDirection.SetIsVoid(true);
5553 return;
5554 }
5555
5556 TextControlState* state = GetEditorState();
5557 MOZ_ASSERT(state, "SupportsTextSelection came back true!");
5558 state->GetSelectionDirectionString(aDirection, aRv);
5559 }
5560
SetSelectionDirection(const nsAString & aDirection,ErrorResult & aRv)5561 void HTMLInputElement::SetSelectionDirection(const nsAString& aDirection,
5562 ErrorResult& aRv) {
5563 if (!SupportsTextSelection()) {
5564 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5565 return;
5566 }
5567
5568 TextControlState* state = GetEditorState();
5569 MOZ_ASSERT(state, "SupportsTextSelection came back true!");
5570 state->SetSelectionDirection(aDirection, aRv);
5571 }
5572
5573 #ifdef ACCESSIBILITY
FireEventForAccessibility(HTMLInputElement * aTarget,EventMessage aEventMessage)5574 /*static*/ nsresult FireEventForAccessibility(HTMLInputElement* aTarget,
5575 EventMessage aEventMessage) {
5576 Element* element = static_cast<Element*>(aTarget);
5577 return nsContentUtils::DispatchTrustedEvent<WidgetEvent>(
5578 element->OwnerDoc(), element, aEventMessage, CanBubble::eYes,
5579 Cancelable::eYes);
5580 }
5581 #endif
5582
UpdateApzAwareFlag()5583 void HTMLInputElement::UpdateApzAwareFlag() {
5584 #if !defined(ANDROID) && !defined(XP_MACOSX)
5585 if (mType == FormControlType::InputNumber ||
5586 mType == FormControlType::InputRange) {
5587 SetMayBeApzAware();
5588 }
5589 #endif
5590 }
5591
SetDefaultValueAsValue()5592 nsresult HTMLInputElement::SetDefaultValueAsValue() {
5593 NS_ASSERTION(GetValueMode() == VALUE_MODE_VALUE,
5594 "GetValueMode() should return VALUE_MODE_VALUE!");
5595
5596 // The element has a content attribute value different from it's value when
5597 // it's in the value mode value.
5598 nsAutoString resetVal;
5599 GetDefaultValue(resetVal);
5600
5601 // SetValueInternal is going to sanitize the value.
5602 // TODO(mbrodesser): sanitizing will only happen if `mDoneCreating` is true.
5603 return SetValueInternal(resetVal, ValueSetterOption::ByInternalAPI);
5604 }
5605
SetDirectionFromValue(bool aNotify)5606 void HTMLInputElement::SetDirectionFromValue(bool aNotify) {
5607 if (IsSingleLineTextControl(true)) {
5608 nsAutoString value;
5609 GetValue(value, CallerType::System);
5610 SetDirectionalityFromValue(this, value, aNotify);
5611 }
5612 }
5613
5614 NS_IMETHODIMP
Reset()5615 HTMLInputElement::Reset() {
5616 // We should be able to reset all dirty flags regardless of the type.
5617 SetCheckedChanged(false);
5618 SetValueChanged(false);
5619 SetLastValueChangeWasInteractive(false);
5620
5621 switch (GetValueMode()) {
5622 case VALUE_MODE_VALUE: {
5623 nsresult result = SetDefaultValueAsValue();
5624 if (CreatesDateTimeWidget()) {
5625 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded`
5626 // can fire a change event if necessary.
5627 GetValue(mFocusedValue, CallerType::System);
5628 }
5629 return result;
5630 }
5631 case VALUE_MODE_DEFAULT_ON:
5632 DoSetChecked(DefaultChecked(), true, false);
5633 return NS_OK;
5634 case VALUE_MODE_FILENAME:
5635 ClearFiles(false);
5636 return NS_OK;
5637 case VALUE_MODE_DEFAULT:
5638 default:
5639 return NS_OK;
5640 }
5641 }
5642
5643 NS_IMETHODIMP
SubmitNamesValues(HTMLFormSubmission * aFormSubmission)5644 HTMLInputElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission) {
5645 // Disabled elements don't submit
5646 // For type=reset, and type=button, we just never submit, period.
5647 // For type=image and type=button, we only submit if we were the button
5648 // pressed
5649 // For type=radio and type=checkbox, we only submit if checked=true
5650 if (IsDisabled() || mType == FormControlType::InputReset ||
5651 mType == FormControlType::InputButton ||
5652 ((mType == FormControlType::InputSubmit ||
5653 mType == FormControlType::InputImage) &&
5654 aFormSubmission->GetSubmitterElement() != this) ||
5655 ((mType == FormControlType::InputRadio ||
5656 mType == FormControlType::InputCheckbox) &&
5657 !mChecked)) {
5658 return NS_OK;
5659 }
5660
5661 // Get the name
5662 nsAutoString name;
5663 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
5664
5665 // Submit .x, .y for input type=image
5666 if (mType == FormControlType::InputImage) {
5667 // Get a property set by the frame to find out where it was clicked.
5668 nsIntPoint* lastClickedPoint =
5669 static_cast<nsIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
5670 int32_t x, y;
5671 if (lastClickedPoint) {
5672 // Convert the values to strings for submission
5673 x = lastClickedPoint->x;
5674 y = lastClickedPoint->y;
5675 } else {
5676 x = y = 0;
5677 }
5678
5679 nsAutoString xVal, yVal;
5680 xVal.AppendInt(x);
5681 yVal.AppendInt(y);
5682
5683 if (!name.IsEmpty()) {
5684 aFormSubmission->AddNameValuePair(name + u".x"_ns, xVal);
5685 aFormSubmission->AddNameValuePair(name + u".y"_ns, yVal);
5686 } else {
5687 // If the Image Element has no name, simply return x and y
5688 // to Nav and IE compatibility.
5689 aFormSubmission->AddNameValuePair(u"x"_ns, xVal);
5690 aFormSubmission->AddNameValuePair(u"y"_ns, yVal);
5691 }
5692
5693 return NS_OK;
5694 }
5695
5696 // If name not there, don't submit
5697 if (name.IsEmpty()) {
5698 return NS_OK;
5699 }
5700
5701 //
5702 // Submit file if its input type=file and this encoding method accepts files
5703 //
5704 if (mType == FormControlType::InputFile) {
5705 // Submit files
5706
5707 const nsTArray<OwningFileOrDirectory>& files =
5708 GetFilesOrDirectoriesInternal();
5709
5710 if (files.IsEmpty()) {
5711 NS_ENSURE_STATE(GetOwnerGlobal());
5712 ErrorResult rv;
5713 RefPtr<Blob> blob = Blob::CreateStringBlob(
5714 GetOwnerGlobal(), ""_ns, u"application/octet-stream"_ns);
5715 RefPtr<File> file = blob->ToFile(u""_ns, rv);
5716
5717 if (!rv.Failed()) {
5718 aFormSubmission->AddNameBlobPair(name, file);
5719 }
5720
5721 return rv.StealNSResult();
5722 }
5723
5724 for (uint32_t i = 0; i < files.Length(); ++i) {
5725 if (files[i].IsFile()) {
5726 aFormSubmission->AddNameBlobPair(name, files[i].GetAsFile());
5727 } else {
5728 MOZ_ASSERT(files[i].IsDirectory());
5729 aFormSubmission->AddNameDirectoryPair(name, files[i].GetAsDirectory());
5730 }
5731 }
5732
5733 return NS_OK;
5734 }
5735
5736 if (mType == FormControlType::InputHidden &&
5737 name.LowerCaseEqualsLiteral("_charset_")) {
5738 nsCString charset;
5739 aFormSubmission->GetCharset(charset);
5740 return aFormSubmission->AddNameValuePair(name,
5741 NS_ConvertASCIItoUTF16(charset));
5742 }
5743
5744 //
5745 // Submit name=value
5746 //
5747
5748 // Get the value
5749 nsAutoString value;
5750 GetValue(value, CallerType::System);
5751
5752 if (mType == FormControlType::InputSubmit && value.IsEmpty() &&
5753 !HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
5754 // Get our default value, which is the same as our default label
5755 nsAutoString defaultValue;
5756 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
5757 "Submit", OwnerDoc(), defaultValue);
5758 value = defaultValue;
5759 }
5760
5761 return aFormSubmission->AddNameValuePair(name, value);
5762 }
5763
SaveFileContentData(const nsTArray<OwningFileOrDirectory> & aArray)5764 static nsTArray<FileContentData> SaveFileContentData(
5765 const nsTArray<OwningFileOrDirectory>& aArray) {
5766 nsTArray<FileContentData> res(aArray.Length());
5767 for (auto& it : aArray) {
5768 if (it.IsFile()) {
5769 RefPtr<BlobImpl> impl = it.GetAsFile()->Impl();
5770 res.AppendElement(std::move(impl));
5771 } else {
5772 MOZ_ASSERT(it.IsDirectory());
5773 nsString fullPath;
5774 nsresult rv = it.GetAsDirectory()->GetFullRealPath(fullPath);
5775 if (NS_WARN_IF(NS_FAILED(rv))) {
5776 continue;
5777 }
5778 res.AppendElement(std::move(fullPath));
5779 }
5780 }
5781 return res;
5782 }
5783
5784 NS_IMETHODIMP
SaveState()5785 HTMLInputElement::SaveState() {
5786 PresState* state = nullptr;
5787 switch (GetValueMode()) {
5788 case VALUE_MODE_DEFAULT_ON:
5789 if (mCheckedChanged) {
5790 state = GetPrimaryPresState();
5791 if (!state) {
5792 return NS_OK;
5793 }
5794
5795 state->contentData() = CheckedContentData(mChecked);
5796 }
5797 break;
5798 case VALUE_MODE_FILENAME:
5799 if (!mFileData->mFilesOrDirectories.IsEmpty()) {
5800 state = GetPrimaryPresState();
5801 if (!state) {
5802 return NS_OK;
5803 }
5804
5805 state->contentData() =
5806 SaveFileContentData(mFileData->mFilesOrDirectories);
5807 }
5808 break;
5809 case VALUE_MODE_VALUE:
5810 case VALUE_MODE_DEFAULT:
5811 // VALUE_MODE_DEFAULT shouldn't have their value saved except 'hidden',
5812 // mType should have never been FormControlType::InputPassword and value
5813 // should have changed.
5814 if ((GetValueMode() == VALUE_MODE_DEFAULT &&
5815 mType != FormControlType::InputHidden) ||
5816 mHasBeenTypePassword || !mValueChanged) {
5817 break;
5818 }
5819
5820 state = GetPrimaryPresState();
5821 if (!state) {
5822 return NS_OK;
5823 }
5824
5825 nsAutoString value;
5826 GetValue(value, CallerType::System);
5827
5828 if (!IsSingleLineTextControl(false)) {
5829 nsresult rv = nsLinebreakConverter::ConvertStringLineBreaks(
5830 value, nsLinebreakConverter::eLinebreakPlatform,
5831 nsLinebreakConverter::eLinebreakContent);
5832
5833 if (NS_FAILED(rv)) {
5834 NS_ERROR("Converting linebreaks failed!");
5835 return rv;
5836 }
5837 }
5838
5839 state->contentData() =
5840 TextContentData(value, mLastValueChangeWasInteractive);
5841 break;
5842 }
5843
5844 if (mDisabledChanged) {
5845 if (!state) {
5846 state = GetPrimaryPresState();
5847 }
5848 if (state) {
5849 // We do not want to save the real disabled state but the disabled
5850 // attribute.
5851 state->disabled() = HasAttr(kNameSpaceID_None, nsGkAtoms::disabled);
5852 state->disabledSet() = true;
5853 }
5854 }
5855
5856 return NS_OK;
5857 }
5858
DoneCreatingElement()5859 void HTMLInputElement::DoneCreatingElement() {
5860 mDoneCreating = true;
5861
5862 //
5863 // Restore state as needed. Note that disabled state applies to all control
5864 // types.
5865 //
5866 bool restoredCheckedState = false;
5867 if (!mInhibitRestoration) {
5868 GenerateStateKey();
5869 restoredCheckedState = RestoreFormControlState();
5870 }
5871
5872 //
5873 // If restore does not occur, we initialize .checked using the CHECKED
5874 // property.
5875 //
5876 if (!restoredCheckedState && mShouldInitChecked) {
5877 DoSetChecked(DefaultChecked(), false, false);
5878 }
5879
5880 // Sanitize the value and potentially set mFocusedValue.
5881 if (GetValueMode() == VALUE_MODE_VALUE) {
5882 nsAutoString aValue;
5883 GetValue(aValue, CallerType::System);
5884 // TODO: What should we do if SetValueInternal fails? (The allocation
5885 // may potentially be big, but most likely we've failed to allocate
5886 // before the type change.)
5887 SetValueInternal(aValue, ValueSetterOption::ByInternalAPI);
5888
5889 if (CreatesDateTimeWidget()) {
5890 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded` can
5891 // fire a change event if necessary.
5892 mFocusedValue = aValue;
5893 }
5894 }
5895
5896 mShouldInitChecked = false;
5897 }
5898
DestroyContent()5899 void HTMLInputElement::DestroyContent() {
5900 nsImageLoadingContent::Destroy();
5901 TextControlElement::DestroyContent();
5902 }
5903
IntrinsicState() const5904 EventStates HTMLInputElement::IntrinsicState() const {
5905 // If you add states here, and they're type-dependent, you need to add them
5906 // to the type case in AfterSetAttr.
5907
5908 EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
5909 if (mType == FormControlType::InputCheckbox ||
5910 mType == FormControlType::InputRadio) {
5911 // Check current checked state (:checked)
5912 if (mChecked) {
5913 state |= NS_EVENT_STATE_CHECKED;
5914 }
5915
5916 // Check current indeterminate state (:indeterminate)
5917 if (mType == FormControlType::InputCheckbox && mIndeterminate) {
5918 state |= NS_EVENT_STATE_INDETERMINATE;
5919 }
5920
5921 if (mType == FormControlType::InputRadio) {
5922 HTMLInputElement* selected = GetSelectedRadioButton();
5923 bool indeterminate = !selected && !mChecked;
5924
5925 if (indeterminate) {
5926 state |= NS_EVENT_STATE_INDETERMINATE;
5927 }
5928 }
5929
5930 // Check whether we are the default checked element (:default)
5931 if (DefaultChecked()) {
5932 state |= NS_EVENT_STATE_DEFAULT;
5933 }
5934 } else if (mType == FormControlType::InputImage) {
5935 state |= nsImageLoadingContent::ImageState();
5936 }
5937
5938 if (IsCandidateForConstraintValidation()) {
5939 if (IsValid()) {
5940 state |= NS_EVENT_STATE_VALID;
5941 } else {
5942 state |= NS_EVENT_STATE_INVALID;
5943
5944 if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
5945 (mCanShowInvalidUI && ShouldShowValidityUI())) {
5946 state |= NS_EVENT_STATE_MOZ_UI_INVALID;
5947 }
5948 }
5949
5950 // :-moz-ui-valid applies if all of the following conditions are true:
5951 // 1. The element is not focused, or had either :-moz-ui-valid or
5952 // :-moz-ui-invalid applying before it was focused ;
5953 // 2. The element is either valid or isn't allowed to have
5954 // :-moz-ui-invalid applying ;
5955 // 3. The element has already been modified or the user tried to submit the
5956 // form owner while invalid.
5957 if (mCanShowValidUI && ShouldShowValidityUI() &&
5958 (IsValid() || (!state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
5959 !mCanShowInvalidUI))) {
5960 state |= NS_EVENT_STATE_MOZ_UI_VALID;
5961 }
5962
5963 // :in-range and :out-of-range only apply if the element currently has a
5964 // range
5965 if (mHasRange) {
5966 state |= (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) ||
5967 GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW))
5968 ? NS_EVENT_STATE_OUTOFRANGE
5969 : NS_EVENT_STATE_INRANGE;
5970 }
5971 }
5972
5973 if (PlaceholderApplies() && HasAttr(nsGkAtoms::placeholder) &&
5974 ShouldShowPlaceholder()) {
5975 state |= NS_EVENT_STATE_PLACEHOLDERSHOWN;
5976 }
5977
5978 if (mForm && !mForm->GetValidity() && IsSubmitControl()) {
5979 state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
5980 }
5981
5982 return state;
5983 }
5984
ShouldShowPlaceholder() const5985 bool HTMLInputElement::ShouldShowPlaceholder() const {
5986 MOZ_ASSERT(PlaceholderApplies());
5987 return IsValueEmpty();
5988 }
5989
RestoreFileContentData(nsPIDOMWindowInner * aWindow,const nsTArray<FileContentData> & aData)5990 static nsTArray<OwningFileOrDirectory> RestoreFileContentData(
5991 nsPIDOMWindowInner* aWindow, const nsTArray<FileContentData>& aData) {
5992 nsTArray<OwningFileOrDirectory> res(aData.Length());
5993 for (auto& it : aData) {
5994 if (it.type() == FileContentData::TBlobImpl) {
5995 if (!it.get_BlobImpl()) {
5996 // Serialization failed, skip this file.
5997 continue;
5998 }
5999
6000 RefPtr<File> file = File::Create(aWindow->AsGlobal(), it.get_BlobImpl());
6001 if (NS_WARN_IF(!file)) {
6002 continue;
6003 }
6004
6005 OwningFileOrDirectory* element = res.AppendElement();
6006 element->SetAsFile() = file;
6007 } else {
6008 MOZ_ASSERT(it.type() == FileContentData::TnsString);
6009 nsCOMPtr<nsIFile> file;
6010 nsresult rv =
6011 NS_NewLocalFile(it.get_nsString(), true, getter_AddRefs(file));
6012 if (NS_WARN_IF(NS_FAILED(rv))) {
6013 continue;
6014 }
6015
6016 RefPtr<Directory> directory =
6017 Directory::Create(aWindow->AsGlobal(), file);
6018 MOZ_ASSERT(directory);
6019
6020 OwningFileOrDirectory* element = res.AppendElement();
6021 element->SetAsDirectory() = directory;
6022 }
6023 }
6024 return res;
6025 }
6026
RestoreState(PresState * aState)6027 bool HTMLInputElement::RestoreState(PresState* aState) {
6028 bool restoredCheckedState = false;
6029
6030 const PresContentData& inputState = aState->contentData();
6031
6032 switch (GetValueMode()) {
6033 case VALUE_MODE_DEFAULT_ON:
6034 if (inputState.type() == PresContentData::TCheckedContentData) {
6035 restoredCheckedState = true;
6036 bool checked = inputState.get_CheckedContentData().checked();
6037 DoSetChecked(checked, true, true);
6038 }
6039 break;
6040 case VALUE_MODE_FILENAME:
6041 if (inputState.type() == PresContentData::TArrayOfFileContentData) {
6042 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
6043 if (window) {
6044 nsTArray<OwningFileOrDirectory> array =
6045 RestoreFileContentData(window, inputState);
6046 SetFilesOrDirectories(array, true);
6047 }
6048 }
6049 break;
6050 case VALUE_MODE_VALUE:
6051 case VALUE_MODE_DEFAULT:
6052 if (GetValueMode() == VALUE_MODE_DEFAULT &&
6053 mType != FormControlType::InputHidden) {
6054 break;
6055 }
6056
6057 if (inputState.type() == PresContentData::TTextContentData) {
6058 // TODO: What should we do if SetValueInternal fails? (The allocation
6059 // may potentially be big, but most likely we've failed to allocate
6060 // before the type change.)
6061 SetValueInternal(inputState.get_TextContentData().value(),
6062 ValueSetterOption::SetValueChanged);
6063 if (inputState.get_TextContentData().lastValueChangeWasInteractive()) {
6064 SetLastValueChangeWasInteractive(true);
6065 }
6066 }
6067 break;
6068 }
6069
6070 if (aState->disabledSet() && !aState->disabled()) {
6071 SetDisabled(false, IgnoreErrors());
6072 }
6073
6074 return restoredCheckedState;
6075 }
6076
AllowDrop()6077 bool HTMLInputElement::AllowDrop() {
6078 // Allow drop on anything other than file inputs.
6079
6080 return mType != FormControlType::InputFile;
6081 }
6082
6083 /*
6084 * Radio group stuff
6085 */
6086
AddedToRadioGroup()6087 void HTMLInputElement::AddedToRadioGroup() {
6088 // If the element is neither in a form nor a document, there is no group so we
6089 // should just stop here.
6090 if (!mForm && (!GetUncomposedDocOrConnectedShadowRoot() ||
6091 IsInNativeAnonymousSubtree())) {
6092 return;
6093 }
6094
6095 // Make sure not to notify if we're still being created
6096 bool notify = mDoneCreating;
6097
6098 //
6099 // If the input element is checked, and we add it to the group, it will
6100 // deselect whatever is currently selected in that group
6101 //
6102 if (mChecked) {
6103 //
6104 // If it is checked, call "RadioSetChecked" to perform the selection/
6105 // deselection ritual. This has the side effect of repainting the
6106 // radio button, but as adding a checked radio button into the group
6107 // should not be that common an occurrence, I think we can live with
6108 // that.
6109 //
6110 RadioSetChecked(notify);
6111 }
6112
6113 //
6114 // For integrity purposes, we have to ensure that "checkedChanged" is
6115 // the same for this new element as for all the others in the group
6116 //
6117 bool checkedChanged = mCheckedChanged;
6118
6119 nsCOMPtr<nsIRadioVisitor> visitor =
6120 new nsRadioGetCheckedChangedVisitor(&checkedChanged, this);
6121 VisitGroup(visitor);
6122
6123 SetCheckedChangedInternal(checkedChanged);
6124
6125 //
6126 // Add the radio to the radio group container.
6127 //
6128 nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
6129 if (container) {
6130 nsAutoString name;
6131 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
6132 container->AddToRadioGroup(name, this);
6133
6134 // We initialize the validity of the element to the validity of the group
6135 // because we assume UpdateValueMissingState() will be called after.
6136 SetValidityState(VALIDITY_STATE_VALUE_MISSING,
6137 container->GetValueMissingState(name));
6138 }
6139 }
6140
WillRemoveFromRadioGroup()6141 void HTMLInputElement::WillRemoveFromRadioGroup() {
6142 nsIRadioGroupContainer* container = GetRadioGroupContainer();
6143 if (!container) {
6144 return;
6145 }
6146
6147 nsAutoString name;
6148 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
6149
6150 // If this button was checked, we need to notify the group that there is no
6151 // longer a selected radio button
6152 if (mChecked) {
6153 container->SetCurrentRadioButton(name, nullptr);
6154
6155 nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
6156 VisitGroup(visitor);
6157 }
6158
6159 // Remove this radio from its group in the container.
6160 // We need to call UpdateValueMissingValidityStateForRadio before to make sure
6161 // the group validity is updated (with this element being ignored).
6162 UpdateValueMissingValidityStateForRadio(true);
6163 container->RemoveFromRadioGroup(name, this);
6164 }
6165
IsHTMLFocusable(bool aWithMouse,bool * aIsFocusable,int32_t * aTabIndex)6166 bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
6167 int32_t* aTabIndex) {
6168 if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(
6169 aWithMouse, aIsFocusable, aTabIndex)) {
6170 return true;
6171 }
6172
6173 if (IsDisabled()) {
6174 *aIsFocusable = false;
6175 return true;
6176 }
6177
6178 if (IsSingleLineTextControl(false) || mType == FormControlType::InputRange) {
6179 *aIsFocusable = true;
6180 return false;
6181 }
6182
6183 const bool defaultFocusable = IsFormControlDefaultFocusable(aWithMouse);
6184 if (CreatesDateTimeWidget()) {
6185 if (aTabIndex) {
6186 // We only want our native anonymous child to be tabable to, not ourself.
6187 *aTabIndex = -1;
6188 }
6189 *aIsFocusable = true;
6190 return true;
6191 }
6192
6193 if (mType == FormControlType::InputHidden) {
6194 if (aTabIndex) {
6195 *aTabIndex = -1;
6196 }
6197 *aIsFocusable = false;
6198 return false;
6199 }
6200
6201 if (!aTabIndex) {
6202 // The other controls are all focusable
6203 *aIsFocusable = defaultFocusable;
6204 return false;
6205 }
6206
6207 if (mType != FormControlType::InputRadio) {
6208 *aIsFocusable = defaultFocusable;
6209 return false;
6210 }
6211
6212 if (mChecked) {
6213 // Selected radio buttons are tabbable
6214 *aIsFocusable = defaultFocusable;
6215 return false;
6216 }
6217
6218 // Current radio button is not selected.
6219 // But make it tabbable if nothing in group is selected.
6220 nsIRadioGroupContainer* container = GetRadioGroupContainer();
6221 if (!container) {
6222 *aIsFocusable = defaultFocusable;
6223 return false;
6224 }
6225
6226 nsAutoString name;
6227 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
6228
6229 if (container->GetCurrentRadioButton(name)) {
6230 *aTabIndex = -1;
6231 }
6232 *aIsFocusable = defaultFocusable;
6233 return false;
6234 }
6235
VisitGroup(nsIRadioVisitor * aVisitor)6236 nsresult HTMLInputElement::VisitGroup(nsIRadioVisitor* aVisitor) {
6237 nsIRadioGroupContainer* container = GetRadioGroupContainer();
6238 if (container) {
6239 nsAutoString name;
6240 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
6241 return container->WalkRadioGroup(name, aVisitor);
6242 }
6243
6244 aVisitor->Visit(this);
6245 return NS_OK;
6246 }
6247
GetValueMode() const6248 HTMLInputElement::ValueModeType HTMLInputElement::GetValueMode() const {
6249 switch (mType) {
6250 case FormControlType::InputHidden:
6251 case FormControlType::InputSubmit:
6252 case FormControlType::InputButton:
6253 case FormControlType::InputReset:
6254 case FormControlType::InputImage:
6255 return VALUE_MODE_DEFAULT;
6256 case FormControlType::InputCheckbox:
6257 case FormControlType::InputRadio:
6258 return VALUE_MODE_DEFAULT_ON;
6259 case FormControlType::InputFile:
6260 return VALUE_MODE_FILENAME;
6261 #ifdef DEBUG
6262 case FormControlType::InputText:
6263 case FormControlType::InputPassword:
6264 case FormControlType::InputSearch:
6265 case FormControlType::InputTel:
6266 case FormControlType::InputEmail:
6267 case FormControlType::InputUrl:
6268 case FormControlType::InputNumber:
6269 case FormControlType::InputRange:
6270 case FormControlType::InputDate:
6271 case FormControlType::InputTime:
6272 case FormControlType::InputColor:
6273 case FormControlType::InputMonth:
6274 case FormControlType::InputWeek:
6275 case FormControlType::InputDatetimeLocal:
6276 return VALUE_MODE_VALUE;
6277 default:
6278 MOZ_ASSERT_UNREACHABLE("Unexpected input type in GetValueMode()");
6279 return VALUE_MODE_VALUE;
6280 #else // DEBUG
6281 default:
6282 return VALUE_MODE_VALUE;
6283 #endif // DEBUG
6284 }
6285 }
6286
IsMutable() const6287 bool HTMLInputElement::IsMutable() const {
6288 return !IsDisabled() && !(DoesReadOnlyApply() &&
6289 HasAttr(kNameSpaceID_None, nsGkAtoms::readonly));
6290 }
6291
DoesRequiredApply() const6292 bool HTMLInputElement::DoesRequiredApply() const {
6293 switch (mType) {
6294 case FormControlType::InputHidden:
6295 case FormControlType::InputButton:
6296 case FormControlType::InputImage:
6297 case FormControlType::InputReset:
6298 case FormControlType::InputSubmit:
6299 case FormControlType::InputRange:
6300 case FormControlType::InputColor:
6301 return false;
6302 #ifdef DEBUG
6303 case FormControlType::InputRadio:
6304 case FormControlType::InputCheckbox:
6305 case FormControlType::InputFile:
6306 case FormControlType::InputText:
6307 case FormControlType::InputPassword:
6308 case FormControlType::InputSearch:
6309 case FormControlType::InputTel:
6310 case FormControlType::InputEmail:
6311 case FormControlType::InputUrl:
6312 case FormControlType::InputNumber:
6313 case FormControlType::InputDate:
6314 case FormControlType::InputTime:
6315 case FormControlType::InputMonth:
6316 case FormControlType::InputWeek:
6317 case FormControlType::InputDatetimeLocal:
6318 return true;
6319 default:
6320 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6321 return true;
6322 #else // DEBUG
6323 default:
6324 return true;
6325 #endif // DEBUG
6326 }
6327 }
6328
PlaceholderApplies() const6329 bool HTMLInputElement::PlaceholderApplies() const {
6330 if (IsDateTimeInputType(mType)) {
6331 return false;
6332 }
6333 return IsSingleLineTextControl(false);
6334 }
6335
DoesMinMaxApply() const6336 bool HTMLInputElement::DoesMinMaxApply() const {
6337 switch (mType) {
6338 case FormControlType::InputNumber:
6339 case FormControlType::InputDate:
6340 case FormControlType::InputTime:
6341 case FormControlType::InputRange:
6342 case FormControlType::InputMonth:
6343 case FormControlType::InputWeek:
6344 case FormControlType::InputDatetimeLocal:
6345 return true;
6346 #ifdef DEBUG
6347 case FormControlType::InputReset:
6348 case FormControlType::InputSubmit:
6349 case FormControlType::InputImage:
6350 case FormControlType::InputButton:
6351 case FormControlType::InputHidden:
6352 case FormControlType::InputRadio:
6353 case FormControlType::InputCheckbox:
6354 case FormControlType::InputFile:
6355 case FormControlType::InputText:
6356 case FormControlType::InputPassword:
6357 case FormControlType::InputSearch:
6358 case FormControlType::InputTel:
6359 case FormControlType::InputEmail:
6360 case FormControlType::InputUrl:
6361 case FormControlType::InputColor:
6362 return false;
6363 default:
6364 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6365 return false;
6366 #else // DEBUG
6367 default:
6368 return false;
6369 #endif // DEBUG
6370 }
6371 }
6372
DoesAutocompleteApply() const6373 bool HTMLInputElement::DoesAutocompleteApply() const {
6374 switch (mType) {
6375 case FormControlType::InputHidden:
6376 case FormControlType::InputText:
6377 case FormControlType::InputSearch:
6378 case FormControlType::InputUrl:
6379 case FormControlType::InputTel:
6380 case FormControlType::InputEmail:
6381 case FormControlType::InputPassword:
6382 case FormControlType::InputDate:
6383 case FormControlType::InputTime:
6384 case FormControlType::InputNumber:
6385 case FormControlType::InputRange:
6386 case FormControlType::InputColor:
6387 case FormControlType::InputMonth:
6388 case FormControlType::InputWeek:
6389 case FormControlType::InputDatetimeLocal:
6390 return true;
6391 #ifdef DEBUG
6392 case FormControlType::InputReset:
6393 case FormControlType::InputSubmit:
6394 case FormControlType::InputImage:
6395 case FormControlType::InputButton:
6396 case FormControlType::InputRadio:
6397 case FormControlType::InputCheckbox:
6398 case FormControlType::InputFile:
6399 return false;
6400 default:
6401 MOZ_ASSERT_UNREACHABLE(
6402 "Unexpected input type in DoesAutocompleteApply()");
6403 return false;
6404 #else // DEBUG
6405 default:
6406 return false;
6407 #endif // DEBUG
6408 }
6409 }
6410
GetStep() const6411 Decimal HTMLInputElement::GetStep() const {
6412 MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
6413
6414 if (!HasAttr(kNameSpaceID_None, nsGkAtoms::step)) {
6415 return GetDefaultStep() * GetStepScaleFactor();
6416 }
6417
6418 nsAutoString stepStr;
6419 GetAttr(kNameSpaceID_None, nsGkAtoms::step, stepStr);
6420
6421 if (stepStr.LowerCaseEqualsLiteral("any")) {
6422 // The element can't suffer from step mismatch if there is no step.
6423 return kStepAny;
6424 }
6425
6426 Decimal step = StringToDecimal(stepStr);
6427 if (!step.isFinite() || step <= Decimal(0)) {
6428 step = GetDefaultStep();
6429 }
6430
6431 // For input type=date, we round the step value to have a rounded day.
6432 if (mType == FormControlType::InputDate ||
6433 mType == FormControlType::InputMonth ||
6434 mType == FormControlType::InputWeek) {
6435 step = std::max(step.round(), Decimal(1));
6436 }
6437
6438 return step * GetStepScaleFactor();
6439 }
6440
6441 // nsIConstraintValidation
6442
SetCustomValidity(const nsAString & aError)6443 void HTMLInputElement::SetCustomValidity(const nsAString& aError) {
6444 nsIConstraintValidation::SetCustomValidity(aError);
6445
6446 UpdateState(true);
6447 }
6448
IsTooLong()6449 bool HTMLInputElement::IsTooLong() {
6450 if (!mValueChanged || !mLastValueChangeWasInteractive) {
6451 return false;
6452 }
6453
6454 return mInputType->IsTooLong();
6455 }
6456
IsTooShort()6457 bool HTMLInputElement::IsTooShort() {
6458 if (!mValueChanged || !mLastValueChangeWasInteractive) {
6459 return false;
6460 }
6461
6462 return mInputType->IsTooShort();
6463 }
6464
IsValueMissing() const6465 bool HTMLInputElement::IsValueMissing() const {
6466 // Should use UpdateValueMissingValidityStateForRadio() for type radio.
6467 MOZ_ASSERT(mType != FormControlType::InputRadio);
6468
6469 return mInputType->IsValueMissing();
6470 }
6471
HasTypeMismatch() const6472 bool HTMLInputElement::HasTypeMismatch() const {
6473 return mInputType->HasTypeMismatch();
6474 }
6475
HasPatternMismatch() const6476 Maybe<bool> HTMLInputElement::HasPatternMismatch() const {
6477 return mInputType->HasPatternMismatch();
6478 }
6479
IsRangeOverflow() const6480 bool HTMLInputElement::IsRangeOverflow() const {
6481 return mInputType->IsRangeOverflow();
6482 }
6483
IsRangeUnderflow() const6484 bool HTMLInputElement::IsRangeUnderflow() const {
6485 return mInputType->IsRangeUnderflow();
6486 }
6487
HasStepMismatch(bool aUseZeroIfValueNaN) const6488 bool HTMLInputElement::HasStepMismatch(bool aUseZeroIfValueNaN) const {
6489 return mInputType->HasStepMismatch(aUseZeroIfValueNaN);
6490 }
6491
HasBadInput() const6492 bool HTMLInputElement::HasBadInput() const { return mInputType->HasBadInput(); }
6493
UpdateTooLongValidityState()6494 void HTMLInputElement::UpdateTooLongValidityState() {
6495 SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
6496 }
6497
UpdateTooShortValidityState()6498 void HTMLInputElement::UpdateTooShortValidityState() {
6499 SetValidityState(VALIDITY_STATE_TOO_SHORT, IsTooShort());
6500 }
6501
UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf)6502 void HTMLInputElement::UpdateValueMissingValidityStateForRadio(
6503 bool aIgnoreSelf) {
6504 MOZ_ASSERT(mType == FormControlType::InputRadio,
6505 "This should be called only for radio input types");
6506
6507 HTMLInputElement* selection = GetSelectedRadioButton();
6508
6509 aIgnoreSelf = aIgnoreSelf || !IsMutable();
6510
6511 // If there is no selection, that might mean the radio is not in a group.
6512 // In that case, we can look for the checked state of the radio.
6513 bool selected = selection || (!aIgnoreSelf && mChecked);
6514 bool required = !aIgnoreSelf && IsRequired();
6515 bool valueMissing = false;
6516
6517 nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
6518
6519 if (!container) {
6520 SetValidityState(VALIDITY_STATE_VALUE_MISSING,
6521 IsMutable() && required && !selected);
6522 return;
6523 }
6524
6525 nsAutoString name;
6526 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
6527
6528 // If the current radio is required and not ignored, we can assume the entire
6529 // group is required.
6530 if (!required) {
6531 required = (aIgnoreSelf && IsRequired())
6532 ? container->GetRequiredRadioCount(name) - 1
6533 : container->GetRequiredRadioCount(name);
6534 }
6535
6536 valueMissing = required && !selected;
6537
6538 if (container->GetValueMissingState(name) != valueMissing) {
6539 container->SetValueMissingState(name, valueMissing);
6540
6541 SetValidityState(VALIDITY_STATE_VALUE_MISSING, valueMissing);
6542
6543 // nsRadioSetValueMissingState will call ContentStateChanged while visiting.
6544 nsAutoScriptBlocker scriptBlocker;
6545 nsCOMPtr<nsIRadioVisitor> visitor =
6546 new nsRadioSetValueMissingState(this, valueMissing);
6547 VisitGroup(visitor);
6548 }
6549 }
6550
UpdateValueMissingValidityState()6551 void HTMLInputElement::UpdateValueMissingValidityState() {
6552 if (mType == FormControlType::InputRadio) {
6553 UpdateValueMissingValidityStateForRadio(false);
6554 return;
6555 }
6556
6557 SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
6558 }
6559
UpdateTypeMismatchValidityState()6560 void HTMLInputElement::UpdateTypeMismatchValidityState() {
6561 SetValidityState(VALIDITY_STATE_TYPE_MISMATCH, HasTypeMismatch());
6562 }
6563
UpdatePatternMismatchValidityState()6564 void HTMLInputElement::UpdatePatternMismatchValidityState() {
6565 Maybe<bool> hasMismatch = HasPatternMismatch();
6566 // Don't update if the JS engine failed to evaluate it.
6567 if (hasMismatch.isSome()) {
6568 SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH, hasMismatch.value());
6569 }
6570 }
6571
UpdateRangeOverflowValidityState()6572 void HTMLInputElement::UpdateRangeOverflowValidityState() {
6573 SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, IsRangeOverflow());
6574 }
6575
UpdateRangeUnderflowValidityState()6576 void HTMLInputElement::UpdateRangeUnderflowValidityState() {
6577 SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW, IsRangeUnderflow());
6578 }
6579
UpdateStepMismatchValidityState()6580 void HTMLInputElement::UpdateStepMismatchValidityState() {
6581 SetValidityState(VALIDITY_STATE_STEP_MISMATCH, HasStepMismatch());
6582 }
6583
UpdateBadInputValidityState()6584 void HTMLInputElement::UpdateBadInputValidityState() {
6585 SetValidityState(VALIDITY_STATE_BAD_INPUT, HasBadInput());
6586 }
6587
UpdateAllValidityStates(bool aNotify)6588 void HTMLInputElement::UpdateAllValidityStates(bool aNotify) {
6589 bool validBefore = IsValid();
6590 UpdateAllValidityStatesButNotElementState();
6591
6592 if (validBefore != IsValid()) {
6593 UpdateState(aNotify);
6594 }
6595 }
6596
UpdateAllValidityStatesButNotElementState()6597 void HTMLInputElement::UpdateAllValidityStatesButNotElementState() {
6598 UpdateTooLongValidityState();
6599 UpdateTooShortValidityState();
6600 UpdateValueMissingValidityState();
6601 UpdateTypeMismatchValidityState();
6602 UpdatePatternMismatchValidityState();
6603 UpdateRangeOverflowValidityState();
6604 UpdateRangeUnderflowValidityState();
6605 UpdateStepMismatchValidityState();
6606 UpdateBadInputValidityState();
6607 }
6608
UpdateBarredFromConstraintValidation()6609 void HTMLInputElement::UpdateBarredFromConstraintValidation() {
6610 SetBarredFromConstraintValidation(
6611 mType == FormControlType::InputHidden ||
6612 mType == FormControlType::InputButton ||
6613 mType == FormControlType::InputReset ||
6614 HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) || IsDisabled());
6615 }
6616
GetValidationMessage(nsAString & aValidationMessage,ValidityStateType aType)6617 nsresult HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
6618 ValidityStateType aType) {
6619 return mInputType->GetValidationMessage(aValidationMessage, aType);
6620 }
6621
IsSingleLineTextControl() const6622 bool HTMLInputElement::IsSingleLineTextControl() const {
6623 return IsSingleLineTextControl(false);
6624 }
6625
IsTextArea() const6626 bool HTMLInputElement::IsTextArea() const { return false; }
6627
IsPasswordTextControl() const6628 bool HTMLInputElement::IsPasswordTextControl() const {
6629 return mType == FormControlType::InputPassword;
6630 }
6631
GetCols()6632 int32_t HTMLInputElement::GetCols() {
6633 // Else we know (assume) it is an input with size attr
6634 const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::size);
6635 if (attr && attr->Type() == nsAttrValue::eInteger) {
6636 int32_t cols = attr->GetIntegerValue();
6637 if (cols > 0) {
6638 return cols;
6639 }
6640 }
6641
6642 return DEFAULT_COLS;
6643 }
6644
GetWrapCols()6645 int32_t HTMLInputElement::GetWrapCols() {
6646 return 0; // only textarea's can have wrap cols
6647 }
6648
GetRows()6649 int32_t HTMLInputElement::GetRows() { return DEFAULT_ROWS; }
6650
GetDefaultValueFromContent(nsAString & aValue)6651 void HTMLInputElement::GetDefaultValueFromContent(nsAString& aValue) {
6652 TextControlState* state = GetEditorState();
6653 if (state) {
6654 GetDefaultValue(aValue);
6655 // This is called by the frame to show the value.
6656 // We have to sanitize it when needed.
6657 if (mDoneCreating) {
6658 SanitizeValue(aValue);
6659 }
6660 }
6661 }
6662
ValueChanged() const6663 bool HTMLInputElement::ValueChanged() const { return mValueChanged; }
6664
GetTextEditorValue(nsAString & aValue,bool aIgnoreWrap) const6665 void HTMLInputElement::GetTextEditorValue(nsAString& aValue,
6666 bool aIgnoreWrap) const {
6667 TextControlState* state = GetEditorState();
6668 if (state) {
6669 state->GetValue(aValue, aIgnoreWrap);
6670 }
6671 }
6672
TextEditorValueEquals(const nsAString & aValue) const6673 bool HTMLInputElement::TextEditorValueEquals(const nsAString& aValue) const {
6674 TextControlState* state = GetEditorState();
6675 return state ? state->ValueEquals(aValue) : aValue.IsEmpty();
6676 }
6677
InitializeKeyboardEventListeners()6678 void HTMLInputElement::InitializeKeyboardEventListeners() {
6679 TextControlState* state = GetEditorState();
6680 if (state) {
6681 state->InitializeKeyboardEventListeners();
6682 }
6683 }
6684
OnValueChanged(ValueChangeKind aKind)6685 void HTMLInputElement::OnValueChanged(ValueChangeKind aKind) {
6686 if (aKind != ValueChangeKind::Internal) {
6687 mLastValueChangeWasInteractive = aKind == ValueChangeKind::UserInteraction;
6688 }
6689
6690 UpdateAllValidityStates(true);
6691
6692 if (HasDirAuto()) {
6693 SetDirectionFromValue(true);
6694 }
6695
6696 // :placeholder-shown pseudo-class may change when the value changes.
6697 // However, we don't want to waste cycles if the state doesn't apply.
6698 if (PlaceholderApplies() && HasAttr(nsGkAtoms::placeholder)) {
6699 UpdateState(true);
6700 }
6701
6702 // Update clear button state on search inputs
6703 if (mType == FormControlType::InputSearch) {
6704 if (nsSearchControlFrame* searchControlFrame =
6705 do_QueryFrame(GetPrimaryFrame())) {
6706 searchControlFrame->UpdateClearButtonState();
6707 }
6708 }
6709 }
6710
HasCachedSelection()6711 bool HTMLInputElement::HasCachedSelection() {
6712 TextControlState* state = GetEditorState();
6713 if (!state) {
6714 return false;
6715 }
6716 return state->IsSelectionCached() && state->HasNeverInitializedBefore() &&
6717 state->GetSelectionProperties().GetStart() !=
6718 state->GetSelectionProperties().GetEnd();
6719 }
6720
FieldSetDisabledChanged(bool aNotify)6721 void HTMLInputElement::FieldSetDisabledChanged(bool aNotify) {
6722 // This *has* to be called *before* UpdateBarredFromConstraintValidation and
6723 // UpdateValueMissingValidityState because these two functions depend on our
6724 // disabled state.
6725 nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
6726
6727 UpdateValueMissingValidityState();
6728 UpdateBarredFromConstraintValidation();
6729 UpdateState(aNotify);
6730 }
6731
SetFilePickerFiltersFromAccept(nsIFilePicker * filePicker)6732 void HTMLInputElement::SetFilePickerFiltersFromAccept(
6733 nsIFilePicker* filePicker) {
6734 // We always add |filterAll|
6735 filePicker->AppendFilters(nsIFilePicker::filterAll);
6736
6737 NS_ASSERTION(HasAttr(kNameSpaceID_None, nsGkAtoms::accept),
6738 "You should not call SetFilePickerFiltersFromAccept if the"
6739 " element has no accept attribute!");
6740
6741 // Services to retrieve image/*, audio/*, video/* filters
6742 nsCOMPtr<nsIStringBundleService> stringService =
6743 mozilla::components::StringBundle::Service();
6744 if (!stringService) {
6745 return;
6746 }
6747 nsCOMPtr<nsIStringBundle> filterBundle;
6748 if (NS_FAILED(stringService->CreateBundle(
6749 "chrome://global/content/filepicker.properties",
6750 getter_AddRefs(filterBundle)))) {
6751 return;
6752 }
6753
6754 // Service to retrieve mime type information for mime types filters
6755 nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
6756 if (!mimeService) {
6757 return;
6758 }
6759
6760 nsAutoString accept;
6761 GetAttr(kNameSpaceID_None, nsGkAtoms::accept, accept);
6762
6763 HTMLSplitOnSpacesTokenizer tokenizer(accept, ',');
6764
6765 nsTArray<nsFilePickerFilter> filters;
6766 nsString allExtensionsList;
6767
6768 bool allMimeTypeFiltersAreValid = true;
6769 bool atLeastOneFileExtensionFilter = false;
6770
6771 // Retrieve all filters
6772 while (tokenizer.hasMoreTokens()) {
6773 const nsDependentSubstring& token = tokenizer.nextToken();
6774
6775 if (token.IsEmpty()) {
6776 continue;
6777 }
6778
6779 int32_t filterMask = 0;
6780 nsString filterName;
6781 nsString extensionListStr;
6782
6783 // First, check for image/audio/video filters...
6784 if (token.EqualsLiteral("image/*")) {
6785 filterMask = nsIFilePicker::filterImages;
6786 filterBundle->GetStringFromName("imageFilter", extensionListStr);
6787 } else if (token.EqualsLiteral("audio/*")) {
6788 filterMask = nsIFilePicker::filterAudio;
6789 filterBundle->GetStringFromName("audioFilter", extensionListStr);
6790 } else if (token.EqualsLiteral("video/*")) {
6791 filterMask = nsIFilePicker::filterVideo;
6792 filterBundle->GetStringFromName("videoFilter", extensionListStr);
6793 } else if (token.First() == '.') {
6794 if (token.Contains(';') || token.Contains('*')) {
6795 // Ignore this filter as it contains reserved characters
6796 continue;
6797 }
6798 extensionListStr = u"*"_ns + token;
6799 filterName = extensionListStr;
6800 atLeastOneFileExtensionFilter = true;
6801 } else {
6802 //... if no image/audio/video filter is found, check mime types filters
6803 nsCOMPtr<nsIMIMEInfo> mimeInfo;
6804 if (NS_FAILED(
6805 mimeService->GetFromTypeAndExtension(NS_ConvertUTF16toUTF8(token),
6806 ""_ns, // No extension
6807 getter_AddRefs(mimeInfo))) ||
6808 !mimeInfo) {
6809 allMimeTypeFiltersAreValid = false;
6810 continue;
6811 }
6812
6813 // Get a name for the filter: first try the description, then the mime
6814 // type name if there is no description
6815 mimeInfo->GetDescription(filterName);
6816 if (filterName.IsEmpty()) {
6817 nsCString mimeTypeName;
6818 mimeInfo->GetType(mimeTypeName);
6819 CopyUTF8toUTF16(mimeTypeName, filterName);
6820 }
6821
6822 // Get extension list
6823 nsCOMPtr<nsIUTF8StringEnumerator> extensions;
6824 mimeInfo->GetFileExtensions(getter_AddRefs(extensions));
6825
6826 bool hasMore;
6827 while (NS_SUCCEEDED(extensions->HasMore(&hasMore)) && hasMore) {
6828 nsCString extension;
6829 if (NS_FAILED(extensions->GetNext(extension))) {
6830 continue;
6831 }
6832 if (!extensionListStr.IsEmpty()) {
6833 extensionListStr.AppendLiteral("; ");
6834 }
6835 extensionListStr += u"*."_ns + NS_ConvertUTF8toUTF16(extension);
6836 }
6837 }
6838
6839 if (!filterMask && (extensionListStr.IsEmpty() || filterName.IsEmpty())) {
6840 // No valid filter found
6841 allMimeTypeFiltersAreValid = false;
6842 continue;
6843 }
6844
6845 // At this point we're sure the token represents a valid filter, so pass
6846 // it directly as a raw filter.
6847 filePicker->AppendRawFilter(token);
6848
6849 // If we arrived here, that means we have a valid filter: let's create it
6850 // and add it to our list, if no similar filter is already present
6851 nsFilePickerFilter filter;
6852 if (filterMask) {
6853 filter = nsFilePickerFilter(filterMask);
6854 } else {
6855 filter = nsFilePickerFilter(filterName, extensionListStr);
6856 }
6857
6858 if (!filters.Contains(filter)) {
6859 if (!allExtensionsList.IsEmpty()) {
6860 allExtensionsList.AppendLiteral("; ");
6861 }
6862 allExtensionsList += extensionListStr;
6863 filters.AppendElement(filter);
6864 }
6865 }
6866
6867 // Remove similar filters
6868 // Iterate over a copy, as we might modify the original filters list
6869 const nsTArray<nsFilePickerFilter> filtersCopy = filters.Clone();
6870 for (uint32_t i = 0; i < filtersCopy.Length(); ++i) {
6871 const nsFilePickerFilter& filterToCheck = filtersCopy[i];
6872 if (filterToCheck.mFilterMask) {
6873 continue;
6874 }
6875 for (uint32_t j = 0; j < filtersCopy.Length(); ++j) {
6876 if (i == j) {
6877 continue;
6878 }
6879 // Check if this filter's extension list is a substring of the other one.
6880 // e.g. if filters are "*.jpeg" and "*.jpeg; *.jpg" the first one should
6881 // be removed.
6882 // Add an extra "; " to be sure the check will work and avoid cases like
6883 // "*.xls" being a subtring of "*.xslx" while those are two differents
6884 // filters and none should be removed.
6885 if (FindInReadable(filterToCheck.mFilter + u";"_ns,
6886 filtersCopy[j].mFilter + u";"_ns)) {
6887 // We already have a similar, less restrictive filter (i.e.
6888 // filterToCheck extensionList is just a subset of another filter
6889 // extension list): remove this one
6890 filters.RemoveElement(filterToCheck);
6891 }
6892 }
6893 }
6894
6895 // Add "All Supported Types" filter
6896 if (filters.Length() > 1) {
6897 nsAutoString title;
6898 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
6899 "AllSupportedTypes", title);
6900 filePicker->AppendFilter(title, allExtensionsList);
6901 }
6902
6903 // Add each filter
6904 for (uint32_t i = 0; i < filters.Length(); ++i) {
6905 const nsFilePickerFilter& filter = filters[i];
6906 if (filter.mFilterMask) {
6907 filePicker->AppendFilters(filter.mFilterMask);
6908 } else {
6909 filePicker->AppendFilter(filter.mTitle, filter.mFilter);
6910 }
6911 }
6912
6913 if (filters.Length() >= 1 &&
6914 (allMimeTypeFiltersAreValid || atLeastOneFileExtensionFilter)) {
6915 // |filterAll| will always use index=0 so we need to set index=1 as the
6916 // current filter.
6917 filePicker->SetFilterIndex(1);
6918 }
6919 }
6920
GetStepScaleFactor() const6921 Decimal HTMLInputElement::GetStepScaleFactor() const {
6922 MOZ_ASSERT(DoesStepApply());
6923
6924 switch (mType) {
6925 case FormControlType::InputDate:
6926 return kStepScaleFactorDate;
6927 case FormControlType::InputNumber:
6928 case FormControlType::InputRange:
6929 return kStepScaleFactorNumberRange;
6930 case FormControlType::InputTime:
6931 case FormControlType::InputDatetimeLocal:
6932 return kStepScaleFactorTime;
6933 case FormControlType::InputMonth:
6934 return kStepScaleFactorMonth;
6935 case FormControlType::InputWeek:
6936 return kStepScaleFactorWeek;
6937 default:
6938 MOZ_ASSERT(false, "Unrecognized input type");
6939 return Decimal::nan();
6940 }
6941 }
6942
GetDefaultStep() const6943 Decimal HTMLInputElement::GetDefaultStep() const {
6944 MOZ_ASSERT(DoesStepApply());
6945
6946 switch (mType) {
6947 case FormControlType::InputDate:
6948 case FormControlType::InputMonth:
6949 case FormControlType::InputWeek:
6950 case FormControlType::InputNumber:
6951 case FormControlType::InputRange:
6952 return kDefaultStep;
6953 case FormControlType::InputTime:
6954 case FormControlType::InputDatetimeLocal:
6955 return kDefaultStepTime;
6956 default:
6957 MOZ_ASSERT(false, "Unrecognized input type");
6958 return Decimal::nan();
6959 }
6960 }
6961
UpdateValidityUIBits(bool aIsFocused)6962 void HTMLInputElement::UpdateValidityUIBits(bool aIsFocused) {
6963 if (aIsFocused) {
6964 // If the invalid UI is shown, we should show it while focusing (and
6965 // update). Otherwise, we should not.
6966 mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
6967
6968 // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
6969 // UI while typing.
6970 mCanShowValidUI = ShouldShowValidityUI();
6971 } else {
6972 mCanShowInvalidUI = true;
6973 mCanShowValidUI = true;
6974 }
6975 }
6976
UpdateHasRange()6977 void HTMLInputElement::UpdateHasRange() {
6978 /*
6979 * There is a range if min/max applies for the type and if the element
6980 * currently have a valid min or max.
6981 */
6982
6983 mHasRange = false;
6984
6985 if (!DoesMinMaxApply()) {
6986 return;
6987 }
6988
6989 Decimal minimum = GetMinimum();
6990 if (!minimum.isNaN()) {
6991 mHasRange = true;
6992 return;
6993 }
6994
6995 Decimal maximum = GetMaximum();
6996 if (!maximum.isNaN()) {
6997 mHasRange = true;
6998 return;
6999 }
7000 }
7001
PickerClosed()7002 void HTMLInputElement::PickerClosed() { mPickerRunning = false; }
7003
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)7004 JSObject* HTMLInputElement::WrapNode(JSContext* aCx,
7005 JS::Handle<JSObject*> aGivenProto) {
7006 return HTMLInputElement_Binding::Wrap(aCx, this, aGivenProto);
7007 }
7008
GetOrCreateGetFilesHelper(bool aRecursiveFlag,ErrorResult & aRv)7009 GetFilesHelper* HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag,
7010 ErrorResult& aRv) {
7011 MOZ_ASSERT(mFileData);
7012
7013 if (aRecursiveFlag) {
7014 if (!mFileData->mGetFilesRecursiveHelper) {
7015 mFileData->mGetFilesRecursiveHelper = GetFilesHelper::Create(
7016 GetFilesOrDirectoriesInternal(), aRecursiveFlag, aRv);
7017 if (NS_WARN_IF(aRv.Failed())) {
7018 return nullptr;
7019 }
7020 }
7021
7022 return mFileData->mGetFilesRecursiveHelper;
7023 }
7024
7025 if (!mFileData->mGetFilesNonRecursiveHelper) {
7026 mFileData->mGetFilesNonRecursiveHelper = GetFilesHelper::Create(
7027 GetFilesOrDirectoriesInternal(), aRecursiveFlag, aRv);
7028 if (NS_WARN_IF(aRv.Failed())) {
7029 return nullptr;
7030 }
7031 }
7032
7033 return mFileData->mGetFilesNonRecursiveHelper;
7034 }
7035
UpdateEntries(const nsTArray<OwningFileOrDirectory> & aFilesOrDirectories)7036 void HTMLInputElement::UpdateEntries(
7037 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories) {
7038 MOZ_ASSERT(mFileData && mFileData->mEntries.IsEmpty());
7039
7040 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
7041 MOZ_ASSERT(global);
7042
7043 RefPtr<FileSystem> fs = FileSystem::Create(global);
7044 if (NS_WARN_IF(!fs)) {
7045 return;
7046 }
7047
7048 Sequence<RefPtr<FileSystemEntry>> entries;
7049 for (uint32_t i = 0; i < aFilesOrDirectories.Length(); ++i) {
7050 RefPtr<FileSystemEntry> entry =
7051 FileSystemEntry::Create(global, aFilesOrDirectories[i], fs);
7052 MOZ_ASSERT(entry);
7053
7054 if (!entries.AppendElement(entry, fallible)) {
7055 return;
7056 }
7057 }
7058
7059 // The root fileSystem is a DirectoryEntry object that contains only the
7060 // dropped fileEntry and directoryEntry objects.
7061 fs->CreateRoot(entries);
7062
7063 mFileData->mEntries = std::move(entries);
7064 }
7065
GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>> & aSequence)7066 void HTMLInputElement::GetWebkitEntries(
7067 nsTArray<RefPtr<FileSystemEntry>>& aSequence) {
7068 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
7069 return;
7070 }
7071
7072 Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED, true);
7073 aSequence.AppendElements(mFileData->mEntries);
7074 }
7075
GetLabels()7076 already_AddRefed<nsINodeList> HTMLInputElement::GetLabels() {
7077 if (!IsLabelable()) {
7078 return nullptr;
7079 }
7080
7081 return nsGenericHTMLElement::Labels();
7082 }
7083
MaybeFireInputPasswordRemoved()7084 void HTMLInputElement::MaybeFireInputPasswordRemoved() {
7085 // We want this event to be fired only when the password field is removed
7086 // from the DOM tree, not when it is released (ex, tab is closed). So don't
7087 // fire an event when the password input field doesn't have a docshell.
7088 Document* doc = GetComposedDoc();
7089 nsIDocShell* container = doc ? doc->GetDocShell() : nullptr;
7090 if (!container) {
7091 return;
7092 }
7093
7094 // Right now, only the password manager listens to the event and only listen
7095 // to it under certain circumstances. So don't fire this event unless
7096 // necessary.
7097 if (!doc->ShouldNotifyFormOrPasswordRemoved()) {
7098 return;
7099 }
7100
7101 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7102 new AsyncEventDispatcher(this, u"DOMInputPasswordRemoved"_ns,
7103 CanBubble::eNo, ChromeOnlyDispatch::eYes);
7104 asyncDispatcher->RunDOMEventWhenSafe();
7105 }
7106
7107 } // namespace mozilla::dom
7108
7109 #undef NS_ORIGINAL_CHECKED_VALUE
7110