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/DebugOnly.h"
12 #include "mozilla/dom/Date.h"
13 #include "mozilla/dom/Directory.h"
14 #include "mozilla/dom/HTMLFormSubmission.h"
15 #include "mozilla/dom/FileSystemUtils.h"
16 #include "mozilla/dom/GetFilesHelper.h"
17 #include "nsAttrValueInlines.h"
18 #include "nsCRTGlue.h"
19
20 #include "nsIDOMHTMLInputElement.h"
21 #include "nsITextControlElement.h"
22 #include "nsIDOMNSEditableElement.h"
23 #include "nsIRadioVisitor.h"
24 #include "nsIPhonetic.h"
25
26 #include "HTMLFormSubmissionConstants.h"
27 #include "mozilla/Telemetry.h"
28 #include "nsIControllers.h"
29 #include "nsIStringBundle.h"
30 #include "nsFocusManager.h"
31 #include "nsColorControlFrame.h"
32 #include "nsNumberControlFrame.h"
33 #include "nsPIDOMWindow.h"
34 #include "nsRepeatService.h"
35 #include "nsContentCID.h"
36 #include "nsIComponentManager.h"
37 #include "nsIDOMHTMLFormElement.h"
38 #include "mozilla/dom/ProgressEvent.h"
39 #include "nsGkAtoms.h"
40 #include "nsStyleConsts.h"
41 #include "nsPresContext.h"
42 #include "nsMappedAttributes.h"
43 #include "nsIFormControl.h"
44 #include "nsIDocument.h"
45 #include "nsIPresShell.h"
46 #include "nsIFormControlFrame.h"
47 #include "nsITextControlFrame.h"
48 #include "nsIFrame.h"
49 #include "nsRangeFrame.h"
50 #include "nsIServiceManager.h"
51 #include "nsError.h"
52 #include "nsIEditor.h"
53 #include "nsIIOService.h"
54 #include "nsDocument.h"
55 #include "nsAttrValueOrString.h"
56 #include "nsDateTimeControlFrame.h"
57
58 #include "nsPresState.h"
59 #include "nsIDOMEvent.h"
60 #include "nsIDOMNodeList.h"
61 #include "nsIDOMHTMLCollection.h"
62 #include "nsLinebreakConverter.h" //to strip out carriage returns
63 #include "nsReadableUtils.h"
64 #include "nsUnicharUtils.h"
65 #include "nsLayoutUtils.h"
66 #include "nsVariant.h"
67
68 #include "nsIDOMMutationEvent.h"
69 #include "mozilla/ContentEvents.h"
70 #include "mozilla/EventDispatcher.h"
71 #include "mozilla/EventStates.h"
72 #include "mozilla/InternalMutationEvent.h"
73 #include "mozilla/TextEvents.h"
74 #include "mozilla/TouchEvents.h"
75
76 #include "nsRuleData.h"
77 #include <algorithm>
78
79 // input type=radio
80 #include "nsIRadioGroupContainer.h"
81
82 // input type=file
83 #include "mozilla/dom/FileSystemEntry.h"
84 #include "mozilla/dom/FileSystem.h"
85 #include "mozilla/dom/File.h"
86 #include "mozilla/dom/FileList.h"
87 #include "nsIFile.h"
88 #include "nsNetCID.h"
89 #include "nsNetUtil.h"
90 #include "nsDirectoryServiceDefs.h"
91 #include "nsIContentPrefService.h"
92 #include "nsIMIMEService.h"
93 #include "nsIObserverService.h"
94 #include "nsIPopupWindowManager.h"
95 #include "nsGlobalWindow.h"
96
97 // input type=image
98 #include "nsImageLoadingContent.h"
99 #include "imgRequestProxy.h"
100
101 #include "mozAutoDocUpdate.h"
102 #include "nsContentCreatorFunctions.h"
103 #include "nsContentUtils.h"
104 #include "mozilla/dom/DirectionalityUtils.h"
105 #include "nsRadioVisitor.h"
106 #include "nsTextEditorState.h"
107
108 #include "mozilla/LookAndFeel.h"
109 #include "mozilla/Preferences.h"
110 #include "mozilla/MathAlgorithms.h"
111
112 #include "nsIIDNService.h"
113
114 #include <limits>
115
116 #include "nsIColorPicker.h"
117 #include "nsIDatePicker.h"
118 #include "nsIStringEnumerator.h"
119 #include "HTMLSplitOnSpacesTokenizer.h"
120 #include "nsIController.h"
121 #include "nsIMIMEInfo.h"
122 #include "nsFrameSelection.h"
123
124 #include "nsIConsoleService.h"
125
126 // input type=date
127 #include "js/Date.h"
128
129 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
130
131 // XXX align=left, hspace, vspace, border? other nav4 attrs
132
133 static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID);
134
135 // This must come outside of any namespace, or else it won't overload with the
136 // double based version in nsMathUtils.h
137 inline mozilla::Decimal
NS_floorModulo(mozilla::Decimal x,mozilla::Decimal y)138 NS_floorModulo(mozilla::Decimal x, mozilla::Decimal y)
139 {
140 return (x - y * (x / y).floor());
141 }
142
143 namespace mozilla {
144 namespace dom {
145
146 // First bits are needed for the control type.
147 #define NS_OUTER_ACTIVATE_EVENT (1 << 9)
148 #define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
149 #define NS_NO_CONTENT_DISPATCH (1 << 11)
150 #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
151 #define NS_CONTROL_TYPE(bits) ((bits) & ~( \
152 NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | NS_NO_CONTENT_DISPATCH | \
153 NS_ORIGINAL_INDETERMINATE_VALUE))
154
155 // whether textfields should be selected once focused:
156 // -1: no, 1: yes, 0: uninitialized
157 static int32_t gSelectTextFieldOnFocus;
158 UploadLastDir* HTMLInputElement::gUploadLastDir;
159
160 static const nsAttrValue::EnumTable kInputTypeTable[] = {
161 { "button", NS_FORM_INPUT_BUTTON },
162 { "checkbox", NS_FORM_INPUT_CHECKBOX },
163 { "color", NS_FORM_INPUT_COLOR },
164 { "date", NS_FORM_INPUT_DATE },
165 { "datetime-local", NS_FORM_INPUT_DATETIME_LOCAL },
166 { "email", NS_FORM_INPUT_EMAIL },
167 { "file", NS_FORM_INPUT_FILE },
168 { "hidden", NS_FORM_INPUT_HIDDEN },
169 { "reset", NS_FORM_INPUT_RESET },
170 { "image", NS_FORM_INPUT_IMAGE },
171 { "month", NS_FORM_INPUT_MONTH },
172 { "number", NS_FORM_INPUT_NUMBER },
173 { "password", NS_FORM_INPUT_PASSWORD },
174 { "radio", NS_FORM_INPUT_RADIO },
175 { "range", NS_FORM_INPUT_RANGE },
176 { "search", NS_FORM_INPUT_SEARCH },
177 { "submit", NS_FORM_INPUT_SUBMIT },
178 { "tel", NS_FORM_INPUT_TEL },
179 { "text", NS_FORM_INPUT_TEXT },
180 { "time", NS_FORM_INPUT_TIME },
181 { "url", NS_FORM_INPUT_URL },
182 { "week", NS_FORM_INPUT_WEEK },
183 { nullptr, 0 }
184 };
185
186 // Default type is 'text'.
187 static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[18];
188
189 static const uint8_t NS_INPUT_INPUTMODE_AUTO = 0;
190 static const uint8_t NS_INPUT_INPUTMODE_NUMERIC = 1;
191 static const uint8_t NS_INPUT_INPUTMODE_DIGIT = 2;
192 static const uint8_t NS_INPUT_INPUTMODE_UPPERCASE = 3;
193 static const uint8_t NS_INPUT_INPUTMODE_LOWERCASE = 4;
194 static const uint8_t NS_INPUT_INPUTMODE_TITLECASE = 5;
195 static const uint8_t NS_INPUT_INPUTMODE_AUTOCAPITALIZED = 6;
196
197 static const nsAttrValue::EnumTable kInputInputmodeTable[] = {
198 { "auto", NS_INPUT_INPUTMODE_AUTO },
199 { "numeric", NS_INPUT_INPUTMODE_NUMERIC },
200 { "digit", NS_INPUT_INPUTMODE_DIGIT },
201 { "uppercase", NS_INPUT_INPUTMODE_UPPERCASE },
202 { "lowercase", NS_INPUT_INPUTMODE_LOWERCASE },
203 { "titlecase", NS_INPUT_INPUTMODE_TITLECASE },
204 { "autocapitalized", NS_INPUT_INPUTMODE_AUTOCAPITALIZED },
205 { nullptr, 0 }
206 };
207
208 // Default inputmode value is "auto".
209 static const nsAttrValue::EnumTable* kInputDefaultInputmode = &kInputInputmodeTable[0];
210
211 const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000);
212 const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
213 const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
214 const Decimal HTMLInputElement::kStepScaleFactorMonth = Decimal(1);
215 const Decimal HTMLInputElement::kStepScaleFactorWeek = Decimal(7 * 86400000);
216 const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0);
217 const Decimal HTMLInputElement::kDefaultStepBaseWeek = Decimal(-259200000);
218 const Decimal HTMLInputElement::kDefaultStep = Decimal(1);
219 const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60);
220 const Decimal HTMLInputElement::kStepAny = Decimal(0);
221
222 const double HTMLInputElement::kMinimumYear = 1;
223 const double HTMLInputElement::kMaximumYear = 275760;
224 const double HTMLInputElement::kMaximumWeekInMaximumYear = 37;
225 const double HTMLInputElement::kMaximumDayInMaximumYear = 13;
226 const double HTMLInputElement::kMaximumMonthInMaximumYear = 9;
227 const double HTMLInputElement::kMaximumWeekInYear = 53;
228 const double HTMLInputElement::kMsPerDay = 24 * 60 * 60 * 1000;
229
230 #define NS_INPUT_ELEMENT_STATE_IID \
231 { /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */ \
232 0xdc3b3d14, \
233 0x23e2, \
234 0x4479, \
235 {0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \
236 }
237
238 #define PROGRESS_STR "progress"
239 static const uint32_t kProgressEventInterval = 50; // ms
240
241 // An helper class for the dispatching of the 'change' event.
242 // This class is used when the FilePicker finished its task (or when files and
243 // directories are set by some chrome/test only method).
244 // The task of this class is to postpone the dispatching of 'change' and 'input'
245 // events at the end of the exploration of the directories.
246 class DispatchChangeEventCallback final : public GetFilesCallback
247 {
248 public:
DispatchChangeEventCallback(HTMLInputElement * aInputElement)249 explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement)
250 : mInputElement(aInputElement)
251 {
252 MOZ_ASSERT(aInputElement);
253 }
254
255 virtual void
Callback(nsresult aStatus,const Sequence<RefPtr<File>> & aFiles)256 Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override
257 {
258 nsTArray<OwningFileOrDirectory> array;
259 for (uint32_t i = 0; i < aFiles.Length(); ++i) {
260 OwningFileOrDirectory* element = array.AppendElement();
261 element->SetAsFile() = aFiles[i];
262 }
263
264 mInputElement->SetFilesOrDirectories(array, true);
265 Unused << NS_WARN_IF(NS_FAILED(DispatchEvents()));
266 }
267
268 nsresult
DispatchEvents()269 DispatchEvents()
270 {
271 nsresult rv = NS_OK;
272 rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
273 static_cast<nsIDOMHTMLInputElement*>(mInputElement.get()),
274 NS_LITERAL_STRING("input"), true,
275 false);
276 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
277
278 rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
279 static_cast<nsIDOMHTMLInputElement*>(mInputElement.get()),
280 NS_LITERAL_STRING("change"), true,
281 false);
282
283 return rv;
284 }
285
286 private:
287 RefPtr<HTMLInputElement> mInputElement;
288 };
289
290 class HTMLInputElementState final : public nsISupports
291 {
292 public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID)293 NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID)
294 NS_DECL_ISUPPORTS
295
296 bool IsCheckedSet()
297 {
298 return mCheckedSet;
299 }
300
GetChecked()301 bool GetChecked()
302 {
303 return mChecked;
304 }
305
SetChecked(bool aChecked)306 void SetChecked(bool aChecked)
307 {
308 mChecked = aChecked;
309 mCheckedSet = true;
310 }
311
GetValue()312 const nsString& GetValue()
313 {
314 return mValue;
315 }
316
SetValue(const nsAString & aValue)317 void SetValue(const nsAString& aValue)
318 {
319 mValue = aValue;
320 }
321
322 void
GetFilesOrDirectories(nsPIDOMWindowInner * aWindow,nsTArray<OwningFileOrDirectory> & aResult) const323 GetFilesOrDirectories(nsPIDOMWindowInner* aWindow,
324 nsTArray<OwningFileOrDirectory>& aResult) const
325 {
326 for (uint32_t i = 0; i < mBlobImplsOrDirectoryPaths.Length(); ++i) {
327 if (mBlobImplsOrDirectoryPaths[i].mType == BlobImplOrDirectoryPath::eBlobImpl) {
328 RefPtr<File> file =
329 File::Create(aWindow,
330 mBlobImplsOrDirectoryPaths[i].mBlobImpl);
331 MOZ_ASSERT(file);
332
333 OwningFileOrDirectory* element = aResult.AppendElement();
334 element->SetAsFile() = file;
335 } else {
336 MOZ_ASSERT(mBlobImplsOrDirectoryPaths[i].mType == BlobImplOrDirectoryPath::eDirectoryPath);
337
338 nsCOMPtr<nsIFile> file;
339 nsresult rv =
340 NS_NewLocalFile(mBlobImplsOrDirectoryPaths[i].mDirectoryPath,
341 true, getter_AddRefs(file));
342 if (NS_WARN_IF(NS_FAILED(rv))) {
343 continue;
344 }
345
346 RefPtr<Directory> directory = Directory::Create(aWindow, file);
347 MOZ_ASSERT(directory);
348
349 OwningFileOrDirectory* element = aResult.AppendElement();
350 element->SetAsDirectory() = directory;
351 }
352 }
353 }
354
SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory> & aArray)355 void SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aArray)
356 {
357 mBlobImplsOrDirectoryPaths.Clear();
358 for (uint32_t i = 0; i < aArray.Length(); ++i) {
359 if (aArray[i].IsFile()) {
360 BlobImplOrDirectoryPath* data = mBlobImplsOrDirectoryPaths.AppendElement();
361
362 data->mBlobImpl = aArray[i].GetAsFile()->Impl();
363 data->mType = BlobImplOrDirectoryPath::eBlobImpl;
364 } else {
365 MOZ_ASSERT(aArray[i].IsDirectory());
366 nsAutoString fullPath;
367 nsresult rv = aArray[i].GetAsDirectory()->GetFullRealPath(fullPath);
368 if (NS_WARN_IF(NS_FAILED(rv))) {
369 continue;
370 }
371
372 BlobImplOrDirectoryPath* data =
373 mBlobImplsOrDirectoryPaths.AppendElement();
374
375 data->mDirectoryPath = fullPath;
376 data->mType = BlobImplOrDirectoryPath::eDirectoryPath;
377 }
378 }
379 }
380
HTMLInputElementState()381 HTMLInputElementState()
382 : mValue()
383 , mChecked(false)
384 , mCheckedSet(false)
385 {}
386
387 protected:
~HTMLInputElementState()388 ~HTMLInputElementState() {}
389
390 nsString mValue;
391
392 struct BlobImplOrDirectoryPath
393 {
394 RefPtr<BlobImpl> mBlobImpl;
395 nsString mDirectoryPath;
396
397 enum {
398 eBlobImpl,
399 eDirectoryPath
400 } mType;
401 };
402
403 nsTArray<BlobImplOrDirectoryPath> mBlobImplsOrDirectoryPaths;
404
405 bool mChecked;
406 bool mCheckedSet;
407 };
408
NS_DEFINE_STATIC_IID_ACCESSOR(HTMLInputElementState,NS_INPUT_ELEMENT_STATE_IID)409 NS_DEFINE_STATIC_IID_ACCESSOR(HTMLInputElementState, NS_INPUT_ELEMENT_STATE_IID)
410
411 NS_IMPL_ISUPPORTS(HTMLInputElementState, HTMLInputElementState)
412
413 HTMLInputElement::nsFilePickerShownCallback::nsFilePickerShownCallback(
414 HTMLInputElement* aInput, nsIFilePicker* aFilePicker)
415 : mFilePicker(aFilePicker)
416 , mInput(aInput)
417 {
418 }
419
NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback,nsIContentPrefCallback2)420 NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback, nsIContentPrefCallback2)
421
422 NS_IMETHODIMP
423 UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason)
424 {
425 nsCOMPtr<nsIFile> localFile;
426 nsAutoString prefStr;
427
428 if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || !mResult) {
429 prefStr = Preferences::GetString("dom.input.fallbackUploadDir");
430 if (prefStr.IsEmpty()) {
431 // If no custom directory was set through the pref, default to
432 // "desktop" directory for each platform.
433 NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(localFile));
434 }
435 }
436
437 if (!localFile) {
438 if (prefStr.IsEmpty() && mResult) {
439 nsCOMPtr<nsIVariant> pref;
440 mResult->GetValue(getter_AddRefs(pref));
441 pref->GetAsAString(prefStr);
442 }
443 localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
444 localFile->InitWithPath(prefStr);
445 }
446
447 mFilePicker->SetDisplayDirectory(localFile);
448 mFilePicker->Open(mFpCallback);
449 return NS_OK;
450 }
451
452 NS_IMETHODIMP
HandleResult(nsIContentPref * pref)453 UploadLastDir::ContentPrefCallback::HandleResult(nsIContentPref* pref)
454 {
455 mResult = pref;
456 return NS_OK;
457 }
458
459 NS_IMETHODIMP
HandleError(nsresult error)460 UploadLastDir::ContentPrefCallback::HandleError(nsresult error)
461 {
462 // HandleCompletion is always called (even with HandleError was called),
463 // so we don't need to do anything special here.
464 return NS_OK;
465 }
466
467 namespace {
468
469 /**
470 * This may return nullptr if the DOM File's implementation of
471 * File::mozFullPathInternal does not successfully return a non-empty
472 * string that is a valid path. This can happen on Firefox OS, for example,
473 * where the file picker can create Blobs.
474 */
475 static already_AddRefed<nsIFile>
LastUsedDirectory(const OwningFileOrDirectory & aData)476 LastUsedDirectory(const OwningFileOrDirectory& aData)
477 {
478 if (aData.IsFile()) {
479 nsAutoString path;
480 ErrorResult error;
481 aData.GetAsFile()->GetMozFullPathInternal(path, error);
482 if (error.Failed() || path.IsEmpty()) {
483 error.SuppressException();
484 return nullptr;
485 }
486
487 nsCOMPtr<nsIFile> localFile;
488 nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
489 getter_AddRefs(localFile));
490 if (NS_WARN_IF(NS_FAILED(rv))) {
491 return nullptr;
492 }
493
494 nsCOMPtr<nsIFile> parentFile;
495 rv = localFile->GetParent(getter_AddRefs(parentFile));
496 if (NS_WARN_IF(NS_FAILED(rv))) {
497 return nullptr;
498 }
499
500 return parentFile.forget();
501 }
502
503 MOZ_ASSERT(aData.IsDirectory());
504
505 nsCOMPtr<nsIFile> localFile = aData.GetAsDirectory()->GetInternalNsIFile();
506 MOZ_ASSERT(localFile);
507
508 return localFile.forget();
509 }
510
511 void
GetDOMFileOrDirectoryName(const OwningFileOrDirectory & aData,nsAString & aName)512 GetDOMFileOrDirectoryName(const OwningFileOrDirectory& aData,
513 nsAString& aName)
514 {
515 if (aData.IsFile()) {
516 aData.GetAsFile()->GetName(aName);
517 } else {
518 MOZ_ASSERT(aData.IsDirectory());
519 ErrorResult rv;
520 aData.GetAsDirectory()->GetName(aName, rv);
521 if (NS_WARN_IF(rv.Failed())) {
522 rv.SuppressException();
523 }
524 }
525 }
526
527 void
GetDOMFileOrDirectoryPath(const OwningFileOrDirectory & aData,nsAString & aPath,ErrorResult & aRv)528 GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData,
529 nsAString& aPath,
530 ErrorResult& aRv)
531 {
532 if (aData.IsFile()) {
533 aData.GetAsFile()->GetMozFullPathInternal(aPath, aRv);
534 } else {
535 MOZ_ASSERT(aData.IsDirectory());
536 aData.GetAsDirectory()->GetFullRealPath(aPath);
537 }
538 }
539
540 } // namespace
541
542 /* static */
543 bool
ValueAsDateEnabled(JSContext * cx,JSObject * obj)544 HTMLInputElement::ValueAsDateEnabled(JSContext* cx, JSObject* obj)
545 {
546 return Preferences::GetBool("dom.experimental_forms", false) ||
547 Preferences::GetBool("dom.forms.datepicker", false) ||
548 Preferences::GetBool("dom.forms.datetime", false);
549 }
550
551 NS_IMETHODIMP
Done(int16_t aResult)552 HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult)
553 {
554 mInput->PickerClosed();
555
556 if (aResult == nsIFilePicker::returnCancel) {
557 return NS_OK;
558 }
559
560 int16_t mode;
561 mFilePicker->GetMode(&mode);
562
563 // Collect new selected filenames
564 nsTArray<OwningFileOrDirectory> newFilesOrDirectories;
565 if (mode == static_cast<int16_t>(nsIFilePicker::modeOpenMultiple)) {
566 nsCOMPtr<nsISimpleEnumerator> iter;
567 nsresult rv =
568 mFilePicker->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter));
569 NS_ENSURE_SUCCESS(rv, rv);
570
571 if (!iter) {
572 return NS_OK;
573 }
574
575 nsCOMPtr<nsISupports> tmp;
576 bool hasMore = true;
577
578 while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
579 iter->GetNext(getter_AddRefs(tmp));
580 nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(tmp);
581 MOZ_ASSERT(domBlob,
582 "Null file object from FilePicker's file enumerator?");
583 if (!domBlob) {
584 continue;
585 }
586
587 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
588 element->SetAsFile() = static_cast<File*>(domBlob.get());
589 }
590 } else {
591 MOZ_ASSERT(mode == static_cast<int16_t>(nsIFilePicker::modeOpen) ||
592 mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder));
593 nsCOMPtr<nsISupports> tmp;
594 nsresult rv = mFilePicker->GetDomFileOrDirectory(getter_AddRefs(tmp));
595 NS_ENSURE_SUCCESS(rv, rv);
596
597 nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(tmp);
598 if (blob) {
599 RefPtr<File> file = static_cast<Blob*>(blob.get())->ToFile();
600 MOZ_ASSERT(file);
601
602 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
603 element->SetAsFile() = file;
604 } else if (tmp) {
605 RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
606 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
607 element->SetAsDirectory() = directory;
608 }
609 }
610
611 if (newFilesOrDirectories.IsEmpty()) {
612 return NS_OK;
613 }
614
615 // Store the last used directory using the content pref service:
616 nsCOMPtr<nsIFile> lastUsedDir = LastUsedDirectory(newFilesOrDirectories[0]);
617
618 if (lastUsedDir) {
619 HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
620 mInput->OwnerDoc(), lastUsedDir);
621 }
622
623 // The text control frame (if there is one) isn't going to send a change
624 // event because it will think this is done by a script.
625 // So, we can safely send one by ourself.
626 mInput->SetFilesOrDirectories(newFilesOrDirectories, true);
627
628 RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
629 new DispatchChangeEventCallback(mInput);
630
631 if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
632 mInput->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
633 ErrorResult error;
634 GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
635 if (NS_WARN_IF(error.Failed())) {
636 return error.StealNSResult();
637 }
638
639 helper->AddCallback(dispatchChangeEventCallback);
640 return NS_OK;
641 }
642
643 return dispatchChangeEventCallback->DispatchEvents();
644 }
645
646 NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback,
647 nsIFilePickerShownCallback)
648
649 class nsColorPickerShownCallback final
650 : public nsIColorPickerShownCallback
651 {
~nsColorPickerShownCallback()652 ~nsColorPickerShownCallback() {}
653
654 public:
nsColorPickerShownCallback(HTMLInputElement * aInput,nsIColorPicker * aColorPicker)655 nsColorPickerShownCallback(HTMLInputElement* aInput,
656 nsIColorPicker* aColorPicker)
657 : mInput(aInput)
658 , mColorPicker(aColorPicker)
659 , mValueChanged(false)
660 {}
661
662 NS_DECL_ISUPPORTS
663
664 NS_IMETHOD Update(const nsAString& aColor) override;
665 NS_IMETHOD Done(const nsAString& aColor) override;
666
667 private:
668 /**
669 * Updates the internals of the object using aColor as the new value.
670 * If aTrustedUpdate is true, it will consider that aColor is a new value.
671 * Otherwise, it will check that aColor is different from the current value.
672 */
673 nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
674
675 RefPtr<HTMLInputElement> mInput;
676 nsCOMPtr<nsIColorPicker> mColorPicker;
677 bool mValueChanged;
678 };
679
680 nsresult
UpdateInternal(const nsAString & aColor,bool aTrustedUpdate)681 nsColorPickerShownCallback::UpdateInternal(const nsAString& aColor,
682 bool aTrustedUpdate)
683 {
684 bool valueChanged = false;
685
686 nsAutoString oldValue;
687 if (aTrustedUpdate) {
688 valueChanged = true;
689 } else {
690 mInput->GetValue(oldValue);
691 }
692
693 mInput->SetValue(aColor);
694
695 if (!aTrustedUpdate) {
696 nsAutoString newValue;
697 mInput->GetValue(newValue);
698 if (!oldValue.Equals(newValue)) {
699 valueChanged = true;
700 }
701 }
702
703 if (valueChanged) {
704 mValueChanged = true;
705 return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
706 static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
707 NS_LITERAL_STRING("input"), true,
708 false);
709 }
710
711 return NS_OK;
712 }
713
714 NS_IMETHODIMP
Update(const nsAString & aColor)715 nsColorPickerShownCallback::Update(const nsAString& aColor)
716 {
717 return UpdateInternal(aColor, true);
718 }
719
720 NS_IMETHODIMP
Done(const nsAString & aColor)721 nsColorPickerShownCallback::Done(const nsAString& aColor)
722 {
723 /**
724 * When Done() is called, we might be at the end of a serie of Update() calls
725 * in which case mValueChanged is set to true and a change event will have to
726 * be fired but we might also be in a one shot Done() call situation in which
727 * case we should fire a change event iif the value actually changed.
728 * UpdateInternal(bool) is taking care of that logic for us.
729 */
730 nsresult rv = NS_OK;
731
732 mInput->PickerClosed();
733
734 if (!aColor.IsEmpty()) {
735 UpdateInternal(aColor, false);
736 }
737
738 if (mValueChanged) {
739 rv = nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
740 static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
741 NS_LITERAL_STRING("change"), true,
742 false);
743 }
744
745 return rv;
746 }
747
748 NS_IMPL_ISUPPORTS(nsColorPickerShownCallback, nsIColorPickerShownCallback)
749
750 class DatePickerShownCallback final : public nsIDatePickerShownCallback
751 {
~DatePickerShownCallback()752 ~DatePickerShownCallback() {}
753 public:
DatePickerShownCallback(HTMLInputElement * aInput,nsIDatePicker * aDatePicker)754 DatePickerShownCallback(HTMLInputElement* aInput,
755 nsIDatePicker* aDatePicker)
756 : mInput(aInput)
757 , mDatePicker(aDatePicker)
758 {}
759
760 NS_DECL_ISUPPORTS
761
762 NS_IMETHOD Done(const nsAString& aDate) override;
763 NS_IMETHOD Cancel() override;
764
765 private:
766 RefPtr<HTMLInputElement> mInput;
767 nsCOMPtr<nsIDatePicker> mDatePicker;
768 };
769
770 NS_IMETHODIMP
Cancel()771 DatePickerShownCallback::Cancel()
772 {
773 mInput->PickerClosed();
774 return NS_OK;
775 }
776
777 NS_IMETHODIMP
Done(const nsAString & aDate)778 DatePickerShownCallback::Done(const nsAString& aDate)
779 {
780 nsAutoString oldValue;
781
782 mInput->PickerClosed();
783 mInput->GetValue(oldValue);
784
785 if(!oldValue.Equals(aDate)){
786 mInput->SetValue(aDate);
787 nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
788 static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
789 NS_LITERAL_STRING("input"), true,
790 false);
791 return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
792 static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
793 NS_LITERAL_STRING("change"), true,
794 false);
795 }
796
797 return NS_OK;
798 }
799
NS_IMPL_ISUPPORTS(DatePickerShownCallback,nsIDatePickerShownCallback)800 NS_IMPL_ISUPPORTS(DatePickerShownCallback, nsIDatePickerShownCallback)
801
802
803 bool
804 HTMLInputElement::IsPopupBlocked() const
805 {
806 nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow();
807 MOZ_ASSERT(win, "window should not be null");
808 if (!win) {
809 return true;
810 }
811
812 // Check if page is allowed to open the popup
813 if (win->GetPopupControlState() <= openControlled) {
814 return false;
815 }
816
817 nsCOMPtr<nsIPopupWindowManager> pm = do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID);
818 if (!pm) {
819 return true;
820 }
821
822 uint32_t permission;
823 pm->TestPermission(OwnerDoc()->NodePrincipal(), &permission);
824 return permission == nsIPopupWindowManager::DENY_POPUP;
825 }
826
827 nsresult
InitDatePicker()828 HTMLInputElement::InitDatePicker()
829 {
830 if (!Preferences::GetBool("dom.forms.datepicker", false)) {
831 return NS_OK;
832 }
833
834 if (mPickerRunning) {
835 NS_WARNING("Just one nsIDatePicker is allowed");
836 return NS_ERROR_FAILURE;
837 }
838
839 nsCOMPtr<nsIDocument> doc = OwnerDoc();
840
841 nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
842 if (!win) {
843 return NS_ERROR_FAILURE;
844 }
845
846 if (IsPopupBlocked()) {
847 win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
848 return NS_OK;
849 }
850
851 // Get Loc title
852 nsXPIDLString title;
853 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
854 "DatePicker", title);
855
856 nsresult rv;
857 nsCOMPtr<nsIDatePicker> datePicker = do_CreateInstance("@mozilla.org/datepicker;1", &rv);
858 if (!datePicker) {
859 return rv;
860 }
861
862 nsAutoString initialValue;
863 GetValueInternal(initialValue);
864 rv = datePicker->Init(win, title, initialValue);
865
866 nsCOMPtr<nsIDatePickerShownCallback> callback =
867 new DatePickerShownCallback(this, datePicker);
868
869 rv = datePicker->Open(callback);
870 if (NS_SUCCEEDED(rv)) {
871 mPickerRunning = true;
872 }
873
874 return rv;
875 }
876
877 nsresult
InitColorPicker()878 HTMLInputElement::InitColorPicker()
879 {
880 if (mPickerRunning) {
881 NS_WARNING("Just one nsIColorPicker is allowed");
882 return NS_ERROR_FAILURE;
883 }
884
885 nsCOMPtr<nsIDocument> doc = OwnerDoc();
886
887 nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
888 if (!win) {
889 return NS_ERROR_FAILURE;
890 }
891
892 if (IsPopupBlocked()) {
893 win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
894 return NS_OK;
895 }
896
897 // Get Loc title
898 nsXPIDLString title;
899 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
900 "ColorPicker", title);
901
902 nsCOMPtr<nsIColorPicker> colorPicker = do_CreateInstance("@mozilla.org/colorpicker;1");
903 if (!colorPicker) {
904 return NS_ERROR_FAILURE;
905 }
906
907 nsAutoString initialValue;
908 GetValueInternal(initialValue);
909 nsresult rv = colorPicker->Init(win, title, initialValue);
910 NS_ENSURE_SUCCESS(rv, rv);
911
912 nsCOMPtr<nsIColorPickerShownCallback> callback =
913 new nsColorPickerShownCallback(this, colorPicker);
914
915 rv = colorPicker->Open(callback);
916 if (NS_SUCCEEDED(rv)) {
917 mPickerRunning = true;
918 }
919
920 return rv;
921 }
922
923 nsresult
InitFilePicker(FilePickerType aType)924 HTMLInputElement::InitFilePicker(FilePickerType aType)
925 {
926 if (mPickerRunning) {
927 NS_WARNING("Just one nsIFilePicker is allowed");
928 return NS_ERROR_FAILURE;
929 }
930
931 // Get parent nsPIDOMWindow object.
932 nsCOMPtr<nsIDocument> doc = OwnerDoc();
933
934 nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
935 if (!win) {
936 return NS_ERROR_FAILURE;
937 }
938
939 if (IsPopupBlocked()) {
940 win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
941 return NS_OK;
942 }
943
944 // Get Loc title
945 nsXPIDLString title;
946 nsXPIDLString okButtonLabel;
947 if (aType == FILE_PICKER_DIRECTORY) {
948 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
949 "DirectoryUpload", title);
950
951 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
952 "DirectoryPickerOkButtonLabel",
953 okButtonLabel);
954 } else {
955 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
956 "FileUpload", title);
957 }
958
959 nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1");
960 if (!filePicker)
961 return NS_ERROR_FAILURE;
962
963 int16_t mode;
964
965 if (aType == FILE_PICKER_DIRECTORY) {
966 mode = static_cast<int16_t>(nsIFilePicker::modeGetFolder);
967 } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
968 mode = static_cast<int16_t>(nsIFilePicker::modeOpenMultiple);
969 } else {
970 mode = static_cast<int16_t>(nsIFilePicker::modeOpen);
971 }
972
973 nsresult rv = filePicker->Init(win, title, mode);
974 NS_ENSURE_SUCCESS(rv, rv);
975
976 if (!okButtonLabel.IsEmpty()) {
977 filePicker->SetOkButtonLabel(okButtonLabel);
978 }
979
980 // Native directory pickers ignore file type filters, so we don't spend
981 // cycles adding them for FILE_PICKER_DIRECTORY.
982 if (HasAttr(kNameSpaceID_None, nsGkAtoms::accept) &&
983 aType != FILE_PICKER_DIRECTORY) {
984 SetFilePickerFiltersFromAccept(filePicker);
985 } else {
986 filePicker->AppendFilters(nsIFilePicker::filterAll);
987 }
988
989 // Set default directory and filename
990 nsAutoString defaultName;
991
992 const nsTArray<OwningFileOrDirectory>& oldFiles =
993 GetFilesOrDirectoriesInternal();
994
995 nsCOMPtr<nsIFilePickerShownCallback> callback =
996 new HTMLInputElement::nsFilePickerShownCallback(this, filePicker);
997
998 if (!oldFiles.IsEmpty() &&
999 aType != FILE_PICKER_DIRECTORY) {
1000 nsAutoString path;
1001
1002 nsCOMPtr<nsIFile> parentFile = LastUsedDirectory(oldFiles[0]);
1003 if (parentFile) {
1004 filePicker->SetDisplayDirectory(parentFile);
1005 }
1006
1007 // Unfortunately nsIFilePicker doesn't allow multiple files to be
1008 // default-selected, so only select something by default if exactly
1009 // one file was selected before.
1010 if (oldFiles.Length() == 1) {
1011 nsAutoString leafName;
1012 GetDOMFileOrDirectoryName(oldFiles[0], leafName);
1013
1014 if (!leafName.IsEmpty()) {
1015 filePicker->SetDefaultString(leafName);
1016 }
1017 }
1018
1019 rv = filePicker->Open(callback);
1020 if (NS_SUCCEEDED(rv)) {
1021 mPickerRunning = true;
1022 }
1023
1024 return rv;
1025 }
1026
1027 HTMLInputElement::gUploadLastDir->FetchDirectoryAndDisplayPicker(doc, filePicker, callback);
1028 mPickerRunning = true;
1029 return NS_OK;
1030 }
1031
1032 #define CPS_PREF_NAME NS_LITERAL_STRING("browser.upload.lastDir")
1033
NS_IMPL_ISUPPORTS(UploadLastDir,nsIObserver,nsISupportsWeakReference)1034 NS_IMPL_ISUPPORTS(UploadLastDir, nsIObserver, nsISupportsWeakReference)
1035
1036 void
1037 HTMLInputElement::InitUploadLastDir() {
1038 gUploadLastDir = new UploadLastDir();
1039 NS_ADDREF(gUploadLastDir);
1040
1041 nsCOMPtr<nsIObserverService> observerService =
1042 mozilla::services::GetObserverService();
1043 if (observerService && gUploadLastDir) {
1044 observerService->AddObserver(gUploadLastDir, "browser:purge-session-history", true);
1045 }
1046 }
1047
1048 void
DestroyUploadLastDir()1049 HTMLInputElement::DestroyUploadLastDir() {
1050 NS_IF_RELEASE(gUploadLastDir);
1051 }
1052
1053 nsresult
FetchDirectoryAndDisplayPicker(nsIDocument * aDoc,nsIFilePicker * aFilePicker,nsIFilePickerShownCallback * aFpCallback)1054 UploadLastDir::FetchDirectoryAndDisplayPicker(nsIDocument* aDoc,
1055 nsIFilePicker* aFilePicker,
1056 nsIFilePickerShownCallback* aFpCallback)
1057 {
1058 NS_PRECONDITION(aDoc, "aDoc is null");
1059 NS_PRECONDITION(aFilePicker, "aFilePicker is null");
1060 NS_PRECONDITION(aFpCallback, "aFpCallback is null");
1061
1062 nsIURI* docURI = aDoc->GetDocumentURI();
1063 NS_PRECONDITION(docURI, "docURI is null");
1064
1065 nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
1066 nsCOMPtr<nsIContentPrefCallback2> prefCallback =
1067 new UploadLastDir::ContentPrefCallback(aFilePicker, aFpCallback);
1068
1069 #ifdef MOZ_B2G
1070 if (XRE_IsContentProcess()) {
1071 prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
1072 return NS_OK;
1073 }
1074 #endif
1075
1076 // Attempt to get the CPS, if it's not present we'll fallback to use the Desktop folder
1077 nsCOMPtr<nsIContentPrefService2> contentPrefService =
1078 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
1079 if (!contentPrefService) {
1080 prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
1081 return NS_OK;
1082 }
1083
1084 nsAutoCString cstrSpec;
1085 docURI->GetSpec(cstrSpec);
1086 NS_ConvertUTF8toUTF16 spec(cstrSpec);
1087
1088 contentPrefService->GetByDomainAndName(spec, CPS_PREF_NAME, loadContext, prefCallback);
1089 return NS_OK;
1090 }
1091
1092 nsresult
StoreLastUsedDirectory(nsIDocument * aDoc,nsIFile * aDir)1093 UploadLastDir::StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aDir)
1094 {
1095 NS_PRECONDITION(aDoc, "aDoc is null");
1096 if (!aDir) {
1097 return NS_OK;
1098 }
1099
1100 #ifdef MOZ_B2G
1101 if (XRE_IsContentProcess()) {
1102 return NS_OK;
1103 }
1104 #endif
1105
1106 nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
1107 NS_PRECONDITION(docURI, "docURI is null");
1108
1109 // Attempt to get the CPS, if it's not present we'll just return
1110 nsCOMPtr<nsIContentPrefService2> contentPrefService =
1111 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
1112 if (!contentPrefService)
1113 return NS_ERROR_NOT_AVAILABLE;
1114
1115 nsAutoCString cstrSpec;
1116 docURI->GetSpec(cstrSpec);
1117 NS_ConvertUTF8toUTF16 spec(cstrSpec);
1118
1119 // Find the parent of aFile, and store it
1120 nsString unicodePath;
1121 aDir->GetPath(unicodePath);
1122 if (unicodePath.IsEmpty()) // nothing to do
1123 return NS_OK;
1124 RefPtr<nsVariantCC> prefValue = new nsVariantCC();
1125 prefValue->SetAsAString(unicodePath);
1126
1127 // Use the document's current load context to ensure that the content pref
1128 // service doesn't persistently store this directory for this domain if the
1129 // user is using private browsing:
1130 nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
1131 return contentPrefService->Set(spec, CPS_PREF_NAME, prefValue, loadContext, nullptr);
1132 }
1133
1134 NS_IMETHODIMP
Observe(nsISupports * aSubject,char const * aTopic,char16_t const * aData)1135 UploadLastDir::Observe(nsISupports* aSubject, char const* aTopic, char16_t const* aData)
1136 {
1137 if (strcmp(aTopic, "browser:purge-session-history") == 0) {
1138 nsCOMPtr<nsIContentPrefService2> contentPrefService =
1139 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
1140 if (contentPrefService)
1141 contentPrefService->RemoveByName(CPS_PREF_NAME, nullptr, nullptr);
1142 }
1143 return NS_OK;
1144 }
1145
1146 #ifdef ACCESSIBILITY
1147 //Helper method
1148 static nsresult FireEventForAccessibility(nsIDOMHTMLInputElement* aTarget,
1149 nsPresContext* aPresContext,
1150 const nsAString& aEventType);
1151 #endif
1152
1153 //
1154 // construction, destruction
1155 //
1156
HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo> & aNodeInfo,FromParser aFromParser,FromClone aFromClone)1157 HTMLInputElement::HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
1158 FromParser aFromParser, FromClone aFromClone)
1159 : nsGenericHTMLFormElementWithState(aNodeInfo)
1160 , mType(kInputDefaultType->value)
1161 , mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown)
1162 , mDisabledChanged(false)
1163 , mValueChanged(false)
1164 , mLastValueChangeWasInteractive(false)
1165 , mCheckedChanged(false)
1166 , mChecked(false)
1167 , mHandlingSelectEvent(false)
1168 , mShouldInitChecked(false)
1169 , mDoneCreating(aFromParser == NOT_FROM_PARSER &&
1170 aFromClone == FromClone::no)
1171 , mInInternalActivate(false)
1172 , mCheckedIsToggled(false)
1173 , mIndeterminate(false)
1174 , mInhibitRestoration(aFromParser & FROM_PARSER_FRAGMENT)
1175 , mCanShowValidUI(true)
1176 , mCanShowInvalidUI(true)
1177 , mHasRange(false)
1178 , mIsDraggingRange(false)
1179 , mNumberControlSpinnerIsSpinning(false)
1180 , mNumberControlSpinnerSpinsUp(false)
1181 , mPickerRunning(false)
1182 , mSelectionCached(true)
1183 {
1184 // We are in a type=text so we now we currenty need a nsTextEditorState.
1185 mInputData.mState = new nsTextEditorState(this);
1186
1187 if (!gUploadLastDir)
1188 HTMLInputElement::InitUploadLastDir();
1189
1190 // Set up our default state. By default we're enabled (since we're
1191 // a control type that can be disabled but not actually disabled
1192 // right now), optional, and valid. We are NOT readwrite by default
1193 // until someone calls UpdateEditableState on us, apparently! Also
1194 // by default we don't have to show validity UI and so forth.
1195 AddStatesSilently(NS_EVENT_STATE_ENABLED |
1196 NS_EVENT_STATE_OPTIONAL |
1197 NS_EVENT_STATE_VALID);
1198 UpdateApzAwareFlag();
1199 }
1200
~HTMLInputElement()1201 HTMLInputElement::~HTMLInputElement()
1202 {
1203 if (mNumberControlSpinnerIsSpinning) {
1204 StopNumberControlSpinnerSpin(eDisallowDispatchingEvents);
1205 }
1206 DestroyImageLoadingContent();
1207 FreeData();
1208 }
1209
1210 void
FreeData()1211 HTMLInputElement::FreeData()
1212 {
1213 if (!IsSingleLineTextControl(false)) {
1214 free(mInputData.mValue);
1215 mInputData.mValue = nullptr;
1216 } else {
1217 UnbindFromFrame(nullptr);
1218 delete mInputData.mState;
1219 mInputData.mState = nullptr;
1220 }
1221 }
1222
1223 nsTextEditorState*
GetEditorState() const1224 HTMLInputElement::GetEditorState() const
1225 {
1226 if (!IsSingleLineTextControl(false)) {
1227 return nullptr;
1228 }
1229
1230 MOZ_ASSERT(mInputData.mState, "Single line text controls need to have a state"
1231 " associated with them");
1232
1233 return mInputData.mState;
1234 }
1235
1236
1237 // nsISupports
1238
1239 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement)
1240
1241 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
1242 nsGenericHTMLFormElementWithState)
1243 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
1244 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
1245 if (tmp->IsSingleLineTextControl(false)) {
1246 tmp->mInputData.mState->Traverse(cb);
1247 }
1248 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories)
1249
1250 if (tmp->mGetFilesRecursiveHelper) {
1251 tmp->mGetFilesRecursiveHelper->Traverse(cb);
1252 }
1253
1254 if (tmp->mGetFilesNonRecursiveHelper) {
1255 tmp->mGetFilesNonRecursiveHelper->Traverse(cb);
1256 }
1257
1258 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
1259 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries)
1260 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1261
1262 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
1263 nsGenericHTMLFormElementWithState)
1264 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
1265 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
1266 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories)
1267 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
1268 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries)
1269 if (tmp->IsSingleLineTextControl(false)) {
1270 tmp->mInputData.mState->Unlink();
1271 }
1272
1273 tmp->ClearGetFilesHelpers();
1274
1275 //XXX should unlink more?
1276 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1277
NS_IMPL_ADDREF_INHERITED(HTMLInputElement,Element)1278 NS_IMPL_ADDREF_INHERITED(HTMLInputElement, Element)
1279 NS_IMPL_RELEASE_INHERITED(HTMLInputElement, Element)
1280
1281 // QueryInterface implementation for HTMLInputElement
1282 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLInputElement)
1283 NS_INTERFACE_TABLE_INHERITED(HTMLInputElement,
1284 nsIDOMHTMLInputElement,
1285 nsITextControlElement,
1286 nsIPhonetic,
1287 imgINotificationObserver,
1288 nsIImageLoadingContent,
1289 imgIOnloadBlocker,
1290 nsIDOMNSEditableElement,
1291 nsIConstraintValidation)
1292 NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState)
1293
1294 // nsIConstraintValidation
1295 NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLInputElement)
1296
1297 // nsIDOMNode
1298
1299 nsresult
1300 HTMLInputElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const
1301 {
1302 *aResult = nullptr;
1303
1304 already_AddRefed<mozilla::dom::NodeInfo> ni = RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
1305 RefPtr<HTMLInputElement> it = new HTMLInputElement(ni, NOT_FROM_PARSER,
1306 FromClone::yes);
1307
1308 nsresult rv = const_cast<HTMLInputElement*>(this)->CopyInnerTo(it);
1309 NS_ENSURE_SUCCESS(rv, rv);
1310
1311 switch (GetValueMode()) {
1312 case VALUE_MODE_VALUE:
1313 if (mValueChanged) {
1314 // We don't have our default value anymore. Set our value on
1315 // the clone.
1316 nsAutoString value;
1317 GetValueInternal(value);
1318 // SetValueInternal handles setting the VALUE_CHANGED bit for us
1319 rv = it->SetValueInternal(value, nsTextEditorState::eSetValue_Notify);
1320 NS_ENSURE_SUCCESS(rv, rv);
1321 }
1322 break;
1323 case VALUE_MODE_FILENAME:
1324 if (it->OwnerDoc()->IsStaticDocument()) {
1325 // We're going to be used in print preview. Since the doc is static
1326 // we can just grab the pretty string and use it as wallpaper
1327 GetDisplayFileName(it->mStaticDocFileList);
1328 } else {
1329 it->ClearGetFilesHelpers();
1330 it->mFilesOrDirectories.Clear();
1331 it->mFilesOrDirectories.AppendElements(mFilesOrDirectories);
1332 }
1333 break;
1334 case VALUE_MODE_DEFAULT_ON:
1335 if (mCheckedChanged) {
1336 // We no longer have our original checked state. Set our
1337 // checked state on the clone.
1338 it->DoSetChecked(mChecked, false, true);
1339 // Then tell DoneCreatingElement() not to overwrite:
1340 it->mShouldInitChecked = false;
1341 }
1342 break;
1343 case VALUE_MODE_DEFAULT:
1344 if (mType == NS_FORM_INPUT_IMAGE && it->OwnerDoc()->IsStaticDocument()) {
1345 CreateStaticImageClone(it);
1346 }
1347 break;
1348 }
1349
1350 it->DoneCreatingElement();
1351
1352 it->mLastValueChangeWasInteractive = mLastValueChangeWasInteractive;
1353 it.forget(aResult);
1354 return NS_OK;
1355 }
1356
1357 nsresult
BeforeSetAttr(int32_t aNameSpaceID,nsIAtom * aName,nsAttrValueOrString * aValue,bool aNotify)1358 HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
1359 nsAttrValueOrString* aValue,
1360 bool aNotify)
1361 {
1362 if (aNameSpaceID == kNameSpaceID_None) {
1363 //
1364 // When name or type changes, radio should be removed from radio group.
1365 // (type changes are handled in the form itself currently)
1366 // If we are not done creating the radio, we also should not do it.
1367 //
1368 if ((aName == nsGkAtoms::name ||
1369 (aName == nsGkAtoms::type && !mForm)) &&
1370 mType == NS_FORM_INPUT_RADIO &&
1371 (mForm || mDoneCreating)) {
1372 WillRemoveFromRadioGroup();
1373 } else if (aNotify && aName == nsGkAtoms::src &&
1374 mType == NS_FORM_INPUT_IMAGE) {
1375 if (aValue) {
1376 LoadImage(aValue->String(), true, aNotify, eImageLoadType_Normal);
1377 } else {
1378 // Null value means the attr got unset; drop the image
1379 CancelImageRequests(aNotify);
1380 }
1381 } else if (aNotify && aName == nsGkAtoms::disabled) {
1382 mDisabledChanged = true;
1383 } else if (aName == nsGkAtoms::dir &&
1384 AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
1385 nsGkAtoms::_auto, eIgnoreCase)) {
1386 SetDirectionIfAuto(false, aNotify);
1387 } else if (mType == NS_FORM_INPUT_RADIO && aName == nsGkAtoms::required) {
1388 nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
1389
1390 if (container &&
1391 ((aValue && !HasAttr(aNameSpaceID, aName)) ||
1392 (!aValue && HasAttr(aNameSpaceID, aName)))) {
1393 nsAutoString name;
1394 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
1395 container->RadioRequiredWillChange(name, !!aValue);
1396 }
1397 }
1398
1399 if (aName == nsGkAtoms::webkitdirectory) {
1400 Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED, true);
1401 }
1402 }
1403
1404 return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
1405 aValue, aNotify);
1406 }
1407
1408 nsresult
AfterSetAttr(int32_t aNameSpaceID,nsIAtom * aName,const nsAttrValue * aValue,bool aNotify)1409 HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
1410 const nsAttrValue* aValue, bool aNotify)
1411 {
1412 if (aNameSpaceID == kNameSpaceID_None) {
1413 //
1414 // When name or type changes, radio should be added to radio group.
1415 // (type changes are handled in the form itself currently)
1416 // If we are not done creating the radio, we also should not do it.
1417 //
1418 if ((aName == nsGkAtoms::name ||
1419 (aName == nsGkAtoms::type && !mForm)) &&
1420 mType == NS_FORM_INPUT_RADIO &&
1421 (mForm || mDoneCreating)) {
1422 AddedToRadioGroup();
1423 UpdateValueMissingValidityStateForRadio(false);
1424 }
1425
1426 // If @value is changed and BF_VALUE_CHANGED is false, @value is the value
1427 // of the element so, if the value of the element is different than @value,
1428 // we have to re-set it. This is only the case when GetValueMode() returns
1429 // VALUE_MODE_VALUE.
1430 if (aName == nsGkAtoms::value &&
1431 !mValueChanged && GetValueMode() == VALUE_MODE_VALUE) {
1432 SetDefaultValueAsValue();
1433 }
1434
1435 //
1436 // Checked must be set no matter what type of control it is, since
1437 // mChecked must reflect the new value
1438 if (aName == nsGkAtoms::checked && !mCheckedChanged) {
1439 // Delay setting checked if we are creating this element (wait
1440 // until everything is set)
1441 if (!mDoneCreating) {
1442 mShouldInitChecked = true;
1443 } else {
1444 DoSetChecked(DefaultChecked(), true, true);
1445 SetCheckedChanged(false);
1446 }
1447 }
1448
1449 if (aName == nsGkAtoms::type) {
1450 if (!aValue) {
1451 // We're now a text input. Note that we have to handle this manually,
1452 // since removing an attribute (which is what happened, since aValue is
1453 // null) doesn't call ParseAttribute.
1454 HandleTypeChange(kInputDefaultType->value);
1455 }
1456
1457 UpdateBarredFromConstraintValidation();
1458
1459 if (mType != NS_FORM_INPUT_IMAGE) {
1460 // We're no longer an image input. Cancel our image requests, if we have
1461 // any. Note that doing this when we already weren't an image is ok --
1462 // just does nothing.
1463 CancelImageRequests(aNotify);
1464 } else if (aNotify) {
1465 // We just got switched to be an image input; we should see
1466 // whether we have an image to load;
1467 nsAutoString src;
1468 if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
1469 LoadImage(src, false, aNotify, eImageLoadType_Normal);
1470 }
1471 }
1472
1473 if (mType == NS_FORM_INPUT_PASSWORD && IsInComposedDoc()) {
1474 AsyncEventDispatcher* dispatcher =
1475 new AsyncEventDispatcher(this,
1476 NS_LITERAL_STRING("DOMInputPasswordAdded"),
1477 true,
1478 true);
1479 dispatcher->PostDOMEvent();
1480 }
1481 }
1482
1483 if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
1484 aName == nsGkAtoms::readonly) {
1485 UpdateValueMissingValidityState();
1486
1487 // This *has* to be called *after* validity has changed.
1488 if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
1489 UpdateBarredFromConstraintValidation();
1490 }
1491 } else if (MinOrMaxLengthApplies() && aName == nsGkAtoms::maxlength) {
1492 UpdateTooLongValidityState();
1493 } else if (MinOrMaxLengthApplies() && aName == nsGkAtoms::minlength) {
1494 UpdateTooShortValidityState();
1495 } else if (aName == nsGkAtoms::pattern && mDoneCreating) {
1496 UpdatePatternMismatchValidityState();
1497 } else if (aName == nsGkAtoms::multiple) {
1498 UpdateTypeMismatchValidityState();
1499 } else if (aName == nsGkAtoms::max) {
1500 UpdateHasRange();
1501 if (mType == NS_FORM_INPUT_RANGE) {
1502 // The value may need to change when @max changes since the value may
1503 // have been invalid and can now change to a valid value, or vice
1504 // versa. For example, consider:
1505 // <input type=range value=-1 max=1 step=3>. The valid range is 0 to 1
1506 // while the nearest valid steps are -1 and 2 (the max value having
1507 // prevented there being a valid step in range). Changing @max to/from
1508 // 1 and a number greater than on equal to 3 should change whether we
1509 // have a step mismatch or not.
1510 // The value may also need to change between a value that results in
1511 // a step mismatch and a value that results in overflow. For example,
1512 // if @max in the example above were to change from 1 to -1.
1513 nsAutoString value;
1514 GetValue(value);
1515 nsresult rv =
1516 SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
1517 NS_ENSURE_SUCCESS(rv, rv);
1518 }
1519 // Validity state must be updated *after* the SetValueInternal call above
1520 // or else the following assert will not be valid.
1521 // We don't assert the state of underflow during creation since
1522 // DoneCreatingElement sanitizes.
1523 UpdateRangeOverflowValidityState();
1524 MOZ_ASSERT(!mDoneCreating ||
1525 mType != NS_FORM_INPUT_RANGE ||
1526 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1527 "HTML5 spec does not allow underflow for type=range");
1528 } else if (aName == nsGkAtoms::min) {
1529 UpdateHasRange();
1530 if (mType == NS_FORM_INPUT_RANGE) {
1531 // See @max comment
1532 nsAutoString value;
1533 GetValue(value);
1534 nsresult rv =
1535 SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
1536 NS_ENSURE_SUCCESS(rv, rv);
1537 }
1538 // See corresponding @max comment
1539 UpdateRangeUnderflowValidityState();
1540 UpdateStepMismatchValidityState();
1541 MOZ_ASSERT(!mDoneCreating ||
1542 mType != NS_FORM_INPUT_RANGE ||
1543 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1544 "HTML5 spec does not allow underflow for type=range");
1545 } else if (aName == nsGkAtoms::step) {
1546 if (mType == NS_FORM_INPUT_RANGE) {
1547 // See @max comment
1548 nsAutoString value;
1549 GetValue(value);
1550 nsresult rv =
1551 SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
1552 NS_ENSURE_SUCCESS(rv, rv);
1553 }
1554 // See corresponding @max comment
1555 UpdateStepMismatchValidityState();
1556 MOZ_ASSERT(!mDoneCreating ||
1557 mType != NS_FORM_INPUT_RANGE ||
1558 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1559 "HTML5 spec does not allow underflow for type=range");
1560 } else if (aName == nsGkAtoms::dir &&
1561 aValue && aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
1562 SetDirectionIfAuto(true, aNotify);
1563 } else if (aName == nsGkAtoms::lang) {
1564 if (mType == NS_FORM_INPUT_NUMBER) {
1565 // Update the value that is displayed to the user to the new locale:
1566 nsAutoString value;
1567 GetValueInternal(value);
1568 nsNumberControlFrame* numberControlFrame =
1569 do_QueryFrame(GetPrimaryFrame());
1570 if (numberControlFrame) {
1571 numberControlFrame->SetValueOfAnonTextControl(value);
1572 }
1573 }
1574 } else if (aName == nsGkAtoms::autocomplete) {
1575 // Clear the cached @autocomplete attribute state.
1576 mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
1577 }
1578
1579 UpdateState(aNotify);
1580 }
1581
1582 return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
1583 aValue, aNotify);
1584 }
1585
1586 // nsIDOMHTMLInputElement
1587
1588 NS_IMETHODIMP
GetForm(nsIDOMHTMLFormElement ** aForm)1589 HTMLInputElement::GetForm(nsIDOMHTMLFormElement** aForm)
1590 {
1591 return nsGenericHTMLFormElementWithState::GetForm(aForm);
1592 }
1593
NS_IMPL_STRING_ATTR(HTMLInputElement,DefaultValue,value)1594 NS_IMPL_STRING_ATTR(HTMLInputElement, DefaultValue, value)
1595 NS_IMPL_BOOL_ATTR(HTMLInputElement, DefaultChecked, checked)
1596 NS_IMPL_STRING_ATTR(HTMLInputElement, Accept, accept)
1597 NS_IMPL_STRING_ATTR(HTMLInputElement, Align, align)
1598 NS_IMPL_STRING_ATTR(HTMLInputElement, Alt, alt)
1599 NS_IMPL_BOOL_ATTR(HTMLInputElement, Autofocus, autofocus)
1600 //NS_IMPL_BOOL_ATTR(HTMLInputElement, Checked, checked)
1601 NS_IMPL_BOOL_ATTR(HTMLInputElement, Disabled, disabled)
1602 NS_IMPL_STRING_ATTR(HTMLInputElement, Max, max)
1603 NS_IMPL_STRING_ATTR(HTMLInputElement, Min, min)
1604 NS_IMPL_ACTION_ATTR(HTMLInputElement, FormAction, formaction)
1605 NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLInputElement, FormEnctype, formenctype,
1606 "", kFormDefaultEnctype->tag)
1607 NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLInputElement, FormMethod, formmethod,
1608 "", kFormDefaultMethod->tag)
1609 NS_IMPL_BOOL_ATTR(HTMLInputElement, FormNoValidate, formnovalidate)
1610 NS_IMPL_STRING_ATTR(HTMLInputElement, FormTarget, formtarget)
1611 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, InputMode, inputmode,
1612 kInputDefaultInputmode->tag)
1613 NS_IMPL_BOOL_ATTR(HTMLInputElement, Multiple, multiple)
1614 NS_IMPL_NON_NEGATIVE_INT_ATTR(HTMLInputElement, MaxLength, maxlength)
1615 NS_IMPL_NON_NEGATIVE_INT_ATTR(HTMLInputElement, MinLength, minlength)
1616 NS_IMPL_STRING_ATTR(HTMLInputElement, Name, name)
1617 NS_IMPL_BOOL_ATTR(HTMLInputElement, ReadOnly, readonly)
1618 NS_IMPL_BOOL_ATTR(HTMLInputElement, Required, required)
1619 NS_IMPL_URI_ATTR(HTMLInputElement, Src, src)
1620 NS_IMPL_STRING_ATTR(HTMLInputElement, Step, step)
1621 NS_IMPL_STRING_ATTR(HTMLInputElement, UseMap, usemap)
1622 //NS_IMPL_STRING_ATTR(HTMLInputElement, Value, value)
1623 NS_IMPL_UINT_ATTR_NON_ZERO_DEFAULT_VALUE(HTMLInputElement, Size, size, DEFAULT_COLS)
1624 NS_IMPL_STRING_ATTR(HTMLInputElement, Pattern, pattern)
1625 NS_IMPL_STRING_ATTR(HTMLInputElement, Placeholder, placeholder)
1626 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, Type, type,
1627 kInputDefaultType->tag)
1628
1629 NS_IMETHODIMP
1630 HTMLInputElement::GetAutocomplete(nsAString& aValue)
1631 {
1632 if (!DoesAutocompleteApply()) {
1633 return NS_OK;
1634 }
1635
1636 aValue.Truncate(0);
1637 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1638
1639 mAutocompleteAttrState =
1640 nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
1641 mAutocompleteAttrState);
1642 return NS_OK;
1643 }
1644
1645 NS_IMETHODIMP
SetAutocomplete(const nsAString & aValue)1646 HTMLInputElement::SetAutocomplete(const nsAString& aValue)
1647 {
1648 return SetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, nullptr, aValue, true);
1649 }
1650
1651 void
GetAutocompleteInfo(Nullable<AutocompleteInfo> & aInfo)1652 HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo)
1653 {
1654 if (!DoesAutocompleteApply()) {
1655 aInfo.SetNull();
1656 return;
1657 }
1658
1659 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1660 mAutocompleteAttrState =
1661 nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aInfo.SetValue(),
1662 mAutocompleteAttrState);
1663 }
1664
1665 int32_t
TabIndexDefault()1666 HTMLInputElement::TabIndexDefault()
1667 {
1668 return 0;
1669 }
1670
1671 uint32_t
Height()1672 HTMLInputElement::Height()
1673 {
1674 if (mType != NS_FORM_INPUT_IMAGE) {
1675 return 0;
1676 }
1677 return GetWidthHeightForImage(mCurrentRequest).height;
1678 }
1679
1680 NS_IMETHODIMP
GetHeight(uint32_t * aHeight)1681 HTMLInputElement::GetHeight(uint32_t* aHeight)
1682 {
1683 *aHeight = Height();
1684 return NS_OK;
1685 }
1686
1687 NS_IMETHODIMP
SetHeight(uint32_t aHeight)1688 HTMLInputElement::SetHeight(uint32_t aHeight)
1689 {
1690 ErrorResult rv;
1691 SetHeight(aHeight, rv);
1692 return rv.StealNSResult();
1693 }
1694
1695 NS_IMETHODIMP
GetIndeterminate(bool * aValue)1696 HTMLInputElement::GetIndeterminate(bool* aValue)
1697 {
1698 *aValue = Indeterminate();
1699 return NS_OK;
1700 }
1701
1702 void
SetIndeterminateInternal(bool aValue,bool aShouldInvalidate)1703 HTMLInputElement::SetIndeterminateInternal(bool aValue,
1704 bool aShouldInvalidate)
1705 {
1706 mIndeterminate = aValue;
1707
1708 if (aShouldInvalidate) {
1709 // Repaint the frame
1710 nsIFrame* frame = GetPrimaryFrame();
1711 if (frame)
1712 frame->InvalidateFrameSubtree();
1713 }
1714
1715 UpdateState(true);
1716 }
1717
1718 NS_IMETHODIMP
SetIndeterminate(bool aValue)1719 HTMLInputElement::SetIndeterminate(bool aValue)
1720 {
1721 SetIndeterminateInternal(aValue, true);
1722 return NS_OK;
1723 }
1724
1725 uint32_t
Width()1726 HTMLInputElement::Width()
1727 {
1728 if (mType != NS_FORM_INPUT_IMAGE) {
1729 return 0;
1730 }
1731 return GetWidthHeightForImage(mCurrentRequest).width;
1732 }
1733
1734 NS_IMETHODIMP
GetWidth(uint32_t * aWidth)1735 HTMLInputElement::GetWidth(uint32_t* aWidth)
1736 {
1737 *aWidth = Width();
1738 return NS_OK;
1739 }
1740
1741 NS_IMETHODIMP
SetWidth(uint32_t aWidth)1742 HTMLInputElement::SetWidth(uint32_t aWidth)
1743 {
1744 ErrorResult rv;
1745 SetWidth(aWidth, rv);
1746 return rv.StealNSResult();
1747 }
1748
1749 NS_IMETHODIMP
GetValue(nsAString & aValue)1750 HTMLInputElement::GetValue(nsAString& aValue)
1751 {
1752 nsresult rv = GetValueInternal(aValue);
1753 if (NS_FAILED(rv)) {
1754 return rv;
1755 }
1756
1757 // Don't return non-sanitized value for types that are experimental on mobile
1758 // or datetime types
1759 if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
1760 SanitizeValue(aValue);
1761 }
1762
1763 return NS_OK;
1764 }
1765
1766 nsresult
GetValueInternal(nsAString & aValue) const1767 HTMLInputElement::GetValueInternal(nsAString& aValue) const
1768 {
1769 switch (GetValueMode()) {
1770 case VALUE_MODE_VALUE:
1771 if (IsSingleLineTextControl(false)) {
1772 mInputData.mState->GetValue(aValue, true);
1773 } else if (!aValue.Assign(mInputData.mValue, fallible)) {
1774 return NS_ERROR_OUT_OF_MEMORY;
1775 }
1776 return NS_OK;
1777
1778 case VALUE_MODE_FILENAME:
1779 if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
1780 aValue.Assign(mFirstFilePath);
1781 } else {
1782 // Just return the leaf name
1783 if (mFilesOrDirectories.IsEmpty()) {
1784 aValue.Truncate();
1785 } else {
1786 GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue);
1787 }
1788 }
1789
1790 return NS_OK;
1791
1792 case VALUE_MODE_DEFAULT:
1793 // Treat defaultValue as value.
1794 GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue);
1795 return NS_OK;
1796
1797 case VALUE_MODE_DEFAULT_ON:
1798 // Treat default value as value and returns "on" if no value.
1799 if (!GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue)) {
1800 aValue.AssignLiteral("on");
1801 }
1802 return NS_OK;
1803 }
1804
1805 // This return statement is required for some compilers.
1806 return NS_OK;
1807 }
1808
1809 bool
IsValueEmpty() const1810 HTMLInputElement::IsValueEmpty() const
1811 {
1812 nsAutoString value;
1813 GetValueInternal(value);
1814
1815 return value.IsEmpty();
1816 }
1817
1818 void
ClearFiles(bool aSetValueChanged)1819 HTMLInputElement::ClearFiles(bool aSetValueChanged)
1820 {
1821 nsTArray<OwningFileOrDirectory> data;
1822 SetFilesOrDirectories(data, aSetValueChanged);
1823 }
1824
1825 int32_t
MonthsSinceJan1970(uint32_t aYear,uint32_t aMonth) const1826 HTMLInputElement::MonthsSinceJan1970(uint32_t aYear, uint32_t aMonth) const
1827 {
1828 return (aYear - 1970) * 12 + aMonth - 1;
1829 }
1830
1831 /* static */ Decimal
StringToDecimal(const nsAString & aValue)1832 HTMLInputElement::StringToDecimal(const nsAString& aValue)
1833 {
1834 if (!IsASCII(aValue)) {
1835 return Decimal::nan();
1836 }
1837 NS_LossyConvertUTF16toASCII asciiString(aValue);
1838 std::string stdString = asciiString.get();
1839 return Decimal::fromString(stdString);
1840 }
1841
1842 bool
ConvertStringToNumber(nsAString & aValue,Decimal & aResultValue) const1843 HTMLInputElement::ConvertStringToNumber(nsAString& aValue,
1844 Decimal& aResultValue) const
1845 {
1846 MOZ_ASSERT(DoesValueAsNumberApply(),
1847 "ConvertStringToNumber only applies if .valueAsNumber applies");
1848
1849 switch (mType) {
1850 case NS_FORM_INPUT_NUMBER:
1851 case NS_FORM_INPUT_RANGE:
1852 {
1853 aResultValue = StringToDecimal(aValue);
1854 if (!aResultValue.isFinite()) {
1855 return false;
1856 }
1857 return true;
1858 }
1859 case NS_FORM_INPUT_DATE:
1860 {
1861 uint32_t year, month, day;
1862 if (!ParseDate(aValue, &year, &month, &day)) {
1863 return false;
1864 }
1865
1866 JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
1867 if (!time.isValid()) {
1868 return false;
1869 }
1870
1871 aResultValue = Decimal::fromDouble(time.toDouble());
1872 return true;
1873 }
1874 case NS_FORM_INPUT_TIME:
1875 uint32_t milliseconds;
1876 if (!ParseTime(aValue, &milliseconds)) {
1877 return false;
1878 }
1879
1880 aResultValue = Decimal(int32_t(milliseconds));
1881 return true;
1882 case NS_FORM_INPUT_MONTH:
1883 {
1884 uint32_t year, month;
1885 if (!ParseMonth(aValue, &year, &month)) {
1886 return false;
1887 }
1888
1889 if (year < kMinimumYear || year > kMaximumYear) {
1890 return false;
1891 }
1892
1893 // Maximum valid month is 275760-09.
1894 if (year == kMaximumYear && month > kMaximumMonthInMaximumYear) {
1895 return false;
1896 }
1897
1898 int32_t months = MonthsSinceJan1970(year, month);
1899 aResultValue = Decimal(int32_t(months));
1900 return true;
1901 }
1902 case NS_FORM_INPUT_WEEK:
1903 {
1904 uint32_t year, week;
1905 if (!ParseWeek(aValue, &year, &week)) {
1906 return false;
1907 }
1908
1909 if (year < kMinimumYear || year > kMaximumYear) {
1910 return false;
1911 }
1912
1913 // Maximum week is 275760-W37, the week of 275760-09-13.
1914 if (year == kMaximumYear && week > kMaximumWeekInMaximumYear) {
1915 return false;
1916 }
1917
1918 double days = DaysSinceEpochFromWeek(year, week);
1919 aResultValue = Decimal::fromDouble(days * kMsPerDay);
1920 return true;
1921 }
1922 default:
1923 MOZ_ASSERT(false, "Unrecognized input type");
1924 return false;
1925 }
1926 }
1927
1928 Decimal
GetValueAsDecimal() const1929 HTMLInputElement::GetValueAsDecimal() const
1930 {
1931 Decimal decimalValue;
1932 nsAutoString stringValue;
1933
1934 GetValueInternal(stringValue);
1935
1936 return !ConvertStringToNumber(stringValue, decimalValue) ? Decimal::nan()
1937 : decimalValue;
1938 }
1939
1940 void
GetValue(nsAString & aValue,ErrorResult & aRv)1941 HTMLInputElement::GetValue(nsAString& aValue, ErrorResult& aRv)
1942 {
1943 nsresult rv = GetValue(aValue);
1944 if (NS_FAILED(rv)) {
1945 aRv.Throw(rv);
1946 }
1947 }
1948
1949 void
SetValue(const nsAString & aValue,ErrorResult & aRv)1950 HTMLInputElement::SetValue(const nsAString& aValue, ErrorResult& aRv)
1951 {
1952 // check security. Note that setting the value to the empty string is always
1953 // OK and gives pages a way to clear a file input if necessary.
1954 if (mType == NS_FORM_INPUT_FILE) {
1955 if (!aValue.IsEmpty()) {
1956 if (!nsContentUtils::IsCallerChrome()) {
1957 // setting the value of a "FILE" input widget requires
1958 // chrome privilege
1959 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
1960 return;
1961 }
1962 Sequence<nsString> list;
1963 if (!list.AppendElement(aValue, fallible)) {
1964 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1965 return;
1966 }
1967
1968 MozSetFileNameArray(list, aRv);
1969 return;
1970 }
1971 else {
1972 ClearFiles(true);
1973 }
1974 }
1975 else {
1976 if (MayFireChangeOnBlur()) {
1977 // If the value has been set by a script, we basically want to keep the
1978 // current change event state. If the element is ready to fire a change
1979 // event, we should keep it that way. Otherwise, we should make sure the
1980 // element will not fire any event because of the script interaction.
1981 //
1982 // NOTE: this is currently quite expensive work (too much string
1983 // manipulation). We should probably optimize that.
1984 nsAutoString currentValue;
1985 GetValue(currentValue);
1986
1987 nsresult rv =
1988 SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent |
1989 nsTextEditorState::eSetValue_Notify);
1990 if (NS_FAILED(rv)) {
1991 aRv.Throw(rv);
1992 return;
1993 }
1994
1995 if (mFocusedValue.Equals(currentValue)) {
1996 GetValue(mFocusedValue);
1997 }
1998 } else {
1999 nsresult rv =
2000 SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent |
2001 nsTextEditorState::eSetValue_Notify);
2002 if (NS_FAILED(rv)) {
2003 aRv.Throw(rv);
2004 return;
2005 }
2006 }
2007 }
2008 }
2009
2010 NS_IMETHODIMP
SetValue(const nsAString & aValue)2011 HTMLInputElement::SetValue(const nsAString& aValue)
2012 {
2013 ErrorResult rv;
2014 SetValue(aValue, rv);
2015 return rv.StealNSResult();
2016 }
2017
2018 nsGenericHTMLElement*
GetList() const2019 HTMLInputElement::GetList() const
2020 {
2021 nsAutoString dataListId;
2022 GetAttr(kNameSpaceID_None, nsGkAtoms::list, dataListId);
2023 if (dataListId.IsEmpty()) {
2024 return nullptr;
2025 }
2026
2027 //XXXsmaug How should this all work in case input element is in Shadow DOM.
2028 nsIDocument* doc = GetUncomposedDoc();
2029 if (!doc) {
2030 return nullptr;
2031 }
2032
2033 Element* element = doc->GetElementById(dataListId);
2034 if (!element || !element->IsHTMLElement(nsGkAtoms::datalist)) {
2035 return nullptr;
2036 }
2037
2038 return static_cast<nsGenericHTMLElement*>(element);
2039 }
2040
2041 NS_IMETHODIMP
GetList(nsIDOMHTMLElement ** aValue)2042 HTMLInputElement::GetList(nsIDOMHTMLElement** aValue)
2043 {
2044 *aValue = nullptr;
2045
2046 RefPtr<nsGenericHTMLElement> element = GetList();
2047 if (!element) {
2048 return NS_OK;
2049 }
2050
2051 element.forget(aValue);
2052 return NS_OK;
2053 }
2054
2055 void
SetValue(Decimal aValue)2056 HTMLInputElement::SetValue(Decimal aValue)
2057 {
2058 MOZ_ASSERT(!aValue.isInfinity(), "aValue must not be Infinity!");
2059
2060 if (aValue.isNaN()) {
2061 SetValue(EmptyString());
2062 return;
2063 }
2064
2065 nsAutoString value;
2066 ConvertNumberToString(aValue, value);
2067 SetValue(value);
2068 }
2069
2070 bool
ConvertNumberToString(Decimal aValue,nsAString & aResultString) const2071 HTMLInputElement::ConvertNumberToString(Decimal aValue,
2072 nsAString& aResultString) const
2073 {
2074 MOZ_ASSERT(DoesValueAsNumberApply(),
2075 "ConvertNumberToString is only implemented for types implementing .valueAsNumber");
2076 MOZ_ASSERT(aValue.isFinite(),
2077 "aValue must be a valid non-Infinite number.");
2078
2079 aResultString.Truncate();
2080
2081 switch (mType) {
2082 case NS_FORM_INPUT_NUMBER:
2083 case NS_FORM_INPUT_RANGE:
2084 {
2085 char buf[32];
2086 bool ok = aValue.toString(buf, ArrayLength(buf));
2087 aResultString.AssignASCII(buf);
2088 MOZ_ASSERT(ok, "buf not big enough");
2089 return ok;
2090 }
2091 case NS_FORM_INPUT_DATE:
2092 {
2093 // The specs (and our JS APIs) require |aValue| to be truncated.
2094 aValue = aValue.floor();
2095
2096 double year = JS::YearFromTime(aValue.toDouble());
2097 double month = JS::MonthFromTime(aValue.toDouble());
2098 double day = JS::DayFromTime(aValue.toDouble());
2099
2100 if (IsNaN(year) || IsNaN(month) || IsNaN(day)) {
2101 return false;
2102 }
2103
2104 aResultString.AppendPrintf("%04.0f-%02.0f-%02.0f", year,
2105 month + 1, day);
2106
2107 return true;
2108 }
2109 case NS_FORM_INPUT_TIME:
2110 {
2111 // Per spec, we need to truncate |aValue| and we should only represent
2112 // times inside a day [00:00, 24:00[, which means that we should do a
2113 // modulo on |aValue| using the number of milliseconds in a day (86400000).
2114 uint32_t value = NS_floorModulo(aValue.floor(), Decimal(86400000)).toDouble();
2115
2116 uint16_t milliseconds = value % 1000;
2117 value /= 1000;
2118
2119 uint8_t seconds = value % 60;
2120 value /= 60;
2121
2122 uint8_t minutes = value % 60;
2123 value /= 60;
2124
2125 uint8_t hours = value;
2126
2127 if (milliseconds != 0) {
2128 aResultString.AppendPrintf("%02d:%02d:%02d.%03d",
2129 hours, minutes, seconds, milliseconds);
2130 } else if (seconds != 0) {
2131 aResultString.AppendPrintf("%02d:%02d:%02d",
2132 hours, minutes, seconds);
2133 } else {
2134 aResultString.AppendPrintf("%02d:%02d", hours, minutes);
2135 }
2136
2137 return true;
2138 }
2139 case NS_FORM_INPUT_MONTH:
2140 {
2141 aValue = aValue.floor();
2142
2143 double month = NS_floorModulo(aValue, Decimal(12)).toDouble();
2144 month = (month < 0 ? month + 12 : month);
2145
2146 double year = 1970 + (aValue.toDouble() - month) / 12;
2147
2148 // Maximum valid month is 275760-09.
2149 if (year < kMinimumYear || year > kMaximumYear) {
2150 return false;
2151 }
2152
2153 if (year == kMaximumYear && month > 8) {
2154 return false;
2155 }
2156
2157 aResultString.AppendPrintf("%04.0f-%02.0f", year, month + 1);
2158 return true;
2159 }
2160 case NS_FORM_INPUT_WEEK:
2161 {
2162 aValue = aValue.floor();
2163
2164 // Based on ISO 8601 date.
2165 double year = JS::YearFromTime(aValue.toDouble());
2166 double month = JS::MonthFromTime(aValue.toDouble());
2167 double day = JS::DayFromTime(aValue.toDouble());
2168 // Adding 1 since day starts from 0.
2169 double dayInYear = JS::DayWithinYear(aValue.toDouble(), year) + 1;
2170
2171 // Adding 1 since month starts from 0.
2172 uint32_t isoWeekday = DayOfWeek(year, month + 1, day, true);
2173 // Target on Wednesday since ISO 8601 states that week 1 is the week
2174 // with the first Thursday of that year.
2175 uint32_t week = (dayInYear - isoWeekday + 10) / 7;
2176
2177 if (week < 1) {
2178 year--;
2179 if (year < 1) {
2180 return false;
2181 }
2182 week = MaximumWeekInYear(year);
2183 } else if (week > MaximumWeekInYear(year)) {
2184 year++;
2185 if (year > kMaximumYear ||
2186 (year == kMaximumYear && week > kMaximumWeekInMaximumYear)) {
2187 return false;
2188 }
2189 week = 1;
2190 }
2191
2192 aResultString.AppendPrintf("%04.0f-W%02d", year, week);
2193 return true;
2194 }
2195 default:
2196 MOZ_ASSERT(false, "Unrecognized input type");
2197 return false;
2198 }
2199 }
2200
2201
2202 Nullable<Date>
GetValueAsDate(ErrorResult & aRv)2203 HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
2204 {
2205 // TODO: this is temporary until bug 888331 is fixed.
2206 if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
2207 return Nullable<Date>();
2208 }
2209
2210 switch (mType) {
2211 case NS_FORM_INPUT_DATE:
2212 {
2213 uint32_t year, month, day;
2214 nsAutoString value;
2215 GetValueInternal(value);
2216 if (!ParseDate(value, &year, &month, &day)) {
2217 return Nullable<Date>();
2218 }
2219
2220 JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
2221 return Nullable<Date>(Date(time));
2222 }
2223 case NS_FORM_INPUT_TIME:
2224 {
2225 uint32_t millisecond;
2226 nsAutoString value;
2227 GetValueInternal(value);
2228 if (!ParseTime(value, &millisecond)) {
2229 return Nullable<Date>();
2230 }
2231
2232 JS::ClippedTime time = JS::TimeClip(millisecond);
2233 MOZ_ASSERT(time.toDouble() == millisecond,
2234 "HTML times are restricted to the day after the epoch and "
2235 "never clip");
2236 return Nullable<Date>(Date(time));
2237 }
2238 case NS_FORM_INPUT_MONTH:
2239 {
2240 uint32_t year, month;
2241 nsAutoString value;
2242 GetValueInternal(value);
2243 if (!ParseMonth(value, &year, &month)) {
2244 return Nullable<Date>();
2245 }
2246
2247 JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, 1));
2248 return Nullable<Date>(Date(time));
2249 }
2250 case NS_FORM_INPUT_WEEK:
2251 {
2252 uint32_t year, week;
2253 nsAutoString value;
2254 GetValueInternal(value);
2255 if (!ParseWeek(value, &year, &week)) {
2256 return Nullable<Date>();
2257 }
2258
2259 double days = DaysSinceEpochFromWeek(year, week);
2260 JS::ClippedTime time = JS::TimeClip(days * kMsPerDay);
2261
2262 return Nullable<Date>(Date(time));
2263 }
2264 }
2265
2266 MOZ_ASSERT(false, "Unrecognized input type");
2267 aRv.Throw(NS_ERROR_UNEXPECTED);
2268 return Nullable<Date>();
2269 }
2270
2271 void
SetValueAsDate(Nullable<Date> aDate,ErrorResult & aRv)2272 HTMLInputElement::SetValueAsDate(Nullable<Date> aDate, ErrorResult& aRv)
2273 {
2274 // TODO: this is temporary until bug 888331 is fixed.
2275 if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
2276 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2277 return;
2278 }
2279
2280 if (aDate.IsNull() || aDate.Value().IsUndefined()) {
2281 aRv = SetValue(EmptyString());
2282 return;
2283 }
2284
2285 double milliseconds = aDate.Value().TimeStamp().toDouble();
2286
2287 if (mType != NS_FORM_INPUT_MONTH) {
2288 SetValue(Decimal::fromDouble(milliseconds));
2289 return;
2290 }
2291
2292 // type=month expects the value to be number of months.
2293 double year = JS::YearFromTime(milliseconds);
2294 double month = JS::MonthFromTime(milliseconds);
2295
2296 if (IsNaN(year) || IsNaN(month)) {
2297 SetValue(EmptyString());
2298 return;
2299 }
2300
2301 int32_t months = MonthsSinceJan1970(year, month + 1);
2302 SetValue(Decimal(int32_t(months)));
2303 }
2304
2305 NS_IMETHODIMP
GetValueAsNumber(double * aValueAsNumber)2306 HTMLInputElement::GetValueAsNumber(double* aValueAsNumber)
2307 {
2308 *aValueAsNumber = ValueAsNumber();
2309 return NS_OK;
2310 }
2311
2312 void
SetValueAsNumber(double aValueAsNumber,ErrorResult & aRv)2313 HTMLInputElement::SetValueAsNumber(double aValueAsNumber, ErrorResult& aRv)
2314 {
2315 // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
2316 // bug 825197.
2317 if (IsInfinite(aValueAsNumber)) {
2318 aRv.Throw(NS_ERROR_INVALID_ARG);
2319 return;
2320 }
2321
2322 if (!DoesValueAsNumberApply()) {
2323 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
2324 return;
2325 }
2326
2327 SetValue(Decimal::fromDouble(aValueAsNumber));
2328 }
2329
2330 NS_IMETHODIMP
SetValueAsNumber(double aValueAsNumber)2331 HTMLInputElement::SetValueAsNumber(double aValueAsNumber)
2332 {
2333 ErrorResult rv;
2334 SetValueAsNumber(aValueAsNumber, rv);
2335 return rv.StealNSResult();
2336 }
2337
2338 Decimal
GetMinimum() const2339 HTMLInputElement::GetMinimum() const
2340 {
2341 MOZ_ASSERT(DoesValueAsNumberApply(),
2342 "GetMinimum() should only be used for types that allow .valueAsNumber");
2343
2344 // Only type=range has a default minimum
2345 Decimal defaultMinimum =
2346 mType == NS_FORM_INPUT_RANGE ? Decimal(0) : Decimal::nan();
2347
2348 if (!HasAttr(kNameSpaceID_None, nsGkAtoms::min)) {
2349 return defaultMinimum;
2350 }
2351
2352 nsAutoString minStr;
2353 GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
2354
2355 Decimal min;
2356 return ConvertStringToNumber(minStr, min) ? min : defaultMinimum;
2357 }
2358
2359 Decimal
GetMaximum() const2360 HTMLInputElement::GetMaximum() const
2361 {
2362 MOZ_ASSERT(DoesValueAsNumberApply(),
2363 "GetMaximum() should only be used for types that allow .valueAsNumber");
2364
2365 // Only type=range has a default maximum
2366 Decimal defaultMaximum =
2367 mType == NS_FORM_INPUT_RANGE ? Decimal(100) : Decimal::nan();
2368
2369 if (!HasAttr(kNameSpaceID_None, nsGkAtoms::max)) {
2370 return defaultMaximum;
2371 }
2372
2373 nsAutoString maxStr;
2374 GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
2375
2376 Decimal max;
2377 return ConvertStringToNumber(maxStr, max) ? max : defaultMaximum;
2378 }
2379
2380 Decimal
GetStepBase() const2381 HTMLInputElement::GetStepBase() const
2382 {
2383 MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER ||
2384 mType == NS_FORM_INPUT_DATE ||
2385 mType == NS_FORM_INPUT_TIME ||
2386 mType == NS_FORM_INPUT_MONTH ||
2387 mType == NS_FORM_INPUT_WEEK ||
2388 mType == NS_FORM_INPUT_RANGE,
2389 "Check that kDefaultStepBase is correct for this new type");
2390
2391 Decimal stepBase;
2392
2393 // Do NOT use GetMinimum here - the spec says to use "the min content
2394 // attribute", not "the minimum".
2395 nsAutoString minStr;
2396 if (GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr) &&
2397 ConvertStringToNumber(minStr, stepBase)) {
2398 return stepBase;
2399 }
2400
2401 // If @min is not a double, we should use @value.
2402 nsAutoString valueStr;
2403 if (GetAttr(kNameSpaceID_None, nsGkAtoms::value, valueStr) &&
2404 ConvertStringToNumber(valueStr, stepBase)) {
2405 return stepBase;
2406 }
2407
2408 if (mType == NS_FORM_INPUT_WEEK) {
2409 return kDefaultStepBaseWeek;
2410 }
2411
2412 return kDefaultStepBase;
2413 }
2414
2415 nsresult
GetValueIfStepped(int32_t aStep,StepCallerType aCallerType,Decimal * aNextStep)2416 HTMLInputElement::GetValueIfStepped(int32_t aStep,
2417 StepCallerType aCallerType,
2418 Decimal* aNextStep)
2419 {
2420 if (!DoStepDownStepUpApply()) {
2421 return NS_ERROR_DOM_INVALID_STATE_ERR;
2422 }
2423
2424 Decimal stepBase = GetStepBase();
2425 Decimal step = GetStep();
2426 if (step == kStepAny) {
2427 if (aCallerType != CALLED_FOR_USER_EVENT) {
2428 return NS_ERROR_DOM_INVALID_STATE_ERR;
2429 }
2430 // Allow the spin buttons and up/down arrow keys to do something sensible:
2431 step = GetDefaultStep();
2432 }
2433
2434 Decimal minimum = GetMinimum();
2435 Decimal maximum = GetMaximum();
2436
2437 if (!maximum.isNaN()) {
2438 // "max - (max - stepBase) % step" is the nearest valid value to max.
2439 maximum = maximum - NS_floorModulo(maximum - stepBase, step);
2440 if (!minimum.isNaN()) {
2441 if (minimum > maximum) {
2442 // Either the minimum was greater than the maximum prior to our
2443 // adjustment to align maximum on a step, or else (if we adjusted
2444 // maximum) there is no valid step between minimum and the unadjusted
2445 // maximum.
2446 return NS_OK;
2447 }
2448 }
2449 }
2450
2451 Decimal value = GetValueAsDecimal();
2452 bool valueWasNaN = false;
2453 if (value.isNaN()) {
2454 value = Decimal(0);
2455 valueWasNaN = true;
2456 }
2457 Decimal valueBeforeStepping = value;
2458
2459 Decimal deltaFromStep = NS_floorModulo(value - stepBase, step);
2460
2461 if (deltaFromStep != Decimal(0)) {
2462 if (aStep > 0) {
2463 value += step - deltaFromStep; // partial step
2464 value += step * Decimal(aStep - 1); // then remaining steps
2465 } else if (aStep < 0) {
2466 value -= deltaFromStep; // partial step
2467 value += step * Decimal(aStep + 1); // then remaining steps
2468 }
2469 } else {
2470 value += step * Decimal(aStep);
2471 }
2472
2473 if (value < minimum) {
2474 value = minimum;
2475 deltaFromStep = NS_floorModulo(value - stepBase, step);
2476 if (deltaFromStep != Decimal(0)) {
2477 value += step - deltaFromStep;
2478 }
2479 }
2480 if (value > maximum) {
2481 value = maximum;
2482 deltaFromStep = NS_floorModulo(value - stepBase, step);
2483 if (deltaFromStep != Decimal(0)) {
2484 value -= deltaFromStep;
2485 }
2486 }
2487
2488 if (!valueWasNaN && // value="", resulting in us using "0"
2489 ((aStep > 0 && value < valueBeforeStepping) ||
2490 (aStep < 0 && value > valueBeforeStepping))) {
2491 // We don't want step-up to effectively step down, or step-down to
2492 // effectively step up, so return;
2493 return NS_OK;
2494 }
2495
2496 *aNextStep = value;
2497 return NS_OK;
2498 }
2499
2500 nsresult
ApplyStep(int32_t aStep)2501 HTMLInputElement::ApplyStep(int32_t aStep)
2502 {
2503 Decimal nextStep = Decimal::nan(); // unchanged if value will not change
2504
2505 nsresult rv = GetValueIfStepped(aStep, CALLED_FOR_SCRIPT, &nextStep);
2506
2507 if (NS_SUCCEEDED(rv) && nextStep.isFinite()) {
2508 SetValue(nextStep);
2509 }
2510
2511 return rv;
2512 }
2513
2514 /* static */
2515 bool
IsExperimentalMobileType(uint8_t aType)2516 HTMLInputElement::IsExperimentalMobileType(uint8_t aType)
2517 {
2518 return (aType == NS_FORM_INPUT_DATE &&
2519 !Preferences::GetBool("dom.forms.datetime", false) &&
2520 !Preferences::GetBool("dom.forms.datepicker", false)) ||
2521 (aType == NS_FORM_INPUT_TIME &&
2522 !Preferences::GetBool("dom.forms.datetime", false));
2523 }
2524
2525 bool
IsDateTimeInputType(uint8_t aType)2526 HTMLInputElement::IsDateTimeInputType(uint8_t aType)
2527 {
2528 return aType == NS_FORM_INPUT_DATE ||
2529 aType == NS_FORM_INPUT_TIME ||
2530 aType == NS_FORM_INPUT_MONTH ||
2531 aType == NS_FORM_INPUT_WEEK ||
2532 aType == NS_FORM_INPUT_DATETIME_LOCAL;
2533 }
2534
2535 NS_IMETHODIMP
StepDown(int32_t n,uint8_t optional_argc)2536 HTMLInputElement::StepDown(int32_t n, uint8_t optional_argc)
2537 {
2538 return ApplyStep(optional_argc ? -n : -1);
2539 }
2540
2541 NS_IMETHODIMP
StepUp(int32_t n,uint8_t optional_argc)2542 HTMLInputElement::StepUp(int32_t n, uint8_t optional_argc)
2543 {
2544 return ApplyStep(optional_argc ? n : 1);
2545 }
2546
2547 void
FlushFrames()2548 HTMLInputElement::FlushFrames()
2549 {
2550 if (GetComposedDoc()) {
2551 GetComposedDoc()->FlushPendingNotifications(Flush_Frames);
2552 }
2553 }
2554
2555 void
MozGetFileNameArray(nsTArray<nsString> & aArray,ErrorResult & aRv)2556 HTMLInputElement::MozGetFileNameArray(nsTArray<nsString>& aArray,
2557 ErrorResult& aRv)
2558 {
2559 for (uint32_t i = 0; i < mFilesOrDirectories.Length(); i++) {
2560 nsAutoString str;
2561 GetDOMFileOrDirectoryPath(mFilesOrDirectories[i], str, aRv);
2562 if (NS_WARN_IF(aRv.Failed())) {
2563 return;
2564 }
2565
2566 aArray.AppendElement(str);
2567 }
2568 }
2569
2570
2571 NS_IMETHODIMP
MozGetFileNameArray(uint32_t * aLength,char16_t *** aFileNames)2572 HTMLInputElement::MozGetFileNameArray(uint32_t* aLength, char16_t*** aFileNames)
2573 {
2574 if (!nsContentUtils::IsCallerChrome()) {
2575 // Since this function returns full paths it's important that normal pages
2576 // can't call it.
2577 return NS_ERROR_DOM_SECURITY_ERR;
2578 }
2579
2580 ErrorResult rv;
2581 nsTArray<nsString> array;
2582 MozGetFileNameArray(array, rv);
2583 if (NS_WARN_IF(rv.Failed())) {
2584 return rv.StealNSResult();
2585 }
2586
2587 *aLength = array.Length();
2588 char16_t** ret =
2589 static_cast<char16_t**>(moz_xmalloc(*aLength * sizeof(char16_t*)));
2590 if (!ret) {
2591 return NS_ERROR_OUT_OF_MEMORY;
2592 }
2593
2594 for (uint32_t i = 0; i < *aLength; ++i) {
2595 ret[i] = NS_strdup(array[i].get());
2596 }
2597
2598 *aFileNames = ret;
2599
2600 return NS_OK;
2601 }
2602
2603 void
MozSetFileArray(const Sequence<OwningNonNull<File>> & aFiles)2604 HTMLInputElement::MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles)
2605 {
2606 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2607 MOZ_ASSERT(global);
2608 if (!global) {
2609 return;
2610 }
2611
2612 nsTArray<OwningFileOrDirectory> files;
2613 for (uint32_t i = 0; i < aFiles.Length(); ++i) {
2614 RefPtr<File> file = File::Create(global, aFiles[i].get()->Impl());
2615 MOZ_ASSERT(file);
2616
2617 OwningFileOrDirectory* element = files.AppendElement();
2618 element->SetAsFile() = file;
2619 }
2620
2621 SetFilesOrDirectories(files, true);
2622 }
2623
2624 void
MozSetFileNameArray(const Sequence<nsString> & aFileNames,ErrorResult & aRv)2625 HTMLInputElement::MozSetFileNameArray(const Sequence<nsString>& aFileNames,
2626 ErrorResult& aRv)
2627 {
2628 if (XRE_IsContentProcess()) {
2629 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
2630 return;
2631 }
2632
2633 nsTArray<OwningFileOrDirectory> files;
2634 for (uint32_t i = 0; i < aFileNames.Length(); ++i) {
2635 nsCOMPtr<nsIFile> file;
2636
2637 if (StringBeginsWith(aFileNames[i], NS_LITERAL_STRING("file:"),
2638 nsASCIICaseInsensitiveStringComparator())) {
2639 // Converts the URL string into the corresponding nsIFile if possible
2640 // A local file will be created if the URL string begins with file://
2641 NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames[i]),
2642 getter_AddRefs(file));
2643 }
2644
2645 if (!file) {
2646 // this is no "file://", try as local file
2647 NS_NewLocalFile(aFileNames[i], false, getter_AddRefs(file));
2648 }
2649
2650 if (!file) {
2651 continue; // Not much we can do if the file doesn't exist
2652 }
2653
2654 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2655 if (!global) {
2656 aRv.Throw(NS_ERROR_FAILURE);
2657 return;
2658 }
2659
2660 RefPtr<File> domFile = File::CreateFromFile(global, file);
2661
2662 OwningFileOrDirectory* element = files.AppendElement();
2663 element->SetAsFile() = domFile;
2664 }
2665
2666 SetFilesOrDirectories(files, true);
2667 }
2668
2669 NS_IMETHODIMP
MozSetFileNameArray(const char16_t ** aFileNames,uint32_t aLength)2670 HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames,
2671 uint32_t aLength)
2672 {
2673 if (!nsContentUtils::IsCallerChrome()) {
2674 // setting the value of a "FILE" input widget requires chrome privilege
2675 return NS_ERROR_DOM_SECURITY_ERR;
2676 }
2677
2678 Sequence<nsString> list;
2679 nsString* names = list.AppendElements(aLength, fallible);
2680 if (!names) {
2681 return NS_ERROR_OUT_OF_MEMORY;
2682 }
2683 for (uint32_t i = 0; i < aLength; ++i) {
2684 const char16_t* filename = aFileNames[i];
2685 names[i].Rebind(filename, nsCharTraits<char16_t>::length(filename));
2686 }
2687
2688 ErrorResult rv;
2689 MozSetFileNameArray(list, rv);
2690 return rv.StealNSResult();
2691 }
2692
2693 void
MozSetDirectory(const nsAString & aDirectoryPath,ErrorResult & aRv)2694 HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
2695 ErrorResult& aRv)
2696 {
2697 nsCOMPtr<nsIFile> file;
2698 aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file));
2699 if (NS_WARN_IF(aRv.Failed())) {
2700 return;
2701 }
2702
2703 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
2704 if (NS_WARN_IF(!window)) {
2705 aRv.Throw(NS_ERROR_FAILURE);
2706 return;
2707 }
2708
2709 RefPtr<Directory> directory = Directory::Create(window, file);
2710 MOZ_ASSERT(directory);
2711
2712 nsTArray<OwningFileOrDirectory> array;
2713 OwningFileOrDirectory* element = array.AppendElement();
2714 element->SetAsDirectory() = directory;
2715
2716 SetFilesOrDirectories(array, true);
2717 }
2718
GetDateTimeInputBoxValue(DateTimeValue & aValue)2719 void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue)
2720 {
2721 if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
2722 return;
2723 }
2724
2725 aValue = *mDateTimeInputBoxValue;
2726 }
2727
2728 void
UpdateDateTimeInputBox(const DateTimeValue & aValue)2729 HTMLInputElement::UpdateDateTimeInputBox(const DateTimeValue& aValue)
2730 {
2731 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2732 return;
2733 }
2734
2735 nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
2736 if (frame) {
2737 frame->SetValueFromPicker(aValue);
2738 }
2739 }
2740
2741 void
SetDateTimePickerState(bool aOpen)2742 HTMLInputElement::SetDateTimePickerState(bool aOpen)
2743 {
2744 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2745 return;
2746 }
2747
2748 nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
2749 if (frame) {
2750 frame->SetPickerState(aOpen);
2751 }
2752 }
2753
2754 void
OpenDateTimePicker(const DateTimeValue & aInitialValue)2755 HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue)
2756 {
2757 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2758 return;
2759 }
2760
2761 mDateTimeInputBoxValue = new DateTimeValue(aInitialValue);
2762 nsContentUtils::DispatchChromeEvent(OwnerDoc(),
2763 static_cast<nsIDOMHTMLInputElement*>(this),
2764 NS_LITERAL_STRING("MozOpenDateTimePicker"),
2765 true, true);
2766 }
2767
2768 void
UpdateDateTimePicker(const DateTimeValue & aValue)2769 HTMLInputElement::UpdateDateTimePicker(const DateTimeValue& aValue)
2770 {
2771 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2772 return;
2773 }
2774
2775 mDateTimeInputBoxValue = new DateTimeValue(aValue);
2776 nsContentUtils::DispatchChromeEvent(OwnerDoc(),
2777 static_cast<nsIDOMHTMLInputElement*>(this),
2778 NS_LITERAL_STRING("MozUpdateDateTimePicker"),
2779 true, true);
2780 }
2781
2782 void
CloseDateTimePicker()2783 HTMLInputElement::CloseDateTimePicker()
2784 {
2785 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2786 return;
2787 }
2788
2789 nsContentUtils::DispatchChromeEvent(OwnerDoc(),
2790 static_cast<nsIDOMHTMLInputElement*>(this),
2791 NS_LITERAL_STRING("MozCloseDateTimePicker"),
2792 true, true);
2793 }
2794
2795 bool
MozIsTextField(bool aExcludePassword)2796 HTMLInputElement::MozIsTextField(bool aExcludePassword)
2797 {
2798 // TODO: temporary until bug 888320 is fixed.
2799 if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
2800 return false;
2801 }
2802
2803 return IsSingleLineTextControl(aExcludePassword);
2804 }
2805
2806 HTMLInputElement*
GetOwnerNumberControl()2807 HTMLInputElement::GetOwnerNumberControl()
2808 {
2809 if (IsInNativeAnonymousSubtree() &&
2810 mType == NS_FORM_INPUT_TEXT &&
2811 GetParent() && GetParent()->GetParent()) {
2812 HTMLInputElement* grandparent =
2813 HTMLInputElement::FromContentOrNull(GetParent()->GetParent());
2814 if (grandparent && grandparent->mType == NS_FORM_INPUT_NUMBER) {
2815 return grandparent;
2816 }
2817 }
2818 return nullptr;
2819 }
2820
2821 HTMLInputElement*
GetOwnerDateTimeControl()2822 HTMLInputElement::GetOwnerDateTimeControl()
2823 {
2824 if (IsInNativeAnonymousSubtree() &&
2825 mType == NS_FORM_INPUT_TEXT &&
2826 GetParent() &&
2827 GetParent()->GetParent() &&
2828 GetParent()->GetParent()->GetParent() &&
2829 GetParent()->GetParent()->GetParent()->GetParent()) {
2830 // Yes, this is very very deep.
2831 HTMLInputElement* ownerDateTimeControl =
2832 HTMLInputElement::FromContentOrNull(
2833 GetParent()->GetParent()->GetParent()->GetParent());
2834 if (ownerDateTimeControl &&
2835 ownerDateTimeControl->mType == NS_FORM_INPUT_TIME) {
2836 return ownerDateTimeControl;
2837 }
2838 }
2839 return nullptr;
2840 }
2841
2842
2843 NS_IMETHODIMP
MozIsTextField(bool aExcludePassword,bool * aResult)2844 HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
2845 {
2846 *aResult = MozIsTextField(aExcludePassword);
2847 return NS_OK;
2848 }
2849
2850 void
SetUserInput(const nsAString & aInput,nsIPrincipal & aSubjectPrincipal)2851 HTMLInputElement::SetUserInput(const nsAString& aInput,
2852 nsIPrincipal& aSubjectPrincipal) {
2853 if (mType == NS_FORM_INPUT_FILE &&
2854 !nsContentUtils::IsSystemPrincipal(&aSubjectPrincipal)) {
2855 return;
2856 }
2857
2858 SetUserInput(aInput);
2859 }
2860
2861 NS_IMETHODIMP
SetUserInput(const nsAString & aValue)2862 HTMLInputElement::SetUserInput(const nsAString& aValue)
2863 {
2864 if (mType == NS_FORM_INPUT_FILE)
2865 {
2866 Sequence<nsString> list;
2867 if (!list.AppendElement(aValue, fallible)) {
2868 return NS_ERROR_OUT_OF_MEMORY;
2869 }
2870
2871 ErrorResult rv;
2872 MozSetFileNameArray(list, rv);
2873 return rv.StealNSResult();
2874 } else {
2875 nsresult rv =
2876 SetValueInternal(aValue, nsTextEditorState::eSetValue_BySetUserInput |
2877 nsTextEditorState::eSetValue_Notify);
2878 NS_ENSURE_SUCCESS(rv, rv);
2879 }
2880
2881 nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
2882 static_cast<nsIDOMHTMLInputElement*>(this),
2883 NS_LITERAL_STRING("input"), true,
2884 true);
2885
2886 // If this element is not currently focused, it won't receive a change event for this
2887 // update through the normal channels. So fire a change event immediately, instead.
2888 if (!ShouldBlur(this)) {
2889 FireChangeEventIfNeeded();
2890 }
2891
2892 return NS_OK;
2893 }
2894
2895 nsIEditor*
GetEditor()2896 HTMLInputElement::GetEditor()
2897 {
2898 nsTextEditorState* state = GetEditorState();
2899 if (state) {
2900 return state->GetEditor();
2901 }
2902 return nullptr;
2903 }
2904
NS_IMETHODIMP_(nsIEditor *)2905 NS_IMETHODIMP_(nsIEditor*)
2906 HTMLInputElement::GetTextEditor()
2907 {
2908 return GetEditor();
2909 }
2910
NS_IMETHODIMP_(nsISelectionController *)2911 NS_IMETHODIMP_(nsISelectionController*)
2912 HTMLInputElement::GetSelectionController()
2913 {
2914 nsTextEditorState* state = GetEditorState();
2915 if (state) {
2916 return state->GetSelectionController();
2917 }
2918 return nullptr;
2919 }
2920
2921 nsFrameSelection*
GetConstFrameSelection()2922 HTMLInputElement::GetConstFrameSelection()
2923 {
2924 nsTextEditorState* state = GetEditorState();
2925 if (state) {
2926 return state->GetConstFrameSelection();
2927 }
2928 return nullptr;
2929 }
2930
2931 NS_IMETHODIMP
BindToFrame(nsTextControlFrame * aFrame)2932 HTMLInputElement::BindToFrame(nsTextControlFrame* aFrame)
2933 {
2934 nsTextEditorState* state = GetEditorState();
2935 if (state) {
2936 return state->BindToFrame(aFrame);
2937 }
2938 return NS_ERROR_FAILURE;
2939 }
2940
NS_IMETHODIMP_(void)2941 NS_IMETHODIMP_(void)
2942 HTMLInputElement::UnbindFromFrame(nsTextControlFrame* aFrame)
2943 {
2944 nsTextEditorState* state = GetEditorState();
2945 if (state && aFrame) {
2946 state->UnbindFromFrame(aFrame);
2947 }
2948 }
2949
2950 NS_IMETHODIMP
CreateEditor()2951 HTMLInputElement::CreateEditor()
2952 {
2953 nsTextEditorState* state = GetEditorState();
2954 if (state) {
2955 return state->PrepareEditor();
2956 }
2957 return NS_ERROR_FAILURE;
2958 }
2959
NS_IMETHODIMP_(nsIContent *)2960 NS_IMETHODIMP_(nsIContent*)
2961 HTMLInputElement::GetRootEditorNode()
2962 {
2963 nsTextEditorState* state = GetEditorState();
2964 if (state) {
2965 return state->GetRootNode();
2966 }
2967 return nullptr;
2968 }
2969
NS_IMETHODIMP_(Element *)2970 NS_IMETHODIMP_(Element*)
2971 HTMLInputElement::CreatePlaceholderNode()
2972 {
2973 nsTextEditorState* state = GetEditorState();
2974 if (state) {
2975 NS_ENSURE_SUCCESS(state->CreatePlaceholderNode(), nullptr);
2976 return state->GetPlaceholderNode();
2977 }
2978 return nullptr;
2979 }
2980
NS_IMETHODIMP_(Element *)2981 NS_IMETHODIMP_(Element*)
2982 HTMLInputElement::GetPlaceholderNode()
2983 {
2984 nsTextEditorState* state = GetEditorState();
2985 if (state) {
2986 return state->GetPlaceholderNode();
2987 }
2988 return nullptr;
2989 }
2990
NS_IMETHODIMP_(void)2991 NS_IMETHODIMP_(void)
2992 HTMLInputElement::UpdatePlaceholderVisibility(bool aNotify)
2993 {
2994 nsTextEditorState* state = GetEditorState();
2995 if (state) {
2996 state->UpdatePlaceholderVisibility(aNotify);
2997 }
2998 }
2999
NS_IMETHODIMP_(bool)3000 NS_IMETHODIMP_(bool)
3001 HTMLInputElement::GetPlaceholderVisibility()
3002 {
3003 nsTextEditorState* state = GetEditorState();
3004 if (!state) {
3005 return false;
3006 }
3007
3008 return state->GetPlaceholderVisibility();
3009 }
3010
3011 void
GetDisplayFileName(nsAString & aValue) const3012 HTMLInputElement::GetDisplayFileName(nsAString& aValue) const
3013 {
3014 if (OwnerDoc()->IsStaticDocument()) {
3015 aValue = mStaticDocFileList;
3016 return;
3017 }
3018
3019 if (mFilesOrDirectories.Length() == 1) {
3020 GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue);
3021 return;
3022 }
3023
3024 nsXPIDLString value;
3025
3026 if (mFilesOrDirectories.IsEmpty()) {
3027 if ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
3028 (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
3029 HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
3030 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
3031 "NoDirSelected", value);
3032 } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
3033 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
3034 "NoFilesSelected", value);
3035 } else {
3036 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
3037 "NoFileSelected", value);
3038 }
3039 } else {
3040 nsString count;
3041 count.AppendInt(int(mFilesOrDirectories.Length()));
3042
3043 const char16_t* params[] = { count.get() };
3044 nsContentUtils::FormatLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
3045 "XFilesSelected", params, value);
3046 }
3047
3048 aValue = value;
3049 }
3050
3051 void
SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory> & aFilesOrDirectories,bool aSetValueChanged)3052 HTMLInputElement::SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
3053 bool aSetValueChanged)
3054 {
3055 ClearGetFilesHelpers();
3056
3057 if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
3058 HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
3059 mEntries.Clear();
3060 }
3061
3062 mFilesOrDirectories.Clear();
3063 mFilesOrDirectories.AppendElements(aFilesOrDirectories);
3064
3065 AfterSetFilesOrDirectories(aSetValueChanged);
3066 }
3067
3068 void
SetFiles(nsIDOMFileList * aFiles,bool aSetValueChanged)3069 HTMLInputElement::SetFiles(nsIDOMFileList* aFiles,
3070 bool aSetValueChanged)
3071 {
3072 RefPtr<FileList> files = static_cast<FileList*>(aFiles);
3073 mFilesOrDirectories.Clear();
3074 ClearGetFilesHelpers();
3075
3076 if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
3077 HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
3078 mEntries.Clear();
3079 }
3080
3081 if (aFiles) {
3082 uint32_t listLength;
3083 aFiles->GetLength(&listLength);
3084 for (uint32_t i = 0; i < listLength; i++) {
3085 OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
3086 element->SetAsFile() = files->Item(i);
3087 }
3088 }
3089
3090 AfterSetFilesOrDirectories(aSetValueChanged);
3091 }
3092
3093 // This method is used for testing only.
3094 void
MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory> & aFilesOrDirectories)3095 HTMLInputElement::MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
3096 {
3097 SetFilesOrDirectories(aFilesOrDirectories, true);
3098
3099 if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
3100 UpdateEntries(aFilesOrDirectories);
3101 }
3102
3103 RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
3104 new DispatchChangeEventCallback(this);
3105
3106 if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
3107 HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
3108 ErrorResult rv;
3109 GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */,
3110 rv);
3111 if (NS_WARN_IF(rv.Failed())) {
3112 rv.SuppressException();
3113 return;
3114 }
3115
3116 helper->AddCallback(dispatchChangeEventCallback);
3117 } else {
3118 dispatchChangeEventCallback->DispatchEvents();
3119 }
3120 }
3121
3122 void
AfterSetFilesOrDirectories(bool aSetValueChanged)3123 HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged)
3124 {
3125 // No need to flush here, if there's no frame at this point we
3126 // don't need to force creation of one just to tell it about this
3127 // new value. We just want the display to update as needed.
3128 nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
3129 if (formControlFrame) {
3130 nsAutoString readableValue;
3131 GetDisplayFileName(readableValue);
3132 formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue);
3133 }
3134
3135 // Grab the full path here for any chrome callers who access our .value via a
3136 // CPOW. This path won't be called from a CPOW meaning the potential sync IPC
3137 // call under GetMozFullPath won't be rejected for not being urgent.
3138 // XXX Protected by the ifndef because the blob code doesn't allow us to send
3139 // this message in b2g.
3140 if (mFilesOrDirectories.IsEmpty()) {
3141 mFirstFilePath.Truncate();
3142 } else {
3143 ErrorResult rv;
3144 GetDOMFileOrDirectoryPath(mFilesOrDirectories[0], mFirstFilePath, rv);
3145 if (NS_WARN_IF(rv.Failed())) {
3146 rv.SuppressException();
3147 }
3148 }
3149
3150 UpdateFileList();
3151
3152 if (aSetValueChanged) {
3153 SetValueChanged(true);
3154 }
3155
3156 UpdateAllValidityStates(true);
3157 }
3158
3159 void
FireChangeEventIfNeeded()3160 HTMLInputElement::FireChangeEventIfNeeded()
3161 {
3162 nsAutoString value;
3163 GetValue(value);
3164
3165 if (!MayFireChangeOnBlur() || mFocusedValue.Equals(value)) {
3166 return;
3167 }
3168
3169 // Dispatch the change event.
3170 mFocusedValue = value;
3171 nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
3172 static_cast<nsIContent*>(this),
3173 NS_LITERAL_STRING("change"), true,
3174 false);
3175 }
3176
3177 FileList*
GetFiles()3178 HTMLInputElement::GetFiles()
3179 {
3180 if (mType != NS_FORM_INPUT_FILE) {
3181 return nullptr;
3182 }
3183
3184 if (Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs() &&
3185 (!Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) ||
3186 !HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
3187 return nullptr;
3188 }
3189
3190 if (!mFileList) {
3191 mFileList = new FileList(static_cast<nsIContent*>(this));
3192 UpdateFileList();
3193 }
3194
3195 return mFileList;
3196 }
3197
3198 /* static */ void
HandleNumberControlSpin(void * aData)3199 HTMLInputElement::HandleNumberControlSpin(void* aData)
3200 {
3201 HTMLInputElement* input = static_cast<HTMLInputElement*>(aData);
3202
3203 NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
3204 "Should have called nsRepeatService::Stop()");
3205
3206 nsNumberControlFrame* numberControlFrame =
3207 do_QueryFrame(input->GetPrimaryFrame());
3208 if (input->mType != NS_FORM_INPUT_NUMBER || !numberControlFrame) {
3209 // Type has changed (and possibly our frame type hasn't been updated yet)
3210 // or else we've lost our frame. Either way, stop the timer and don't do
3211 // anything else.
3212 input->StopNumberControlSpinnerSpin();
3213 } else {
3214 input->StepNumberControlForUserEvent(input->mNumberControlSpinnerSpinsUp ? 1 : -1);
3215 }
3216 }
3217
3218 void
UpdateFileList()3219 HTMLInputElement::UpdateFileList()
3220 {
3221 if (mFileList) {
3222 mFileList->Clear();
3223
3224 const nsTArray<OwningFileOrDirectory>& array =
3225 GetFilesOrDirectoriesInternal();
3226
3227 for (uint32_t i = 0; i < array.Length(); ++i) {
3228 if (array[i].IsFile()) {
3229 mFileList->Append(array[i].GetAsFile());
3230 }
3231 }
3232 }
3233 }
3234
3235 nsresult
SetValueInternal(const nsAString & aValue,uint32_t aFlags)3236 HTMLInputElement::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
3237 {
3238 NS_PRECONDITION(GetValueMode() != VALUE_MODE_FILENAME,
3239 "Don't call SetValueInternal for file inputs");
3240
3241 switch (GetValueMode()) {
3242 case VALUE_MODE_VALUE:
3243 {
3244 // At the moment, only single line text control have to sanitize their value
3245 // Because we have to create a new string for that, we should prevent doing
3246 // it if it's useless.
3247 nsAutoString value(aValue);
3248
3249 if (mDoneCreating) {
3250 SanitizeValue(value);
3251 }
3252 // else DoneCreatingElement calls us again once mDoneCreating is true
3253
3254 bool setValueChanged = !!(aFlags & nsTextEditorState::eSetValue_Notify);
3255 if (setValueChanged) {
3256 SetValueChanged(true);
3257 }
3258
3259 if (IsSingleLineTextControl(false)) {
3260 if (!mInputData.mState->SetValue(value, aFlags)) {
3261 return NS_ERROR_OUT_OF_MEMORY;
3262 }
3263 if (mType == NS_FORM_INPUT_EMAIL) {
3264 UpdateAllValidityStates(!mDoneCreating);
3265 }
3266 } else {
3267 free(mInputData.mValue);
3268 mInputData.mValue = ToNewUnicode(value);
3269 if (setValueChanged) {
3270 SetValueChanged(true);
3271 }
3272 if (mType == NS_FORM_INPUT_NUMBER) {
3273 // This has to happen before OnValueChanged is called because that
3274 // method needs the new value of our frame's anon text control.
3275 nsNumberControlFrame* numberControlFrame =
3276 do_QueryFrame(GetPrimaryFrame());
3277 if (numberControlFrame) {
3278 numberControlFrame->SetValueOfAnonTextControl(value);
3279 }
3280 } else if (mType == NS_FORM_INPUT_RANGE) {
3281 nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
3282 if (frame) {
3283 frame->UpdateForValueChange();
3284 }
3285 } else if (mType == NS_FORM_INPUT_TIME &&
3286 !IsExperimentalMobileType(mType)) {
3287 nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
3288 if (frame) {
3289 frame->UpdateInputBoxValue();
3290 }
3291 }
3292 if (mDoneCreating) {
3293 OnValueChanged(/* aNotify = */ true,
3294 /* aWasInteractiveUserChange = */ false);
3295 }
3296 // else DoneCreatingElement calls us again once mDoneCreating is true
3297 }
3298
3299 if (mType == NS_FORM_INPUT_COLOR) {
3300 // Update color frame, to reflect color changes
3301 nsColorControlFrame* colorControlFrame = do_QueryFrame(GetPrimaryFrame());
3302 if (colorControlFrame) {
3303 colorControlFrame->UpdateColor();
3304 }
3305 }
3306
3307 // This call might be useless in some situations because if the element is
3308 // a single line text control, nsTextEditorState::SetValue will call
3309 // nsHTMLInputElement::OnValueChanged which is going to call UpdateState()
3310 // if the element is focused. This bug 665547.
3311 if (PlaceholderApplies() &&
3312 HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
3313 UpdateState(true);
3314 }
3315
3316 return NS_OK;
3317 }
3318
3319 case VALUE_MODE_DEFAULT:
3320 case VALUE_MODE_DEFAULT_ON:
3321 // If the value of a hidden input was changed, we mark it changed so that we
3322 // will know we need to save / restore the value. Yes, we are overloading
3323 // the meaning of ValueChanged just a teensy bit to save a measly byte of
3324 // storage space in HTMLInputElement. Yes, you are free to make a new flag,
3325 // NEED_TO_SAVE_VALUE, at such time as mBitField becomes a 16-bit value.
3326 if (mType == NS_FORM_INPUT_HIDDEN) {
3327 SetValueChanged(true);
3328 }
3329
3330 // Treat value == defaultValue for other input elements.
3331 return nsGenericHTMLFormElementWithState::SetAttr(kNameSpaceID_None,
3332 nsGkAtoms::value, aValue,
3333 true);
3334
3335 case VALUE_MODE_FILENAME:
3336 return NS_ERROR_UNEXPECTED;
3337 }
3338
3339 // This return statement is required for some compilers.
3340 return NS_OK;
3341 }
3342
3343 NS_IMETHODIMP
SetValueChanged(bool aValueChanged)3344 HTMLInputElement::SetValueChanged(bool aValueChanged)
3345 {
3346 bool valueChangedBefore = mValueChanged;
3347
3348 mValueChanged = aValueChanged;
3349
3350 if (valueChangedBefore != aValueChanged) {
3351 UpdateState(true);
3352 }
3353
3354 return NS_OK;
3355 }
3356
3357 NS_IMETHODIMP
GetChecked(bool * aChecked)3358 HTMLInputElement::GetChecked(bool* aChecked)
3359 {
3360 *aChecked = Checked();
3361 return NS_OK;
3362 }
3363
3364 void
SetCheckedChanged(bool aCheckedChanged)3365 HTMLInputElement::SetCheckedChanged(bool aCheckedChanged)
3366 {
3367 DoSetCheckedChanged(aCheckedChanged, true);
3368 }
3369
3370 void
DoSetCheckedChanged(bool aCheckedChanged,bool aNotify)3371 HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged,
3372 bool aNotify)
3373 {
3374 if (mType == NS_FORM_INPUT_RADIO) {
3375 if (mCheckedChanged != aCheckedChanged) {
3376 nsCOMPtr<nsIRadioVisitor> visitor =
3377 new nsRadioSetCheckedChangedVisitor(aCheckedChanged);
3378 VisitGroup(visitor, aNotify);
3379 }
3380 } else {
3381 SetCheckedChangedInternal(aCheckedChanged);
3382 }
3383 }
3384
3385 void
SetCheckedChangedInternal(bool aCheckedChanged)3386 HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged)
3387 {
3388 bool checkedChangedBefore = mCheckedChanged;
3389
3390 mCheckedChanged = aCheckedChanged;
3391
3392 // This method can't be called when we are not authorized to notify
3393 // so we do not need a aNotify parameter.
3394 if (checkedChangedBefore != aCheckedChanged) {
3395 UpdateState(true);
3396 }
3397 }
3398
3399 NS_IMETHODIMP
SetChecked(bool aChecked)3400 HTMLInputElement::SetChecked(bool aChecked)
3401 {
3402 DoSetChecked(aChecked, true, true);
3403 return NS_OK;
3404 }
3405
3406 void
DoSetChecked(bool aChecked,bool aNotify,bool aSetValueChanged)3407 HTMLInputElement::DoSetChecked(bool aChecked, bool aNotify,
3408 bool aSetValueChanged)
3409 {
3410 // If the user or JS attempts to set checked, whether it actually changes the
3411 // value or not, we say the value was changed so that defaultValue don't
3412 // affect it no more.
3413 if (aSetValueChanged) {
3414 DoSetCheckedChanged(true, aNotify);
3415 }
3416
3417 // Don't do anything if we're not changing whether it's checked (it would
3418 // screw up state actually, especially when you are setting radio button to
3419 // false)
3420 if (mChecked == aChecked) {
3421 return;
3422 }
3423
3424 // Set checked
3425 if (mType != NS_FORM_INPUT_RADIO) {
3426 SetCheckedInternal(aChecked, aNotify);
3427 return;
3428 }
3429
3430 // For radio button, we need to do some extra fun stuff
3431 if (aChecked) {
3432 RadioSetChecked(aNotify);
3433 return;
3434 }
3435
3436 nsIRadioGroupContainer* container = GetRadioGroupContainer();
3437 if (container) {
3438 nsAutoString name;
3439 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
3440 container->SetCurrentRadioButton(name, nullptr);
3441 }
3442 // SetCheckedInternal is going to ask all radios to update their
3443 // validity state. We have to be sure the radio group container knows
3444 // the currently selected radio.
3445 SetCheckedInternal(false, aNotify);
3446 }
3447
3448 void
RadioSetChecked(bool aNotify)3449 HTMLInputElement::RadioSetChecked(bool aNotify)
3450 {
3451 // Find the selected radio button so we can deselect it
3452 nsCOMPtr<nsIDOMHTMLInputElement> currentlySelected = GetSelectedRadioButton();
3453
3454 // Deselect the currently selected radio button
3455 if (currentlySelected) {
3456 // Pass true for the aNotify parameter since the currently selected
3457 // button is already in the document.
3458 static_cast<HTMLInputElement*>(currentlySelected.get())
3459 ->SetCheckedInternal(false, true);
3460 }
3461
3462 // Let the group know that we are now the One True Radio Button
3463 nsIRadioGroupContainer* container = GetRadioGroupContainer();
3464 if (container) {
3465 nsAutoString name;
3466 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
3467 container->SetCurrentRadioButton(name, this);
3468 }
3469
3470 // SetCheckedInternal is going to ask all radios to update their
3471 // validity state.
3472 SetCheckedInternal(true, aNotify);
3473 }
3474
3475 nsIRadioGroupContainer*
GetRadioGroupContainer() const3476 HTMLInputElement::GetRadioGroupContainer() const
3477 {
3478 NS_ASSERTION(mType == NS_FORM_INPUT_RADIO,
3479 "GetRadioGroupContainer should only be called when type='radio'");
3480
3481 nsAutoString name;
3482 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
3483
3484 if (name.IsEmpty()) {
3485 return nullptr;
3486 }
3487
3488 if (mForm) {
3489 return mForm;
3490 }
3491
3492 //XXXsmaug It isn't clear how this should work in Shadow DOM.
3493 return static_cast<nsDocument*>(GetUncomposedDoc());
3494 }
3495
3496 already_AddRefed<nsIDOMHTMLInputElement>
GetSelectedRadioButton() const3497 HTMLInputElement::GetSelectedRadioButton() const
3498 {
3499 nsIRadioGroupContainer* container = GetRadioGroupContainer();
3500 if (!container) {
3501 return nullptr;
3502 }
3503
3504 nsAutoString name;
3505 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
3506
3507 nsCOMPtr<nsIDOMHTMLInputElement> selected = container->GetCurrentRadioButton(name);
3508 return selected.forget();
3509 }
3510
3511 nsresult
MaybeSubmitForm(nsPresContext * aPresContext)3512 HTMLInputElement::MaybeSubmitForm(nsPresContext* aPresContext)
3513 {
3514 if (!mForm) {
3515 // Nothing to do here.
3516 return NS_OK;
3517 }
3518
3519 nsCOMPtr<nsIPresShell> shell = aPresContext->GetPresShell();
3520 if (!shell) {
3521 return NS_OK;
3522 }
3523
3524 // Get the default submit element
3525 nsIFormControl* submitControl = mForm->GetDefaultSubmitElement();
3526 if (submitControl) {
3527 nsCOMPtr<nsIContent> submitContent = do_QueryInterface(submitControl);
3528 NS_ASSERTION(submitContent, "Form control not implementing nsIContent?!");
3529 // Fire the button's onclick handler and let the button handle
3530 // submitting the form.
3531 WidgetMouseEvent event(true, eMouseClick, nullptr, WidgetMouseEvent::eReal);
3532 nsEventStatus status = nsEventStatus_eIgnore;
3533 shell->HandleDOMEventWithTarget(submitContent, &event, &status);
3534 } else if (!mForm->ImplicitSubmissionIsDisabled() &&
3535 mForm->SubmissionCanProceed(nullptr)) {
3536 // TODO: removing this code and have the submit event sent by the form,
3537 // bug 592124.
3538 // If there's only one text control, just submit the form
3539 // Hold strong ref across the event
3540 RefPtr<mozilla::dom::HTMLFormElement> form = mForm;
3541 InternalFormEvent event(true, eFormSubmit);
3542 nsEventStatus status = nsEventStatus_eIgnore;
3543 shell->HandleDOMEventWithTarget(form, &event, &status);
3544 }
3545
3546 return NS_OK;
3547 }
3548
3549 void
SetCheckedInternal(bool aChecked,bool aNotify)3550 HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify)
3551 {
3552 // Set the value
3553 mChecked = aChecked;
3554
3555 // Notify the frame
3556 if (mType == NS_FORM_INPUT_CHECKBOX || mType == NS_FORM_INPUT_RADIO) {
3557 nsIFrame* frame = GetPrimaryFrame();
3558 if (frame) {
3559 frame->InvalidateFrameSubtree();
3560 }
3561 }
3562
3563 UpdateAllValidityStates(aNotify);
3564
3565 // Notify the document that the CSS :checked pseudoclass for this element
3566 // has changed state.
3567 UpdateState(aNotify);
3568
3569 // Notify all radios in the group that value has changed, this is to let
3570 // radios to have the chance to update its states, e.g., :indeterminate.
3571 if (mType == NS_FORM_INPUT_RADIO) {
3572 nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
3573 VisitGroup(visitor, aNotify);
3574 }
3575 }
3576
3577 void
Blur(ErrorResult & aError)3578 HTMLInputElement::Blur(ErrorResult& aError)
3579 {
3580 if (mType == NS_FORM_INPUT_NUMBER) {
3581 // Blur our anonymous text control, if we have one. (DOM 'change' event
3582 // firing and other things depend on this.)
3583 nsNumberControlFrame* numberControlFrame =
3584 do_QueryFrame(GetPrimaryFrame());
3585 if (numberControlFrame) {
3586 HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl();
3587 if (textControl) {
3588 textControl->Blur(aError);
3589 return;
3590 }
3591 }
3592 }
3593
3594 if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
3595 nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
3596 if (frame) {
3597 frame->HandleBlurEvent();
3598 return;
3599 }
3600 }
3601
3602 nsGenericHTMLElement::Blur(aError);
3603 }
3604
3605 void
Focus(ErrorResult & aError)3606 HTMLInputElement::Focus(ErrorResult& aError)
3607 {
3608 if (mType == NS_FORM_INPUT_NUMBER) {
3609 // Focus our anonymous text control, if we have one.
3610 nsNumberControlFrame* numberControlFrame =
3611 do_QueryFrame(GetPrimaryFrame());
3612 if (numberControlFrame) {
3613 HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl();
3614 if (textControl) {
3615 textControl->Focus(aError);
3616 return;
3617 }
3618 }
3619 }
3620
3621 if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
3622 nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
3623 if (frame) {
3624 frame->HandleFocusEvent();
3625 return;
3626 }
3627 }
3628
3629 if (mType != NS_FORM_INPUT_FILE) {
3630 nsGenericHTMLElement::Focus(aError);
3631 return;
3632 }
3633
3634 // For file inputs, focus the first button instead. In the case of there
3635 // being two buttons (when the picker is a directory picker) the user can
3636 // tab to the next one.
3637 nsIFrame* frame = GetPrimaryFrame();
3638 if (frame) {
3639 for (nsIFrame* childFrame : frame->PrincipalChildList()) {
3640 // See if the child is a button control.
3641 nsCOMPtr<nsIFormControl> formCtrl =
3642 do_QueryInterface(childFrame->GetContent());
3643 if (formCtrl && formCtrl->GetType() == NS_FORM_BUTTON_BUTTON) {
3644 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(formCtrl);
3645 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
3646 if (fm && element) {
3647 fm->SetFocus(element, 0);
3648 }
3649 break;
3650 }
3651 }
3652 }
3653
3654 return;
3655 }
3656
3657 #if !defined(ANDROID) && !defined(XP_MACOSX)
3658 bool
IsNodeApzAwareInternal() const3659 HTMLInputElement::IsNodeApzAwareInternal() const
3660 {
3661 // Tell APZC we may handle mouse wheel event and do preventDefault when input
3662 // type is number.
3663 return (mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE) ||
3664 nsINode::IsNodeApzAwareInternal();
3665 }
3666 #endif
3667
3668 bool
IsInteractiveHTMLContent(bool aIgnoreTabindex) const3669 HTMLInputElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
3670 {
3671 return mType != NS_FORM_INPUT_HIDDEN ||
3672 nsGenericHTMLFormElementWithState::IsInteractiveHTMLContent(aIgnoreTabindex);
3673 }
3674
3675 void
AsyncEventRunning(AsyncEventDispatcher * aEvent)3676 HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher* aEvent)
3677 {
3678 nsImageLoadingContent::AsyncEventRunning(aEvent);
3679 }
3680
3681 NS_IMETHODIMP
Select()3682 HTMLInputElement::Select()
3683 {
3684 if (mType == NS_FORM_INPUT_NUMBER) {
3685 nsNumberControlFrame* numberControlFrame =
3686 do_QueryFrame(GetPrimaryFrame());
3687 if (numberControlFrame) {
3688 return numberControlFrame->HandleSelectCall();
3689 }
3690 return NS_OK;
3691 }
3692
3693 if (!IsSingleLineTextControl(false)) {
3694 return NS_OK;
3695 }
3696
3697 // XXX Bug? We have to give the input focus before contents can be
3698 // selected
3699
3700 FocusTristate state = FocusState();
3701 if (state == eUnfocusable) {
3702 return NS_OK;
3703 }
3704
3705 nsTextEditorState* tes = GetEditorState();
3706 if (tes) {
3707 RefPtr<nsFrameSelection> fs = tes->GetConstFrameSelection();
3708 if (fs && fs->MouseDownRecorded()) {
3709 // This means that we're being called while the frame selection has a mouse
3710 // down event recorded to adjust the caret during the mouse up event.
3711 // We are probably called from the focus event handler. We should override
3712 // the delayed caret data in this case to ensure that this select() call
3713 // takes effect.
3714 fs->SetDelayedCaretData(nullptr);
3715 }
3716 }
3717
3718 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
3719
3720 RefPtr<nsPresContext> presContext = GetPresContext(eForComposedDoc);
3721 if (state == eInactiveWindow) {
3722 if (fm)
3723 fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
3724 SelectAll(presContext);
3725 return NS_OK;
3726 }
3727
3728 if (DispatchSelectEvent(presContext) && fm) {
3729 fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
3730
3731 // ensure that the element is actually focused
3732 nsCOMPtr<nsIDOMElement> focusedElement;
3733 fm->GetFocusedElement(getter_AddRefs(focusedElement));
3734 if (SameCOMIdentity(static_cast<nsIDOMNode*>(this), focusedElement)) {
3735 // Now Select all the text!
3736 SelectAll(presContext);
3737 }
3738 }
3739
3740 return NS_OK;
3741 }
3742
3743 bool
DispatchSelectEvent(nsPresContext * aPresContext)3744 HTMLInputElement::DispatchSelectEvent(nsPresContext* aPresContext)
3745 {
3746 nsEventStatus status = nsEventStatus_eIgnore;
3747
3748 // If already handling select event, don't dispatch a second.
3749 if (!mHandlingSelectEvent) {
3750 WidgetEvent event(nsContentUtils::LegacyIsCallerChromeOrNativeCode(), eFormSelect);
3751
3752 mHandlingSelectEvent = true;
3753 EventDispatcher::Dispatch(static_cast<nsIContent*>(this),
3754 aPresContext, &event, nullptr, &status);
3755 mHandlingSelectEvent = false;
3756 }
3757
3758 // If the DOM event was not canceled (e.g. by a JS event handler
3759 // returning false)
3760 return (status == nsEventStatus_eIgnore);
3761 }
3762
3763 void
SelectAll(nsPresContext * aPresContext)3764 HTMLInputElement::SelectAll(nsPresContext* aPresContext)
3765 {
3766 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
3767
3768 if (formControlFrame) {
3769 formControlFrame->SetFormProperty(nsGkAtoms::select, EmptyString());
3770 }
3771 }
3772
3773 bool
NeedToInitializeEditorForEvent(EventChainPreVisitor & aVisitor) const3774 HTMLInputElement::NeedToInitializeEditorForEvent(
3775 EventChainPreVisitor& aVisitor) const
3776 {
3777 // We only need to initialize the editor for single line input controls because they
3778 // are lazily initialized. We don't need to initialize the control for
3779 // certain types of events, because we know that those events are safe to be
3780 // handled without the editor being initialized. These events include:
3781 // mousein/move/out, overflow/underflow, and DOM mutation events.
3782 if (!IsSingleLineTextControl(false) ||
3783 aVisitor.mEvent->mClass == eMutationEventClass) {
3784 return false;
3785 }
3786
3787 switch (aVisitor.mEvent->mMessage) {
3788 case eMouseMove:
3789 case eMouseEnterIntoWidget:
3790 case eMouseExitFromWidget:
3791 case eMouseOver:
3792 case eMouseOut:
3793 case eScrollPortUnderflow:
3794 case eScrollPortOverflow:
3795 return false;
3796 default:
3797 return true;
3798 }
3799 }
3800
3801 bool
IsDisabledForEvents(EventMessage aMessage)3802 HTMLInputElement::IsDisabledForEvents(EventMessage aMessage)
3803 {
3804 return IsElementDisabledForEvents(aMessage, GetPrimaryFrame());
3805 }
3806
3807 nsresult
PreHandleEvent(EventChainPreVisitor & aVisitor)3808 HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
3809 {
3810 // Do not process any DOM events if the element is disabled
3811 aVisitor.mCanHandle = false;
3812 if (IsDisabledForEvents(aVisitor.mEvent->mMessage)) {
3813 return NS_OK;
3814 }
3815
3816 // Initialize the editor if needed.
3817 if (NeedToInitializeEditorForEvent(aVisitor)) {
3818 nsITextControlFrame* textControlFrame = do_QueryFrame(GetPrimaryFrame());
3819 if (textControlFrame)
3820 textControlFrame->EnsureEditorInitialized();
3821 }
3822
3823 //FIXME Allow submission etc. also when there is no prescontext, Bug 329509.
3824 if (!aVisitor.mPresContext) {
3825 return nsGenericHTMLElement::PreHandleEvent(aVisitor);
3826 }
3827 //
3828 // Web pages expect the value of a radio button or checkbox to be set
3829 // *before* onclick and DOMActivate fire, and they expect that if they set
3830 // the value explicitly during onclick or DOMActivate it will not be toggled
3831 // or any such nonsense.
3832 // In order to support that (bug 57137 and 58460 are examples) we toggle
3833 // the checked attribute *first*, and then fire onclick. If the user
3834 // returns false, we reset the control to the old checked value. Otherwise,
3835 // we dispatch DOMActivate. If DOMActivate is cancelled, we also reset
3836 // the control to the old checked value. We need to keep track of whether
3837 // we've already toggled the state from onclick since the user could
3838 // explicitly dispatch DOMActivate on the element.
3839 //
3840 // This is a compatibility hack.
3841 //
3842
3843 // Track whether we're in the outermost Dispatch invocation that will
3844 // cause activation of the input. That is, if we're a click event, or a
3845 // DOMActivate that was dispatched directly, this will be set, but if we're
3846 // a DOMActivate dispatched from click handling, it will not be set.
3847 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3848 bool outerActivateEvent =
3849 ((mouseEvent && mouseEvent->IsLeftClickEvent()) ||
3850 (aVisitor.mEvent->mMessage == eLegacyDOMActivate && !mInInternalActivate));
3851
3852 if (outerActivateEvent) {
3853 aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
3854 }
3855
3856 bool originalCheckedValue = false;
3857
3858 if (outerActivateEvent) {
3859 mCheckedIsToggled = false;
3860
3861 switch(mType) {
3862 case NS_FORM_INPUT_CHECKBOX:
3863 {
3864 if (mIndeterminate) {
3865 // indeterminate is always set to FALSE when the checkbox is toggled
3866 SetIndeterminateInternal(false, false);
3867 aVisitor.mItemFlags |= NS_ORIGINAL_INDETERMINATE_VALUE;
3868 }
3869
3870 GetChecked(&originalCheckedValue);
3871 DoSetChecked(!originalCheckedValue, true, true);
3872 mCheckedIsToggled = true;
3873 }
3874 break;
3875
3876 case NS_FORM_INPUT_RADIO:
3877 {
3878 nsCOMPtr<nsIDOMHTMLInputElement> selectedRadioButton = GetSelectedRadioButton();
3879 aVisitor.mItemData = selectedRadioButton;
3880
3881 originalCheckedValue = mChecked;
3882 if (!originalCheckedValue) {
3883 DoSetChecked(true, true, true);
3884 mCheckedIsToggled = true;
3885 }
3886 }
3887 break;
3888
3889 case NS_FORM_INPUT_SUBMIT:
3890 case NS_FORM_INPUT_IMAGE:
3891 if (mForm) {
3892 // tell the form that we are about to enter a click handler.
3893 // that means that if there are scripted submissions, the
3894 // latest one will be deferred until after the exit point of the handler.
3895 mForm->OnSubmitClickBegin(this);
3896 }
3897 break;
3898
3899 default:
3900 break;
3901 }
3902 }
3903
3904 if (originalCheckedValue) {
3905 aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE;
3906 }
3907
3908 // If mNoContentDispatch is true we will not allow content to handle
3909 // this event. But to allow middle mouse button paste to work we must allow
3910 // middle clicks to go to text fields anyway.
3911 if (aVisitor.mEvent->mFlags.mNoContentDispatch) {
3912 aVisitor.mItemFlags |= NS_NO_CONTENT_DISPATCH;
3913 }
3914 if (IsSingleLineTextControl(false) &&
3915 aVisitor.mEvent->mMessage == eMouseClick &&
3916 aVisitor.mEvent->AsMouseEvent()->button ==
3917 WidgetMouseEvent::eMiddleButton) {
3918 aVisitor.mEvent->mFlags.mNoContentDispatch = false;
3919 }
3920
3921 // We must cache type because mType may change during JS event (bug 2369)
3922 aVisitor.mItemFlags |= mType;
3923
3924 if (aVisitor.mEvent->mMessage == eFocus &&
3925 aVisitor.mEvent->IsTrusted() &&
3926 MayFireChangeOnBlur() &&
3927 // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before
3928 // we get the 'focus' event.
3929 !mIsDraggingRange) {
3930 GetValue(mFocusedValue);
3931 }
3932
3933 // Fire onchange (if necessary), before we do the blur, bug 357684.
3934 if (aVisitor.mEvent->mMessage == eBlur) {
3935 // Experimental mobile types rely on the system UI to prevent users to not
3936 // set invalid values but we have to be extra-careful. Especially if the
3937 // option has been enabled on desktop.
3938 if (IsExperimentalMobileType(mType)) {
3939 nsAutoString aValue;
3940 GetValueInternal(aValue);
3941 nsresult rv =
3942 SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal);
3943 NS_ENSURE_SUCCESS(rv, rv);
3944 }
3945 FireChangeEventIfNeeded();
3946 }
3947
3948 if (mType == NS_FORM_INPUT_RANGE &&
3949 (aVisitor.mEvent->mMessage == eFocus ||
3950 aVisitor.mEvent->mMessage == eBlur)) {
3951 // Just as nsGenericHTMLFormElementWithState::PreHandleEvent calls
3952 // nsIFormControlFrame::SetFocus, we handle focus here.
3953 nsIFrame* frame = GetPrimaryFrame();
3954 if (frame) {
3955 frame->InvalidateFrameSubtree();
3956 }
3957 }
3958
3959 if (mType == NS_FORM_INPUT_TIME &&
3960 !IsExperimentalMobileType(mType) &&
3961 aVisitor.mEvent->mMessage == eFocus &&
3962 aVisitor.mEvent->mOriginalTarget == this) {
3963 // If original target is this and not the anonymous text control, we should
3964 // pass the focus to the anonymous text control.
3965 nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
3966 if (frame) {
3967 frame->HandleFocusEvent();
3968 }
3969 }
3970
3971 if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
3972 if (mNumberControlSpinnerIsSpinning) {
3973 // If the timer is running the user has depressed the mouse on one of the
3974 // spin buttons. If the mouse exits the button we either want to reverse
3975 // the direction of spin if it has moved over the other button, or else
3976 // we want to end the spin. We do this here (rather than in
3977 // PostHandleEvent) because we don't want to let content preventDefault()
3978 // the end of the spin.
3979 if (aVisitor.mEvent->mMessage == eMouseMove) {
3980 // Be aggressive about stopping the spin:
3981 bool stopSpin = true;
3982 nsNumberControlFrame* numberControlFrame =
3983 do_QueryFrame(GetPrimaryFrame());
3984 if (numberControlFrame) {
3985 bool oldNumberControlSpinTimerSpinsUpValue =
3986 mNumberControlSpinnerSpinsUp;
3987 switch (numberControlFrame->GetSpinButtonForPointerEvent(
3988 aVisitor.mEvent->AsMouseEvent())) {
3989 case nsNumberControlFrame::eSpinButtonUp:
3990 mNumberControlSpinnerSpinsUp = true;
3991 stopSpin = false;
3992 break;
3993 case nsNumberControlFrame::eSpinButtonDown:
3994 mNumberControlSpinnerSpinsUp = false;
3995 stopSpin = false;
3996 break;
3997 }
3998 if (mNumberControlSpinnerSpinsUp !=
3999 oldNumberControlSpinTimerSpinsUpValue) {
4000 nsNumberControlFrame* numberControlFrame =
4001 do_QueryFrame(GetPrimaryFrame());
4002 if (numberControlFrame) {
4003 numberControlFrame->SpinnerStateChanged();
4004 }
4005 }
4006 }
4007 if (stopSpin) {
4008 StopNumberControlSpinnerSpin();
4009 }
4010 } else if (aVisitor.mEvent->mMessage == eMouseUp) {
4011 StopNumberControlSpinnerSpin();
4012 }
4013 }
4014 if (aVisitor.mEvent->mMessage == eFocus ||
4015 aVisitor.mEvent->mMessage == eBlur) {
4016 if (aVisitor.mEvent->mMessage == eFocus) {
4017 // Tell our frame it's getting focus so that it can make sure focus
4018 // is moved to our anonymous text control.
4019 nsNumberControlFrame* numberControlFrame =
4020 do_QueryFrame(GetPrimaryFrame());
4021 if (numberControlFrame) {
4022 // This could kill the frame!
4023 numberControlFrame->HandleFocusEvent(aVisitor.mEvent);
4024 }
4025 }
4026 nsIFrame* frame = GetPrimaryFrame();
4027 if (frame && frame->IsThemed()) {
4028 // Our frame's nested <input type=text> will be invalidated when it
4029 // loses focus, but since we are also native themed we need to make
4030 // sure that our entire area is repainted since any focus highlight
4031 // from the theme should be removed from us (the repainting of the
4032 // sub-area occupied by the anon text control is not enough to do
4033 // that).
4034 frame->InvalidateFrame();
4035 }
4036 }
4037 }
4038
4039 nsresult rv = nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
4040
4041 // We do this after calling the base class' PreHandleEvent so that
4042 // nsIContent::PreHandleEvent doesn't reset any change we make to mCanHandle.
4043 if (mType == NS_FORM_INPUT_NUMBER &&
4044 aVisitor.mEvent->IsTrusted() &&
4045 aVisitor.mEvent->mOriginalTarget != this) {
4046 // <input type=number> has an anonymous <input type=text> descendant. If
4047 // 'input' or 'change' events are fired at that text control then we need
4048 // to do some special handling here.
4049 HTMLInputElement* textControl = nullptr;
4050 nsNumberControlFrame* numberControlFrame =
4051 do_QueryFrame(GetPrimaryFrame());
4052 if (numberControlFrame) {
4053 textControl = numberControlFrame->GetAnonTextControl();
4054 }
4055 if (textControl && aVisitor.mEvent->mOriginalTarget == textControl) {
4056 if (aVisitor.mEvent->mMessage == eEditorInput) {
4057 // Propogate the anon text control's new value to our HTMLInputElement:
4058 nsAutoString value;
4059 numberControlFrame->GetValueOfAnonTextControl(value);
4060 numberControlFrame->HandlingInputEvent(true);
4061 nsWeakFrame weakNumberControlFrame(numberControlFrame);
4062 rv = SetValueInternal(value,
4063 nsTextEditorState::eSetValue_BySetUserInput |
4064 nsTextEditorState::eSetValue_Notify);
4065 NS_ENSURE_SUCCESS(rv, rv);
4066 if (weakNumberControlFrame.IsAlive()) {
4067 numberControlFrame->HandlingInputEvent(false);
4068 }
4069 }
4070 else if (aVisitor.mEvent->mMessage == eFormChange) {
4071 // We cancel the DOM 'change' event that is fired for any change to our
4072 // anonymous text control since we fire our own 'change' events and
4073 // content shouldn't be seeing two 'change' events. Besides that we
4074 // (as a number) control have tighter restrictions on when our internal
4075 // value changes than our anon text control does, so in some cases
4076 // (if our text control's value doesn't parse as a number) we don't
4077 // want to fire a 'change' event at all.
4078 aVisitor.mCanHandle = false;
4079 }
4080 }
4081 }
4082
4083 // Stop the event if the related target's first non-native ancestor is the
4084 // same as the original target's first non-native ancestor (we are moving
4085 // inside of the same element).
4086 if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType) &&
4087 (aVisitor.mEvent->mMessage == eFocus ||
4088 aVisitor.mEvent->mMessage == eFocusIn ||
4089 aVisitor.mEvent->mMessage == eFocusOut ||
4090 aVisitor.mEvent->mMessage == eBlur)) {
4091 nsCOMPtr<nsIContent> originalTarget =
4092 do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
4093 nsCOMPtr<nsIContent> relatedTarget =
4094 do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
4095
4096 if (originalTarget && relatedTarget &&
4097 originalTarget->FindFirstNonChromeOnlyAccessContent() ==
4098 relatedTarget->FindFirstNonChromeOnlyAccessContent()) {
4099 aVisitor.mCanHandle = false;
4100 }
4101 }
4102
4103 return rv;
4104 }
4105
4106 void
StartRangeThumbDrag(WidgetGUIEvent * aEvent)4107 HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent* aEvent)
4108 {
4109 mIsDraggingRange = true;
4110 mRangeThumbDragStartValue = GetValueAsDecimal();
4111 // Don't use CAPTURE_RETARGETTOELEMENT, as that breaks pseudo-class styling
4112 // of the thumb.
4113 nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
4114 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4115
4116 // Before we change the value, record the current value so that we'll
4117 // correctly send a 'change' event if appropriate. We need to do this here
4118 // because the 'focus' event is handled after the 'mousedown' event that
4119 // we're being called for (i.e. too late to update mFocusedValue, since we'll
4120 // have changed it by then).
4121 GetValue(mFocusedValue);
4122
4123 SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
4124 }
4125
4126 void
FinishRangeThumbDrag(WidgetGUIEvent * aEvent)4127 HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent* aEvent)
4128 {
4129 MOZ_ASSERT(mIsDraggingRange);
4130
4131 if (nsIPresShell::GetCapturingContent() == this) {
4132 nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
4133 }
4134 if (aEvent) {
4135 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4136 SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
4137 }
4138 mIsDraggingRange = false;
4139 FireChangeEventIfNeeded();
4140 }
4141
4142 void
CancelRangeThumbDrag(bool aIsForUserEvent)4143 HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent)
4144 {
4145 MOZ_ASSERT(mIsDraggingRange);
4146
4147 mIsDraggingRange = false;
4148 if (nsIPresShell::GetCapturingContent() == this) {
4149 nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
4150 }
4151 if (aIsForUserEvent) {
4152 SetValueOfRangeForUserEvent(mRangeThumbDragStartValue);
4153 } else {
4154 // Don't dispatch an 'input' event - at least not using
4155 // DispatchTrustedEvent.
4156 // TODO: decide what we should do here - bug 851782.
4157 nsAutoString val;
4158 ConvertNumberToString(mRangeThumbDragStartValue, val);
4159 // TODO: What should we do if SetValueInternal fails? (The allocation
4160 // is small, so we should be fine here.)
4161 SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
4162 nsTextEditorState::eSetValue_Notify);
4163 nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
4164 if (frame) {
4165 frame->UpdateForValueChange();
4166 }
4167 RefPtr<AsyncEventDispatcher> asyncDispatcher =
4168 new AsyncEventDispatcher(this, NS_LITERAL_STRING("input"), true, false);
4169 asyncDispatcher->RunDOMEventWhenSafe();
4170 }
4171 }
4172
4173 void
SetValueOfRangeForUserEvent(Decimal aValue)4174 HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue)
4175 {
4176 MOZ_ASSERT(aValue.isFinite());
4177
4178 Decimal oldValue = GetValueAsDecimal();
4179
4180 nsAutoString val;
4181 ConvertNumberToString(aValue, val);
4182 // TODO: What should we do if SetValueInternal fails? (The allocation
4183 // is small, so we should be fine here.)
4184 SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
4185 nsTextEditorState::eSetValue_Notify);
4186 nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
4187 if (frame) {
4188 frame->UpdateForValueChange();
4189 }
4190
4191 if (GetValueAsDecimal() != oldValue) {
4192 nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
4193 static_cast<nsIDOMHTMLInputElement*>(this),
4194 NS_LITERAL_STRING("input"), true,
4195 false);
4196 }
4197 }
4198
4199 void
StartNumberControlSpinnerSpin()4200 HTMLInputElement::StartNumberControlSpinnerSpin()
4201 {
4202 MOZ_ASSERT(!mNumberControlSpinnerIsSpinning);
4203
4204 mNumberControlSpinnerIsSpinning = true;
4205
4206 nsRepeatService::GetInstance()->Start(HandleNumberControlSpin, this);
4207
4208 // Capture the mouse so that we can tell if the pointer moves from one
4209 // spin button to the other, or to some other element:
4210 nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
4211
4212 nsNumberControlFrame* numberControlFrame =
4213 do_QueryFrame(GetPrimaryFrame());
4214 if (numberControlFrame) {
4215 numberControlFrame->SpinnerStateChanged();
4216 }
4217 }
4218
4219 void
StopNumberControlSpinnerSpin(SpinnerStopState aState)4220 HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState)
4221 {
4222 if (mNumberControlSpinnerIsSpinning) {
4223 if (nsIPresShell::GetCapturingContent() == this) {
4224 nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
4225 }
4226
4227 nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
4228
4229 mNumberControlSpinnerIsSpinning = false;
4230
4231 if (aState == eAllowDispatchingEvents) {
4232 FireChangeEventIfNeeded();
4233 }
4234
4235 nsNumberControlFrame* numberControlFrame =
4236 do_QueryFrame(GetPrimaryFrame());
4237 if (numberControlFrame) {
4238 MOZ_ASSERT(aState == eAllowDispatchingEvents,
4239 "Shouldn't have primary frame for the element when we're not "
4240 "allowed to dispatch events to it anymore.");
4241 numberControlFrame->SpinnerStateChanged();
4242 }
4243 }
4244 }
4245
4246 void
StepNumberControlForUserEvent(int32_t aDirection)4247 HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection)
4248 {
4249 // We can't use GetValidityState here because the validity state is not set
4250 // if the user hasn't previously taken an action to set or change the value,
4251 // according to the specs.
4252 if (HasBadInput()) {
4253 // If the user has typed a value into the control and inadvertently made a
4254 // mistake (e.g. put a thousand separator at the wrong point) we do not
4255 // want to wipe out what they typed if they try to increment/decrement the
4256 // value. Better is to highlight the value as being invalid so that they
4257 // can correct what they typed.
4258 // We only do this if there actually is a value typed in by/displayed to
4259 // the user. (IsValid() can return false if the 'required' attribute is
4260 // set and the value is the empty string.)
4261 nsNumberControlFrame* numberControlFrame =
4262 do_QueryFrame(GetPrimaryFrame());
4263 if (numberControlFrame &&
4264 !numberControlFrame->AnonTextControlIsEmpty()) {
4265 // We pass 'true' for UpdateValidityUIBits' aIsFocused argument
4266 // regardless because we need the UI to update _now_ or the user will
4267 // wonder why the step behavior isn't functioning.
4268 UpdateValidityUIBits(true);
4269 UpdateState(true);
4270 return;
4271 }
4272 }
4273
4274 Decimal newValue = Decimal::nan(); // unchanged if value will not change
4275
4276 nsresult rv = GetValueIfStepped(aDirection, CALLED_FOR_USER_EVENT, &newValue);
4277
4278 if (NS_FAILED(rv) || !newValue.isFinite()) {
4279 return; // value should not or will not change
4280 }
4281
4282 nsAutoString newVal;
4283 ConvertNumberToString(newValue, newVal);
4284 // TODO: What should we do if SetValueInternal fails? (The allocation
4285 // is small, so we should be fine here.)
4286 SetValueInternal(newVal, nsTextEditorState::eSetValue_BySetUserInput |
4287 nsTextEditorState::eSetValue_Notify);
4288
4289 nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
4290 static_cast<nsIDOMHTMLInputElement*>(this),
4291 NS_LITERAL_STRING("input"), true,
4292 false);
4293 }
4294
4295 static bool
SelectTextFieldOnFocus()4296 SelectTextFieldOnFocus()
4297 {
4298 if (!gSelectTextFieldOnFocus) {
4299 int32_t selectTextfieldsOnKeyFocus = -1;
4300 nsresult rv =
4301 LookAndFeel::GetInt(LookAndFeel::eIntID_SelectTextfieldsOnKeyFocus,
4302 &selectTextfieldsOnKeyFocus);
4303 if (NS_FAILED(rv)) {
4304 gSelectTextFieldOnFocus = -1;
4305 } else {
4306 gSelectTextFieldOnFocus = selectTextfieldsOnKeyFocus != 0 ? 1 : -1;
4307 }
4308 }
4309
4310 return gSelectTextFieldOnFocus == 1;
4311 }
4312
4313 bool
ShouldPreventDOMActivateDispatch(EventTarget * aOriginalTarget)4314 HTMLInputElement::ShouldPreventDOMActivateDispatch(EventTarget* aOriginalTarget)
4315 {
4316 /*
4317 * For the moment, there is only one situation where we actually want to
4318 * prevent firing a DOMActivate event:
4319 * - we are a <input type='file'> that just got a click event,
4320 * - the event was targeted to our button which should have sent a
4321 * DOMActivate event.
4322 */
4323
4324 if (mType != NS_FORM_INPUT_FILE) {
4325 return false;
4326 }
4327
4328 nsCOMPtr<nsIContent> target = do_QueryInterface(aOriginalTarget);
4329 if (!target) {
4330 return false;
4331 }
4332
4333 return target->GetParent() == this &&
4334 target->IsRootOfNativeAnonymousSubtree() &&
4335 target->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
4336 nsGkAtoms::button, eCaseMatters);
4337 }
4338
4339 nsresult
MaybeInitPickers(EventChainPostVisitor & aVisitor)4340 HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor)
4341 {
4342 // Open a file picker when we receive a click on a <input type='file'>, or
4343 // open a color picker when we receive a click on a <input type='color'>.
4344 // A click is handled in the following cases:
4345 // - preventDefault() has not been called (or something similar);
4346 // - it's the left mouse button.
4347 // We do not prevent non-trusted click because authors can already use
4348 // .click(). However, the pickers will follow the rules of popup-blocking.
4349 if (aVisitor.mEvent->DefaultPrevented()) {
4350 return NS_OK;
4351 }
4352 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
4353 if (!(mouseEvent && mouseEvent->IsLeftClickEvent())) {
4354 return NS_OK;
4355 }
4356 if (mType == NS_FORM_INPUT_FILE) {
4357 // If the user clicked on the "Choose folder..." button we open the
4358 // directory picker, else we open the file picker.
4359 FilePickerType type = FILE_PICKER_FILE;
4360 nsCOMPtr<nsIContent> target =
4361 do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
4362 if (target &&
4363 target->FindFirstNonChromeOnlyAccessContent() == this &&
4364 ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
4365 (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
4366 HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)))) {
4367 type = FILE_PICKER_DIRECTORY;
4368 }
4369 return InitFilePicker(type);
4370 }
4371 if (mType == NS_FORM_INPUT_COLOR) {
4372 return InitColorPicker();
4373 }
4374 if (mType == NS_FORM_INPUT_DATE) {
4375 return InitDatePicker();
4376 }
4377
4378 return NS_OK;
4379 }
4380
4381 /**
4382 * Return true if the input event should be ignore because of it's modifiers
4383 */
4384 static bool
IgnoreInputEventWithModifier(WidgetInputEvent * aEvent)4385 IgnoreInputEventWithModifier(WidgetInputEvent* aEvent)
4386 {
4387 return aEvent->IsShift() || aEvent->IsControl() || aEvent->IsAlt() ||
4388 aEvent->IsMeta() || aEvent->IsAltGraph() || aEvent->IsFn() ||
4389 aEvent->IsOS();
4390 }
4391
4392 nsresult
PostHandleEvent(EventChainPostVisitor & aVisitor)4393 HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
4394 {
4395 if (!aVisitor.mPresContext) {
4396 // Hack alert! In order to open file picker even in case the element isn't
4397 // in document, try to init picker even without PresContext.
4398 return MaybeInitPickers(aVisitor);
4399 }
4400
4401 if (aVisitor.mEvent->mMessage == eFocus ||
4402 aVisitor.mEvent->mMessage == eBlur) {
4403 if (aVisitor.mEvent->mMessage == eBlur) {
4404 if (mIsDraggingRange) {
4405 FinishRangeThumbDrag();
4406 } else if (mNumberControlSpinnerIsSpinning) {
4407 StopNumberControlSpinnerSpin();
4408 }
4409 }
4410
4411 UpdateValidityUIBits(aVisitor.mEvent->mMessage == eFocus);
4412
4413 UpdateState(true);
4414 }
4415
4416 nsresult rv = NS_OK;
4417 bool outerActivateEvent = !!(aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT);
4418 bool originalCheckedValue =
4419 !!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE);
4420 bool noContentDispatch = !!(aVisitor.mItemFlags & NS_NO_CONTENT_DISPATCH);
4421 uint8_t oldType = NS_CONTROL_TYPE(aVisitor.mItemFlags);
4422
4423 // Ideally we would make the default action for click and space just dispatch
4424 // DOMActivate, and the default action for DOMActivate flip the checkbox/
4425 // radio state and fire onchange. However, for backwards compatibility, we
4426 // need to flip the state before firing click, and we need to fire click
4427 // when space is pressed. So, we just nest the firing of DOMActivate inside
4428 // the click event handling, and allow cancellation of DOMActivate to cancel
4429 // the click.
4430 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault &&
4431 !IsSingleLineTextControl(true) &&
4432 mType != NS_FORM_INPUT_NUMBER) {
4433 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
4434 if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
4435 !ShouldPreventDOMActivateDispatch(aVisitor.mEvent->mOriginalTarget)) {
4436 // DOMActive event should be trusted since the activation is actually
4437 // occurred even if the cause is an untrusted click event.
4438 InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
4439 actEvent.mDetail = 1;
4440
4441 nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
4442 if (shell) {
4443 nsEventStatus status = nsEventStatus_eIgnore;
4444 mInInternalActivate = true;
4445 rv = shell->HandleDOMEventWithTarget(this, &actEvent, &status);
4446 mInInternalActivate = false;
4447
4448 // If activate is cancelled, we must do the same as when click is
4449 // cancelled (revert the checkbox to its original value).
4450 if (status == nsEventStatus_eConsumeNoDefault) {
4451 aVisitor.mEventStatus = status;
4452 }
4453 }
4454 }
4455 }
4456
4457 if (outerActivateEvent) {
4458 switch(oldType) {
4459 case NS_FORM_INPUT_SUBMIT:
4460 case NS_FORM_INPUT_IMAGE:
4461 if (mForm) {
4462 // tell the form that we are about to exit a click handler
4463 // so the form knows not to defer subsequent submissions
4464 // the pending ones that were created during the handler
4465 // will be flushed or forgoten.
4466 mForm->OnSubmitClickEnd();
4467 }
4468 break;
4469 default:
4470 break;
4471 }
4472 }
4473
4474 // Reset the flag for other content besides this text field
4475 aVisitor.mEvent->mFlags.mNoContentDispatch = noContentDispatch;
4476
4477 // now check to see if the event was "cancelled"
4478 if (mCheckedIsToggled && outerActivateEvent) {
4479 if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) {
4480 // if it was cancelled and a radio button, then set the old
4481 // selected btn to TRUE. if it is a checkbox then set it to its
4482 // original value
4483 if (oldType == NS_FORM_INPUT_RADIO) {
4484 nsCOMPtr<nsIDOMHTMLInputElement> selectedRadioButton =
4485 do_QueryInterface(aVisitor.mItemData);
4486 if (selectedRadioButton) {
4487 selectedRadioButton->SetChecked(true);
4488 }
4489 // If there was no checked radio button or this one is no longer a
4490 // radio button we must reset it back to false to cancel the action.
4491 // See how the web of hack grows?
4492 if (!selectedRadioButton || mType != NS_FORM_INPUT_RADIO) {
4493 DoSetChecked(false, true, true);
4494 }
4495 } else if (oldType == NS_FORM_INPUT_CHECKBOX) {
4496 bool originalIndeterminateValue =
4497 !!(aVisitor.mItemFlags & NS_ORIGINAL_INDETERMINATE_VALUE);
4498 SetIndeterminateInternal(originalIndeterminateValue, false);
4499 DoSetChecked(originalCheckedValue, true, true);
4500 }
4501 } else {
4502 // Fire input event and then change event.
4503 nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
4504 static_cast<nsIDOMHTMLInputElement*>(this),
4505 NS_LITERAL_STRING("input"), true,
4506 false);
4507
4508 nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
4509 static_cast<nsIDOMHTMLInputElement*>(this),
4510 NS_LITERAL_STRING("change"), true,
4511 false);
4512 #ifdef ACCESSIBILITY
4513 // Fire an event to notify accessibility
4514 if (mType == NS_FORM_INPUT_CHECKBOX) {
4515 FireEventForAccessibility(this, aVisitor.mPresContext,
4516 NS_LITERAL_STRING("CheckboxStateChange"));
4517 } else {
4518 FireEventForAccessibility(this, aVisitor.mPresContext,
4519 NS_LITERAL_STRING("RadioStateChange"));
4520 // Fire event for the previous selected radio.
4521 nsCOMPtr<nsIDOMHTMLInputElement> previous =
4522 do_QueryInterface(aVisitor.mItemData);
4523 if (previous) {
4524 FireEventForAccessibility(previous, aVisitor.mPresContext,
4525 NS_LITERAL_STRING("RadioStateChange"));
4526 }
4527 }
4528 #endif
4529 }
4530 }
4531
4532 if (NS_SUCCEEDED(rv)) {
4533 WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
4534 if (mType == NS_FORM_INPUT_NUMBER &&
4535 keyEvent && keyEvent->mMessage == eKeyPress &&
4536 aVisitor.mEvent->IsTrusted() &&
4537 (keyEvent->mKeyCode == NS_VK_UP || keyEvent->mKeyCode == NS_VK_DOWN) &&
4538 !IgnoreInputEventWithModifier(keyEvent)) {
4539 // We handle the up/down arrow keys specially for <input type=number>.
4540 // On some platforms the editor for the nested text control will
4541 // process these keys to send the cursor to the start/end of the text
4542 // control and as a result aVisitor.mEventStatus will already have been
4543 // set to nsEventStatus_eConsumeNoDefault. However, we know that
4544 // whenever the up/down arrow keys cause the value of the number
4545 // control to change the string in the text control will change, and
4546 // the cursor will be moved to the end of the text control, overwriting
4547 // the editor's handling of up/down keypress events. For that reason we
4548 // just ignore aVisitor.mEventStatus here and go ahead and handle the
4549 // event to increase/decrease the value of the number control.
4550 if (!aVisitor.mEvent->DefaultPreventedByContent() && IsMutable()) {
4551 StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
4552 FireChangeEventIfNeeded();
4553 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4554 }
4555 } else if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
4556 switch (aVisitor.mEvent->mMessage) {
4557 case eFocus: {
4558 // see if we should select the contents of the textbox. This happens
4559 // for text and password fields when the field was focused by the
4560 // keyboard or a navigation, the platform allows it, and it wasn't
4561 // just because we raised a window.
4562 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
4563 if (fm && IsSingleLineTextControl(false) &&
4564 !aVisitor.mEvent->AsFocusEvent()->mFromRaise &&
4565 SelectTextFieldOnFocus()) {
4566 nsIDocument* document = GetComposedDoc();
4567 if (document) {
4568 uint32_t lastFocusMethod;
4569 fm->GetLastFocusMethod(document->GetWindow(), &lastFocusMethod);
4570 if (lastFocusMethod &
4571 (nsIFocusManager::FLAG_BYKEY | nsIFocusManager::FLAG_BYMOVEFOCUS)) {
4572 RefPtr<nsPresContext> presContext =
4573 GetPresContext(eForComposedDoc);
4574 if (DispatchSelectEvent(presContext)) {
4575 SelectAll(presContext);
4576 }
4577 }
4578 }
4579 }
4580 break;
4581 }
4582
4583 case eKeyPress:
4584 case eKeyUp:
4585 {
4586 // For backwards compat, trigger checks/radios/buttons with
4587 // space or enter (bug 25300)
4588 WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
4589 if ((aVisitor.mEvent->mMessage == eKeyPress &&
4590 keyEvent->mKeyCode == NS_VK_RETURN) ||
4591 (aVisitor.mEvent->mMessage == eKeyUp &&
4592 keyEvent->mKeyCode == NS_VK_SPACE)) {
4593 switch(mType) {
4594 case NS_FORM_INPUT_CHECKBOX:
4595 case NS_FORM_INPUT_RADIO:
4596 {
4597 // Checkbox and Radio try to submit on Enter press
4598 if (keyEvent->mKeyCode != NS_VK_SPACE) {
4599 MaybeSubmitForm(aVisitor.mPresContext);
4600
4601 break; // If we are submitting, do not send click event
4602 }
4603 // else fall through and treat Space like click...
4604 MOZ_FALLTHROUGH;
4605 }
4606 case NS_FORM_INPUT_BUTTON:
4607 case NS_FORM_INPUT_RESET:
4608 case NS_FORM_INPUT_SUBMIT:
4609 case NS_FORM_INPUT_IMAGE: // Bug 34418
4610 case NS_FORM_INPUT_COLOR:
4611 {
4612 DispatchSimulatedClick(this, aVisitor.mEvent->IsTrusted(),
4613 aVisitor.mPresContext);
4614 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4615 } // case
4616 } // switch
4617 }
4618 if (aVisitor.mEvent->mMessage == eKeyPress &&
4619 mType == NS_FORM_INPUT_RADIO && !keyEvent->IsAlt() &&
4620 !keyEvent->IsControl() && !keyEvent->IsMeta()) {
4621 bool isMovingBack = false;
4622 switch (keyEvent->mKeyCode) {
4623 case NS_VK_UP:
4624 case NS_VK_LEFT:
4625 isMovingBack = true;
4626 MOZ_FALLTHROUGH;
4627 case NS_VK_DOWN:
4628 case NS_VK_RIGHT:
4629 // Arrow key pressed, focus+select prev/next radio button
4630 nsIRadioGroupContainer* container = GetRadioGroupContainer();
4631 if (container) {
4632 nsAutoString name;
4633 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
4634 RefPtr<HTMLInputElement> selectedRadioButton;
4635 container->GetNextRadioButton(name, isMovingBack, this,
4636 getter_AddRefs(selectedRadioButton));
4637 if (selectedRadioButton) {
4638 rv = selectedRadioButton->Focus();
4639 if (NS_SUCCEEDED(rv)) {
4640 rv = DispatchSimulatedClick(selectedRadioButton,
4641 aVisitor.mEvent->IsTrusted(),
4642 aVisitor.mPresContext);
4643 if (NS_SUCCEEDED(rv)) {
4644 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4645 }
4646 }
4647 }
4648 }
4649 }
4650 }
4651
4652 /*
4653 * For some input types, if the user hits enter, the form is submitted.
4654 *
4655 * Bug 99920, bug 109463 and bug 147850:
4656 * (a) if there is a submit control in the form, click the first
4657 * submit control in the form.
4658 * (b) if there is just one text control in the form, submit by
4659 * sending a submit event directly to the form
4660 * (c) if there is more than one text input and no submit buttons, do
4661 * not submit, period.
4662 */
4663
4664 if (aVisitor.mEvent->mMessage == eKeyPress &&
4665 keyEvent->mKeyCode == NS_VK_RETURN &&
4666 (IsSingleLineTextControl(false, mType) ||
4667 mType == NS_FORM_INPUT_NUMBER ||
4668 IsExperimentalMobileType(mType) ||
4669 IsDateTimeInputType(mType))) {
4670 FireChangeEventIfNeeded();
4671 rv = MaybeSubmitForm(aVisitor.mPresContext);
4672 NS_ENSURE_SUCCESS(rv, rv);
4673 }
4674
4675 if (aVisitor.mEvent->mMessage == eKeyPress &&
4676 mType == NS_FORM_INPUT_RANGE && !keyEvent->IsAlt() &&
4677 !keyEvent->IsControl() && !keyEvent->IsMeta() &&
4678 (keyEvent->mKeyCode == NS_VK_LEFT ||
4679 keyEvent->mKeyCode == NS_VK_RIGHT ||
4680 keyEvent->mKeyCode == NS_VK_UP ||
4681 keyEvent->mKeyCode == NS_VK_DOWN ||
4682 keyEvent->mKeyCode == NS_VK_PAGE_UP ||
4683 keyEvent->mKeyCode == NS_VK_PAGE_DOWN ||
4684 keyEvent->mKeyCode == NS_VK_HOME ||
4685 keyEvent->mKeyCode == NS_VK_END)) {
4686 Decimal minimum = GetMinimum();
4687 Decimal maximum = GetMaximum();
4688 MOZ_ASSERT(minimum.isFinite() && maximum.isFinite());
4689 if (minimum < maximum) { // else the value is locked to the minimum
4690 Decimal value = GetValueAsDecimal();
4691 Decimal step = GetStep();
4692 if (step == kStepAny) {
4693 step = GetDefaultStep();
4694 }
4695 MOZ_ASSERT(value.isFinite() && step.isFinite());
4696 Decimal newValue;
4697 switch (keyEvent->mKeyCode) {
4698 case NS_VK_LEFT:
4699 newValue = value + (GetComputedDirectionality() == eDir_RTL
4700 ? step : -step);
4701 break;
4702 case NS_VK_RIGHT:
4703 newValue = value + (GetComputedDirectionality() == eDir_RTL
4704 ? -step : step);
4705 break;
4706 case NS_VK_UP:
4707 // Even for horizontal range, "up" means "increase"
4708 newValue = value + step;
4709 break;
4710 case NS_VK_DOWN:
4711 // Even for horizontal range, "down" means "decrease"
4712 newValue = value - step;
4713 break;
4714 case NS_VK_HOME:
4715 newValue = minimum;
4716 break;
4717 case NS_VK_END:
4718 newValue = maximum;
4719 break;
4720 case NS_VK_PAGE_UP:
4721 // For PgUp/PgDn we jump 10% of the total range, unless step
4722 // requires us to jump more.
4723 newValue = value + std::max(step, (maximum - minimum) / Decimal(10));
4724 break;
4725 case NS_VK_PAGE_DOWN:
4726 newValue = value - std::max(step, (maximum - minimum) / Decimal(10));
4727 break;
4728 }
4729 SetValueOfRangeForUserEvent(newValue);
4730 FireChangeEventIfNeeded();
4731 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4732 }
4733 }
4734
4735 } break; // eKeyPress || eKeyUp
4736
4737 case eMouseDown:
4738 case eMouseUp:
4739 case eMouseDoubleClick: {
4740 // cancel all of these events for buttons
4741 //XXXsmaug Why?
4742 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
4743 if (mouseEvent->button == WidgetMouseEvent::eMiddleButton ||
4744 mouseEvent->button == WidgetMouseEvent::eRightButton) {
4745 if (mType == NS_FORM_INPUT_BUTTON ||
4746 mType == NS_FORM_INPUT_RESET ||
4747 mType == NS_FORM_INPUT_SUBMIT) {
4748 if (aVisitor.mDOMEvent) {
4749 aVisitor.mDOMEvent->StopPropagation();
4750 } else {
4751 rv = NS_ERROR_FAILURE;
4752 }
4753 }
4754 }
4755 if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
4756 if (mouseEvent->button == WidgetMouseEvent::eLeftButton &&
4757 !IgnoreInputEventWithModifier(mouseEvent)) {
4758 nsNumberControlFrame* numberControlFrame =
4759 do_QueryFrame(GetPrimaryFrame());
4760 if (numberControlFrame) {
4761 if (aVisitor.mEvent->mMessage == eMouseDown &&
4762 IsMutable()) {
4763 switch (numberControlFrame->GetSpinButtonForPointerEvent(
4764 aVisitor.mEvent->AsMouseEvent())) {
4765 case nsNumberControlFrame::eSpinButtonUp:
4766 StepNumberControlForUserEvent(1);
4767 mNumberControlSpinnerSpinsUp = true;
4768 StartNumberControlSpinnerSpin();
4769 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4770 break;
4771 case nsNumberControlFrame::eSpinButtonDown:
4772 StepNumberControlForUserEvent(-1);
4773 mNumberControlSpinnerSpinsUp = false;
4774 StartNumberControlSpinnerSpin();
4775 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4776 break;
4777 }
4778 }
4779 }
4780 }
4781 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
4782 // We didn't handle this to step up/down. Whatever this was, be
4783 // aggressive about stopping the spin. (And don't set
4784 // nsEventStatus_eConsumeNoDefault after doing so, since that
4785 // might prevent, say, the context menu from opening.)
4786 StopNumberControlSpinnerSpin();
4787 }
4788 }
4789 break;
4790 }
4791 #if !defined(ANDROID) && !defined(XP_MACOSX)
4792 case eWheel: {
4793 // Handle wheel events as increasing / decreasing the input element's
4794 // value when it's focused and it's type is number or range.
4795 WidgetWheelEvent* wheelEvent = aVisitor.mEvent->AsWheelEvent();
4796 if (!aVisitor.mEvent->DefaultPrevented() &&
4797 aVisitor.mEvent->IsTrusted() && IsMutable() && wheelEvent &&
4798 wheelEvent->mDeltaY != 0 &&
4799 wheelEvent->mDeltaMode != nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
4800 if (mType == NS_FORM_INPUT_NUMBER) {
4801 nsNumberControlFrame* numberControlFrame =
4802 do_QueryFrame(GetPrimaryFrame());
4803 if (numberControlFrame && numberControlFrame->IsFocused()) {
4804 StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
4805 FireChangeEventIfNeeded();
4806 aVisitor.mEvent->PreventDefault();
4807 }
4808 } else if (mType == NS_FORM_INPUT_RANGE &&
4809 nsContentUtils::IsFocusedContent(this) &&
4810 GetMinimum() < GetMaximum()) {
4811 Decimal value = GetValueAsDecimal();
4812 Decimal step = GetStep();
4813 if (step == kStepAny) {
4814 step = GetDefaultStep();
4815 }
4816 MOZ_ASSERT(value.isFinite() && step.isFinite());
4817 SetValueOfRangeForUserEvent(wheelEvent->mDeltaY < 0 ?
4818 value + step : value - step);
4819 FireChangeEventIfNeeded();
4820 aVisitor.mEvent->PreventDefault();
4821 }
4822 }
4823 break;
4824 }
4825 #endif
4826 default:
4827 break;
4828 }
4829
4830 if (outerActivateEvent) {
4831 if (mForm && (oldType == NS_FORM_INPUT_SUBMIT ||
4832 oldType == NS_FORM_INPUT_IMAGE)) {
4833 if (mType != NS_FORM_INPUT_SUBMIT && mType != NS_FORM_INPUT_IMAGE) {
4834 // If the type has changed to a non-submit type, then we want to
4835 // flush the stored submission if there is one (as if the submit()
4836 // was allowed to succeed)
4837 mForm->FlushPendingSubmission();
4838 }
4839 }
4840 switch(mType) {
4841 case NS_FORM_INPUT_RESET:
4842 case NS_FORM_INPUT_SUBMIT:
4843 case NS_FORM_INPUT_IMAGE:
4844 if (mForm) {
4845 InternalFormEvent event(true,
4846 (mType == NS_FORM_INPUT_RESET) ? eFormReset : eFormSubmit);
4847 event.mOriginator = this;
4848 nsEventStatus status = nsEventStatus_eIgnore;
4849
4850 nsCOMPtr<nsIPresShell> presShell =
4851 aVisitor.mPresContext->GetPresShell();
4852
4853 // If |nsIPresShell::Destroy| has been called due to
4854 // handling the event the pres context will return a null
4855 // pres shell. See bug 125624.
4856 // TODO: removing this code and have the submit event sent by the
4857 // form, see bug 592124.
4858 if (presShell && (event.mMessage != eFormSubmit ||
4859 mForm->SubmissionCanProceed(this))) {
4860 // Hold a strong ref while dispatching
4861 RefPtr<mozilla::dom::HTMLFormElement> form(mForm);
4862 presShell->HandleDOMEventWithTarget(form, &event, &status);
4863 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4864 }
4865 }
4866 break;
4867
4868 default:
4869 break;
4870 } //switch
4871 } //click or outer activate event
4872 } else if (outerActivateEvent &&
4873 (oldType == NS_FORM_INPUT_SUBMIT ||
4874 oldType == NS_FORM_INPUT_IMAGE) &&
4875 mForm) {
4876 // tell the form to flush a possible pending submission.
4877 // the reason is that the script returned false (the event was
4878 // not ignored) so if there is a stored submission, it needs to
4879 // be submitted immediately.
4880 mForm->FlushPendingSubmission();
4881 }
4882 } // if
4883
4884 if (NS_SUCCEEDED(rv) && mType == NS_FORM_INPUT_RANGE) {
4885 PostHandleEventForRangeThumb(aVisitor);
4886 }
4887
4888 return MaybeInitPickers(aVisitor);
4889 }
4890
4891 void
PostHandleEventForRangeThumb(EventChainPostVisitor & aVisitor)4892 HTMLInputElement::PostHandleEventForRangeThumb(EventChainPostVisitor& aVisitor)
4893 {
4894 MOZ_ASSERT(mType == NS_FORM_INPUT_RANGE);
4895
4896 if (nsEventStatus_eConsumeNoDefault == aVisitor.mEventStatus ||
4897 !(aVisitor.mEvent->mClass == eMouseEventClass ||
4898 aVisitor.mEvent->mClass == eTouchEventClass ||
4899 aVisitor.mEvent->mClass == eKeyboardEventClass)) {
4900 return;
4901 }
4902
4903 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4904 if (!rangeFrame && mIsDraggingRange) {
4905 CancelRangeThumbDrag();
4906 return;
4907 }
4908
4909 switch (aVisitor.mEvent->mMessage)
4910 {
4911 case eMouseDown:
4912 case eTouchStart: {
4913 if (mIsDraggingRange) {
4914 break;
4915 }
4916 if (nsIPresShell::GetCapturingContent()) {
4917 break; // don't start drag if someone else is already capturing
4918 }
4919 WidgetInputEvent* inputEvent = aVisitor.mEvent->AsInputEvent();
4920 if (IgnoreInputEventWithModifier(inputEvent)) {
4921 break; // ignore
4922 }
4923 if (aVisitor.mEvent->mMessage == eMouseDown) {
4924 if (aVisitor.mEvent->AsMouseEvent()->buttons ==
4925 WidgetMouseEvent::eLeftButtonFlag) {
4926 StartRangeThumbDrag(inputEvent);
4927 } else if (mIsDraggingRange) {
4928 CancelRangeThumbDrag();
4929 }
4930 } else {
4931 if (aVisitor.mEvent->AsTouchEvent()->mTouches.Length() == 1) {
4932 StartRangeThumbDrag(inputEvent);
4933 } else if (mIsDraggingRange) {
4934 CancelRangeThumbDrag();
4935 }
4936 }
4937 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4938 } break;
4939
4940 case eMouseMove:
4941 case eTouchMove:
4942 if (!mIsDraggingRange) {
4943 break;
4944 }
4945 if (nsIPresShell::GetCapturingContent() != this) {
4946 // Someone else grabbed capture.
4947 CancelRangeThumbDrag();
4948 break;
4949 }
4950 SetValueOfRangeForUserEvent(
4951 rangeFrame->GetValueAtEventPoint(aVisitor.mEvent->AsInputEvent()));
4952 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4953 break;
4954
4955 case eMouseUp:
4956 case eTouchEnd:
4957 if (!mIsDraggingRange) {
4958 break;
4959 }
4960 // We don't check to see whether we are the capturing content here and
4961 // call CancelRangeThumbDrag() if that is the case. We just finish off
4962 // the drag and set our final value (unless someone has called
4963 // preventDefault() and prevents us getting here).
4964 FinishRangeThumbDrag(aVisitor.mEvent->AsInputEvent());
4965 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4966 break;
4967
4968 case eKeyPress:
4969 if (mIsDraggingRange &&
4970 aVisitor.mEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
4971 CancelRangeThumbDrag();
4972 }
4973 break;
4974
4975 case eTouchCancel:
4976 if (mIsDraggingRange) {
4977 CancelRangeThumbDrag();
4978 }
4979 break;
4980
4981 default:
4982 break;
4983 }
4984 }
4985
4986 void
MaybeLoadImage()4987 HTMLInputElement::MaybeLoadImage()
4988 {
4989 // Our base URI may have changed; claim that our URI changed, and the
4990 // nsImageLoadingContent will decide whether a new image load is warranted.
4991 nsAutoString uri;
4992 if (mType == NS_FORM_INPUT_IMAGE &&
4993 GetAttr(kNameSpaceID_None, nsGkAtoms::src, uri) &&
4994 (NS_FAILED(LoadImage(uri, false, true, eImageLoadType_Normal)) ||
4995 !LoadingEnabled())) {
4996 CancelImageRequests(true);
4997 }
4998 }
4999
5000 nsresult
BindToTree(nsIDocument * aDocument,nsIContent * aParent,nsIContent * aBindingParent,bool aCompileEventHandlers)5001 HTMLInputElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
5002 nsIContent* aBindingParent,
5003 bool aCompileEventHandlers)
5004 {
5005 nsresult rv = nsGenericHTMLFormElementWithState::BindToTree(aDocument, aParent,
5006 aBindingParent,
5007 aCompileEventHandlers);
5008 NS_ENSURE_SUCCESS(rv, rv);
5009
5010 nsImageLoadingContent::BindToTree(aDocument, aParent, aBindingParent,
5011 aCompileEventHandlers);
5012
5013 if (mType == NS_FORM_INPUT_IMAGE) {
5014 // Our base URI may have changed; claim that our URI changed, and the
5015 // nsImageLoadingContent will decide whether a new image load is warranted.
5016 if (HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
5017 // FIXME: Bug 660963 it would be nice if we could just have
5018 // ClearBrokenState update our state and do it fast...
5019 ClearBrokenState();
5020 RemoveStatesSilently(NS_EVENT_STATE_BROKEN);
5021 nsContentUtils::AddScriptRunner(
5022 NewRunnableMethod(this, &HTMLInputElement::MaybeLoadImage));
5023 }
5024 }
5025
5026 // Add radio to document if we don't have a form already (if we do it's
5027 // already been added into that group)
5028 if (aDocument && !mForm && mType == NS_FORM_INPUT_RADIO) {
5029 AddedToRadioGroup();
5030 }
5031
5032 // Set direction based on value if dir=auto
5033 SetDirectionIfAuto(HasDirAuto(), false);
5034
5035 // An element can't suffer from value missing if it is not in a document.
5036 // We have to check if we suffer from that as we are now in a document.
5037 UpdateValueMissingValidityState();
5038
5039 // If there is a disabled fieldset in the parent chain, the element is now
5040 // barred from constraint validation and can't suffer from value missing
5041 // (call done before).
5042 UpdateBarredFromConstraintValidation();
5043
5044 // And now make sure our state is up to date
5045 UpdateState(false);
5046
5047 if (mType == NS_FORM_INPUT_PASSWORD) {
5048 if (IsInComposedDoc()) {
5049 AsyncEventDispatcher* dispatcher =
5050 new AsyncEventDispatcher(this,
5051 NS_LITERAL_STRING("DOMInputPasswordAdded"),
5052 true,
5053 true);
5054 dispatcher->PostDOMEvent();
5055 }
5056
5057 #ifdef EARLY_BETA_OR_EARLIER
5058 Telemetry::Accumulate(Telemetry::PWMGR_PASSWORD_INPUT_IN_FORM, !!mForm);
5059 #endif
5060 }
5061
5062 return rv;
5063 }
5064
5065 void
UnbindFromTree(bool aDeep,bool aNullParent)5066 HTMLInputElement::UnbindFromTree(bool aDeep, bool aNullParent)
5067 {
5068 // If we have a form and are unbound from it,
5069 // nsGenericHTMLFormElementWithState::UnbindFromTree() will unset the form and
5070 // that takes care of form's WillRemove so we just have to take care
5071 // of the case where we're removing from the document and we don't
5072 // have a form
5073 if (!mForm && mType == NS_FORM_INPUT_RADIO) {
5074 WillRemoveFromRadioGroup();
5075 }
5076
5077 nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
5078 nsGenericHTMLFormElementWithState::UnbindFromTree(aDeep, aNullParent);
5079
5080 // GetCurrentDoc is returning nullptr so we can update the value
5081 // missing validity state to reflect we are no longer into a doc.
5082 UpdateValueMissingValidityState();
5083 // We might be no longer disabled because of parent chain changed.
5084 UpdateBarredFromConstraintValidation();
5085
5086 // And now make sure our state is up to date
5087 UpdateState(false);
5088 }
5089
5090 void
HandleTypeChange(uint8_t aNewType)5091 HTMLInputElement::HandleTypeChange(uint8_t aNewType)
5092 {
5093 if (mType == NS_FORM_INPUT_RANGE && mIsDraggingRange) {
5094 CancelRangeThumbDrag(false);
5095 }
5096
5097 ValueModeType aOldValueMode = GetValueMode();
5098 uint8_t oldType = mType;
5099 nsAutoString aOldValue;
5100
5101 if (aOldValueMode == VALUE_MODE_VALUE) {
5102 GetValue(aOldValue);
5103 }
5104
5105 nsTextEditorState::SelectionProperties sp;
5106
5107 if (GetEditorState()) {
5108 sp = mInputData.mState->GetSelectionProperties();
5109 }
5110
5111 // We already have a copy of the value, lets free it and changes the type.
5112 FreeData();
5113 mType = aNewType;
5114
5115 if (IsSingleLineTextControl()) {
5116
5117 mInputData.mState = new nsTextEditorState(this);
5118 if (!sp.IsDefault()) {
5119 mInputData.mState->SetSelectionProperties(sp);
5120 }
5121 }
5122
5123 /**
5124 * The following code is trying to reproduce the algorithm described here:
5125 * http://www.whatwg.org/specs/web-apps/current-work/complete.html#input-type-change
5126 */
5127 switch (GetValueMode()) {
5128 case VALUE_MODE_DEFAULT:
5129 case VALUE_MODE_DEFAULT_ON:
5130 // If the previous value mode was value, we need to set the value content
5131 // attribute to the previous value.
5132 // There is no value sanitizing algorithm for elements in this mode.
5133 if (aOldValueMode == VALUE_MODE_VALUE && !aOldValue.IsEmpty()) {
5134 SetAttr(kNameSpaceID_None, nsGkAtoms::value, aOldValue, true);
5135 }
5136 break;
5137 case VALUE_MODE_VALUE:
5138 // If the previous value mode wasn't value, we have to set the value to
5139 // the value content attribute.
5140 // SetValueInternal is going to sanitize the value.
5141 {
5142 nsAutoString value;
5143 if (aOldValueMode != VALUE_MODE_VALUE) {
5144 GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
5145 } else {
5146 value = aOldValue;
5147 }
5148 // TODO: What should we do if SetValueInternal fails? (The allocation
5149 // may potentially be big, but most likely we've failed to allocate
5150 // before the type change.)
5151 SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
5152 }
5153 break;
5154 case VALUE_MODE_FILENAME:
5155 default:
5156 // We don't care about the value.
5157 // There is no value sanitizing algorithm for elements in this mode.
5158 break;
5159 }
5160
5161 // Updating mFocusedValue in consequence:
5162 // If the new type fires a change event on blur, but the previous type
5163 // doesn't, we should set mFocusedValue to the current value.
5164 // Otherwise, if the new type doesn't fire a change event on blur, but the
5165 // previous type does, we should clear out mFocusedValue.
5166 if (MayFireChangeOnBlur(mType) && !MayFireChangeOnBlur(oldType)) {
5167 GetValue(mFocusedValue);
5168 } else if (!IsSingleLineTextControl(false, mType) &&
5169 IsSingleLineTextControl(false, oldType)) {
5170 mFocusedValue.Truncate();
5171 }
5172
5173 UpdateHasRange();
5174
5175 // Do not notify, it will be done after if needed.
5176 UpdateAllValidityStates(false);
5177
5178 UpdateApzAwareFlag();
5179 }
5180
5181 void
SanitizeValue(nsAString & aValue)5182 HTMLInputElement::SanitizeValue(nsAString& aValue)
5183 {
5184 NS_ASSERTION(mDoneCreating, "The element creation should be finished!");
5185
5186 switch (mType) {
5187 case NS_FORM_INPUT_TEXT:
5188 case NS_FORM_INPUT_SEARCH:
5189 case NS_FORM_INPUT_TEL:
5190 case NS_FORM_INPUT_PASSWORD:
5191 {
5192 char16_t crlf[] = { char16_t('\r'), char16_t('\n'), 0 };
5193 aValue.StripChars(crlf);
5194 }
5195 break;
5196 case NS_FORM_INPUT_EMAIL:
5197 case NS_FORM_INPUT_URL:
5198 {
5199 char16_t crlf[] = { char16_t('\r'), char16_t('\n'), 0 };
5200 aValue.StripChars(crlf);
5201
5202 aValue = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(aValue);
5203 }
5204 break;
5205 case NS_FORM_INPUT_NUMBER:
5206 {
5207 Decimal value;
5208 bool ok = ConvertStringToNumber(aValue, value);
5209 if (!ok) {
5210 aValue.Truncate();
5211 }
5212 }
5213 break;
5214 case NS_FORM_INPUT_RANGE:
5215 {
5216 Decimal minimum = GetMinimum();
5217 Decimal maximum = GetMaximum();
5218 MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
5219 "type=range should have a default maximum/minimum");
5220
5221 // We use this to avoid modifying the string unnecessarily, since that
5222 // may introduce rounding. This is set to true only if the value we
5223 // parse out from aValue needs to be sanitized.
5224 bool needSanitization = false;
5225
5226 Decimal value;
5227 bool ok = ConvertStringToNumber(aValue, value);
5228 if (!ok) {
5229 needSanitization = true;
5230 // Set value to midway between minimum and maximum.
5231 value = maximum <= minimum ? minimum : minimum + (maximum - minimum)/Decimal(2);
5232 } else if (value < minimum || maximum < minimum) {
5233 needSanitization = true;
5234 value = minimum;
5235 } else if (value > maximum) {
5236 needSanitization = true;
5237 value = maximum;
5238 }
5239
5240 Decimal step = GetStep();
5241 if (step != kStepAny) {
5242 Decimal stepBase = GetStepBase();
5243 // There could be rounding issues below when dealing with fractional
5244 // numbers, but let's ignore that until ECMAScript supplies us with a
5245 // decimal number type.
5246 Decimal deltaToStep = NS_floorModulo(value - stepBase, step);
5247 if (deltaToStep != Decimal(0)) {
5248 // "suffering from a step mismatch"
5249 // Round the element's value to the nearest number for which the
5250 // element would not suffer from a step mismatch, and which is
5251 // greater than or equal to the minimum, and, if the maximum is not
5252 // less than the minimum, which is less than or equal to the
5253 // maximum, if there is a number that matches these constraints:
5254 MOZ_ASSERT(deltaToStep > Decimal(0), "stepBelow/stepAbove will be wrong");
5255 Decimal stepBelow = value - deltaToStep;
5256 Decimal stepAbove = value - deltaToStep + step;
5257 Decimal halfStep = step / Decimal(2);
5258 bool stepAboveIsClosest = (stepAbove - value) <= halfStep;
5259 bool stepAboveInRange = stepAbove >= minimum &&
5260 stepAbove <= maximum;
5261 bool stepBelowInRange = stepBelow >= minimum &&
5262 stepBelow <= maximum;
5263
5264 if ((stepAboveIsClosest || !stepBelowInRange) && stepAboveInRange) {
5265 needSanitization = true;
5266 value = stepAbove;
5267 } else if ((!stepAboveIsClosest || !stepAboveInRange) && stepBelowInRange) {
5268 needSanitization = true;
5269 value = stepBelow;
5270 }
5271 }
5272 }
5273
5274 if (needSanitization) {
5275 char buf[32];
5276 DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
5277 aValue.AssignASCII(buf);
5278 MOZ_ASSERT(ok, "buf not big enough");
5279 }
5280 }
5281 break;
5282 case NS_FORM_INPUT_DATE:
5283 {
5284 if (!aValue.IsEmpty() && !IsValidDate(aValue)) {
5285 aValue.Truncate();
5286 }
5287 }
5288 break;
5289 case NS_FORM_INPUT_TIME:
5290 {
5291 if (!aValue.IsEmpty() && !IsValidTime(aValue)) {
5292 aValue.Truncate();
5293 }
5294 }
5295 break;
5296 case NS_FORM_INPUT_MONTH:
5297 {
5298 if (!aValue.IsEmpty() && !IsValidMonth(aValue)) {
5299 aValue.Truncate();
5300 }
5301 }
5302 break;
5303 case NS_FORM_INPUT_WEEK:
5304 {
5305 if (!aValue.IsEmpty() && !IsValidWeek(aValue)) {
5306 aValue.Truncate();
5307 }
5308 }
5309 break;
5310 case NS_FORM_INPUT_DATETIME_LOCAL:
5311 {
5312 if (!aValue.IsEmpty() && !IsValidDateTimeLocal(aValue)) {
5313 aValue.Truncate();
5314 } else {
5315 NormalizeDateTimeLocal(aValue);
5316 }
5317 }
5318 break;
5319 case NS_FORM_INPUT_COLOR:
5320 {
5321 if (IsValidSimpleColor(aValue)) {
5322 ToLowerCase(aValue);
5323 } else {
5324 // Set default (black) color, if aValue wasn't parsed correctly.
5325 aValue.AssignLiteral("#000000");
5326 }
5327 }
5328 break;
5329 }
5330 }
5331
IsValidSimpleColor(const nsAString & aValue) const5332 bool HTMLInputElement::IsValidSimpleColor(const nsAString& aValue) const
5333 {
5334 if (aValue.Length() != 7 || aValue.First() != '#') {
5335 return false;
5336 }
5337
5338 for (int i = 1; i < 7; ++i) {
5339 if (!nsCRT::IsAsciiDigit(aValue[i]) &&
5340 !(aValue[i] >= 'a' && aValue[i] <= 'f') &&
5341 !(aValue[i] >= 'A' && aValue[i] <= 'F')) {
5342 return false;
5343 }
5344 }
5345 return true;
5346 }
5347
5348 bool
IsLeapYear(uint32_t aYear) const5349 HTMLInputElement::IsLeapYear(uint32_t aYear) const
5350 {
5351 if ((aYear % 4 == 0 && aYear % 100 != 0) || ( aYear % 400 == 0)) {
5352 return true;
5353 }
5354 return false;
5355 }
5356
5357 uint32_t
DayOfWeek(uint32_t aYear,uint32_t aMonth,uint32_t aDay,bool isoWeek) const5358 HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay,
5359 bool isoWeek) const
5360 {
5361 // Tomohiko Sakamoto algorithm.
5362 int monthTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
5363 aYear -= aMonth < 3;
5364
5365 uint32_t day = (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
5366 monthTable[aMonth - 1] + aDay) % 7;
5367
5368 if (isoWeek) {
5369 return ((day + 6) % 7) + 1;
5370 }
5371
5372 return day;
5373 }
5374
5375 uint32_t
MaximumWeekInYear(uint32_t aYear) const5376 HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const
5377 {
5378 int day = DayOfWeek(aYear, 1, 1, true); // January 1.
5379 // A year starting on Thursday or a leap year starting on Wednesday has 53
5380 // weeks. All other years have 52 weeks.
5381 return day == 4 || (day == 3 && IsLeapYear(aYear)) ?
5382 kMaximumWeekInYear : kMaximumWeekInYear - 1;
5383 }
5384
5385 bool
IsValidWeek(const nsAString & aValue) const5386 HTMLInputElement::IsValidWeek(const nsAString& aValue) const
5387 {
5388 uint32_t year, week;
5389 return ParseWeek(aValue, &year, &week);
5390 }
5391
5392 bool
IsValidMonth(const nsAString & aValue) const5393 HTMLInputElement::IsValidMonth(const nsAString& aValue) const
5394 {
5395 uint32_t year, month;
5396 return ParseMonth(aValue, &year, &month);
5397 }
5398
5399 bool
IsValidDate(const nsAString & aValue) const5400 HTMLInputElement::IsValidDate(const nsAString& aValue) const
5401 {
5402 uint32_t year, month, day;
5403 return ParseDate(aValue, &year, &month, &day);
5404 }
5405
5406 bool
IsValidDateTimeLocal(const nsAString & aValue) const5407 HTMLInputElement::IsValidDateTimeLocal(const nsAString& aValue) const
5408 {
5409 uint32_t year, month, day, time;
5410 return ParseDateTimeLocal(aValue, &year, &month, &day, &time);
5411 }
5412
5413 bool
ParseYear(const nsAString & aValue,uint32_t * aYear) const5414 HTMLInputElement::ParseYear(const nsAString& aValue, uint32_t* aYear) const
5415 {
5416 if (aValue.Length() < 4) {
5417 return false;
5418 }
5419
5420 return DigitSubStringToNumber(aValue, 0, aValue.Length(), aYear) &&
5421 *aYear > 0;
5422 }
5423
5424 bool
ParseMonth(const nsAString & aValue,uint32_t * aYear,uint32_t * aMonth) const5425 HTMLInputElement::ParseMonth(const nsAString& aValue, uint32_t* aYear,
5426 uint32_t* aMonth) const
5427 {
5428 // Parse the year, month values out a string formatted as 'yyyy-mm'.
5429 if (aValue.Length() < 7) {
5430 return false;
5431 }
5432
5433 uint32_t endOfYearOffset = aValue.Length() - 3;
5434 if (aValue[endOfYearOffset] != '-') {
5435 return false;
5436 }
5437
5438 const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
5439 if (!ParseYear(yearStr, aYear)) {
5440 return false;
5441 }
5442
5443 return DigitSubStringToNumber(aValue, endOfYearOffset + 1, 2, aMonth) &&
5444 *aMonth > 0 && *aMonth <= 12;
5445 }
5446
5447 bool
ParseWeek(const nsAString & aValue,uint32_t * aYear,uint32_t * aWeek) const5448 HTMLInputElement::ParseWeek(const nsAString& aValue, uint32_t* aYear,
5449 uint32_t* aWeek) const
5450 {
5451 // Parse the year, month values out a string formatted as 'yyyy-Www'.
5452 if (aValue.Length() < 8) {
5453 return false;
5454 }
5455
5456 uint32_t endOfYearOffset = aValue.Length() - 4;
5457 if (aValue[endOfYearOffset] != '-') {
5458 return false;
5459 }
5460
5461 if (aValue[endOfYearOffset + 1] != 'W') {
5462 return false;
5463 }
5464
5465 const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
5466 if (!ParseYear(yearStr, aYear)) {
5467 return false;
5468 }
5469
5470 return DigitSubStringToNumber(aValue, endOfYearOffset + 2, 2, aWeek) &&
5471 *aWeek > 0 && *aWeek <= MaximumWeekInYear(*aYear);
5472
5473 }
5474
5475 bool
ParseDate(const nsAString & aValue,uint32_t * aYear,uint32_t * aMonth,uint32_t * aDay) const5476 HTMLInputElement::ParseDate(const nsAString& aValue, uint32_t* aYear,
5477 uint32_t* aMonth, uint32_t* aDay) const
5478 {
5479 /*
5480 * Parse the year, month, day values out a date string formatted as 'yyyy-mm-dd'.
5481 * -The year must be 4 or more digits long, and year > 0
5482 * -The month must be exactly 2 digits long, and 01 <= month <= 12
5483 * -The day must be exactly 2 digit long, and 01 <= day <= maxday
5484 * Where maxday is the number of days in the month 'month' and year 'year'
5485 */
5486 if (aValue.Length() < 10) {
5487 return false;
5488 }
5489
5490 uint32_t endOfMonthOffset = aValue.Length() - 3;
5491 if (aValue[endOfMonthOffset] != '-') {
5492 return false;
5493 }
5494
5495 const nsAString& yearMonthStr = Substring(aValue, 0, endOfMonthOffset);
5496 if (!ParseMonth(yearMonthStr, aYear, aMonth)) {
5497 return false;
5498 }
5499
5500 return DigitSubStringToNumber(aValue, endOfMonthOffset + 1, 2, aDay) &&
5501 *aDay > 0 && *aDay <= NumberOfDaysInMonth(*aMonth, *aYear);
5502 }
5503
5504 bool
ParseDateTimeLocal(const nsAString & aValue,uint32_t * aYear,uint32_t * aMonth,uint32_t * aDay,uint32_t * aTime) const5505 HTMLInputElement::ParseDateTimeLocal(const nsAString& aValue, uint32_t* aYear,
5506 uint32_t* aMonth, uint32_t* aDay,
5507 uint32_t* aTime) const
5508 {
5509 // Parse the year, month, day and time values out a string formatted as
5510 // 'yyyy-mm-ddThh:mm[:ss.s] or 'yyyy-mm-dd hh:mm[:ss.s]', where fractions of
5511 // seconds can be 1 to 3 digits.
5512 // The minimum length allowed is 16, which is of the form 'yyyy-mm-ddThh:mm'
5513 // or 'yyyy-mm-dd hh:mm'.
5514 if (aValue.Length() < 16) {
5515 return false;
5516 }
5517
5518 const uint32_t sepIndex = 10;
5519 if (aValue[sepIndex] != 'T' && aValue[sepIndex] != ' ') {
5520 return false;
5521 }
5522
5523 const nsAString& dateStr = Substring(aValue, 0, sepIndex);
5524 if (!ParseDate(dateStr, aYear, aMonth, aDay)) {
5525 return false;
5526 }
5527
5528 const nsAString& timeStr = Substring(aValue, sepIndex + 1,
5529 aValue.Length() - sepIndex + 1);
5530 if (!ParseTime(timeStr, aTime)) {
5531 return false;
5532 }
5533
5534 return true;
5535 }
5536
5537 void
NormalizeDateTimeLocal(nsAString & aValue) const5538 HTMLInputElement::NormalizeDateTimeLocal(nsAString& aValue) const
5539 {
5540 if (aValue.IsEmpty()) {
5541 return;
5542 }
5543
5544 // Use 'T' as the separator between date string and time string.
5545 const uint32_t sepIndex = 10;
5546 if (aValue[sepIndex] == ' ') {
5547 aValue.Replace(sepIndex, 1, NS_LITERAL_STRING("T"));
5548 }
5549
5550 // Time expressed as the shortest possible string.
5551 if (aValue.Length() == 16) {
5552 return;
5553 }
5554
5555 // Fractions of seconds part is optional, ommit it if it's 0.
5556 if (aValue.Length() > 19) {
5557 uint32_t milliseconds;
5558 if (!DigitSubStringToNumber(aValue, 20, aValue.Length() - 20,
5559 &milliseconds)) {
5560 return;
5561 }
5562
5563 if (milliseconds != 0) {
5564 return;
5565 }
5566
5567 aValue.Cut(19, aValue.Length() - 19);
5568 }
5569
5570 // Seconds part is optional, ommit it if it's 0.
5571 uint32_t seconds;
5572 if (!DigitSubStringToNumber(aValue, 17, aValue.Length() - 17, &seconds)) {
5573 return;
5574 }
5575
5576 if (seconds != 0) {
5577 return;
5578 }
5579
5580 aValue.Cut(16, aValue.Length() - 16);
5581 }
5582
5583 double
DaysSinceEpochFromWeek(uint32_t aYear,uint32_t aWeek) const5584 HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear, uint32_t aWeek) const
5585 {
5586 double days = JS::DayFromYear(aYear) + (aWeek - 1) * 7;
5587 uint32_t dayOneIsoWeekday = DayOfWeek(aYear, 1, 1, true);
5588
5589 // If day one of that year is on/before Thursday, we should subtract the
5590 // days that belong to last year in our first week, otherwise, our first
5591 // days belong to last year's last week, and we should add those days
5592 // back.
5593 if (dayOneIsoWeekday <= 4) {
5594 days -= (dayOneIsoWeekday - 1);
5595 } else {
5596 days += (7 - dayOneIsoWeekday + 1);
5597 }
5598
5599 return days;
5600 }
5601
5602 uint32_t
NumberOfDaysInMonth(uint32_t aMonth,uint32_t aYear) const5603 HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const
5604 {
5605 /*
5606 * Returns the number of days in a month.
5607 * Months that are |longMonths| always have 31 days.
5608 * Months that are not |longMonths| have 30 days except February (month 2).
5609 * February has 29 days during leap years which are years that are divisible by 400.
5610 * or divisible by 100 and 4. February has 28 days otherwise.
5611 */
5612
5613 static const bool longMonths[] = { true, false, true, false, true, false,
5614 true, true, false, true, false, true };
5615 MOZ_ASSERT(aMonth <= 12 && aMonth > 0);
5616
5617 if (longMonths[aMonth-1]) {
5618 return 31;
5619 }
5620
5621 if (aMonth != 2) {
5622 return 30;
5623 }
5624
5625 return IsLeapYear(aYear) ? 29 : 28;
5626 }
5627
5628 /* static */ bool
DigitSubStringToNumber(const nsAString & aStr,uint32_t aStart,uint32_t aLen,uint32_t * aRetVal)5629 HTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
5630 uint32_t aStart, uint32_t aLen,
5631 uint32_t* aRetVal)
5632 {
5633 MOZ_ASSERT(aStr.Length() > (aStart + aLen - 1));
5634
5635 for (uint32_t offset = 0; offset < aLen; ++offset) {
5636 if (!NS_IsAsciiDigit(aStr[aStart + offset])) {
5637 return false;
5638 }
5639 }
5640
5641 nsresult ec;
5642 *aRetVal = static_cast<uint32_t>(PromiseFlatString(Substring(aStr, aStart, aLen)).ToInteger(&ec));
5643
5644 return NS_SUCCEEDED(ec);
5645 }
5646
5647 bool
IsValidTime(const nsAString & aValue) const5648 HTMLInputElement::IsValidTime(const nsAString& aValue) const
5649 {
5650 return ParseTime(aValue, nullptr);
5651 }
5652
5653 /* static */ bool
ParseTime(const nsAString & aValue,uint32_t * aResult)5654 HTMLInputElement::ParseTime(const nsAString& aValue, uint32_t* aResult)
5655 {
5656 /* The string must have the following parts:
5657 * - HOURS: two digits, value being in [0, 23];
5658 * - Colon (:);
5659 * - MINUTES: two digits, value being in [0, 59];
5660 * - Optional:
5661 * - Colon (:);
5662 * - SECONDS: two digits, value being in [0, 59];
5663 * - Optional:
5664 * - DOT (.);
5665 * - FRACTIONAL SECONDS: one to three digits, no value range.
5666 */
5667
5668 // The following format is the shorter one allowed: "HH:MM".
5669 if (aValue.Length() < 5) {
5670 return false;
5671 }
5672
5673 uint32_t hours;
5674 if (!DigitSubStringToNumber(aValue, 0, 2, &hours) || hours > 23) {
5675 return false;
5676 }
5677
5678 // Hours/minutes separator.
5679 if (aValue[2] != ':') {
5680 return false;
5681 }
5682
5683 uint32_t minutes;
5684 if (!DigitSubStringToNumber(aValue, 3, 2, &minutes) || minutes > 59) {
5685 return false;
5686 }
5687
5688 if (aValue.Length() == 5) {
5689 if (aResult) {
5690 *aResult = ((hours * 60) + minutes) * 60000;
5691 }
5692 return true;
5693 }
5694
5695 // The following format is the next shorter one: "HH:MM:SS".
5696 if (aValue.Length() < 8 || aValue[5] != ':') {
5697 return false;
5698 }
5699
5700 uint32_t seconds;
5701 if (!DigitSubStringToNumber(aValue, 6, 2, &seconds) || seconds > 59) {
5702 return false;
5703 }
5704
5705 if (aValue.Length() == 8) {
5706 if (aResult) {
5707 *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000;
5708 }
5709 return true;
5710 }
5711
5712 // The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
5713 // There can be 1 to 3 digits for the fractions of seconds.
5714 if (aValue.Length() == 9 || aValue.Length() > 12 || aValue[8] != '.') {
5715 return false;
5716 }
5717
5718 uint32_t fractionsSeconds;
5719 if (!DigitSubStringToNumber(aValue, 9, aValue.Length() - 9, &fractionsSeconds)) {
5720 return false;
5721 }
5722
5723 if (aResult) {
5724 *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000 +
5725 // NOTE: there is 10.0 instead of 10 and static_cast<int> because
5726 // some old [and stupid] compilers can't just do the right thing.
5727 fractionsSeconds * pow(10.0, static_cast<int>(3 - (aValue.Length() - 9)));
5728 }
5729
5730 return true;
5731 }
5732
5733 static bool
IsDateTimeEnabled(int32_t aNewType)5734 IsDateTimeEnabled(int32_t aNewType)
5735 {
5736 return (aNewType == NS_FORM_INPUT_DATE &&
5737 (Preferences::GetBool("dom.forms.datetime", false) ||
5738 Preferences::GetBool("dom.experimental_forms", false) ||
5739 Preferences::GetBool("dom.forms.datepicker", false))) ||
5740 (aNewType == NS_FORM_INPUT_TIME &&
5741 (Preferences::GetBool("dom.forms.datetime", false) ||
5742 Preferences::GetBool("dom.experimental_forms", false))) ||
5743 ((aNewType == NS_FORM_INPUT_MONTH ||
5744 aNewType == NS_FORM_INPUT_WEEK ||
5745 aNewType == NS_FORM_INPUT_DATETIME_LOCAL) &&
5746 Preferences::GetBool("dom.forms.datetime", false));
5747 }
5748
5749 bool
ParseAttribute(int32_t aNamespaceID,nsIAtom * aAttribute,const nsAString & aValue,nsAttrValue & aResult)5750 HTMLInputElement::ParseAttribute(int32_t aNamespaceID,
5751 nsIAtom* aAttribute,
5752 const nsAString& aValue,
5753 nsAttrValue& aResult)
5754 {
5755 if (aNamespaceID == kNameSpaceID_None) {
5756 if (aAttribute == nsGkAtoms::type) {
5757 // XXX ARG!! This is major evilness. ParseAttribute
5758 // shouldn't set members. Override SetAttr instead
5759 int32_t newType;
5760 bool success = aResult.ParseEnumValue(aValue, kInputTypeTable, false);
5761 if (success) {
5762 newType = aResult.GetEnumValue();
5763 if ((IsExperimentalMobileType(newType) &&
5764 !Preferences::GetBool("dom.experimental_forms", false)) ||
5765 (newType == NS_FORM_INPUT_NUMBER &&
5766 !Preferences::GetBool("dom.forms.number", false)) ||
5767 (newType == NS_FORM_INPUT_COLOR &&
5768 !Preferences::GetBool("dom.forms.color", false)) ||
5769 (IsDateTimeInputType(newType) && !IsDateTimeEnabled(newType))) {
5770 newType = kInputDefaultType->value;
5771 aResult.SetTo(newType, &aValue);
5772 }
5773 } else {
5774 newType = kInputDefaultType->value;
5775 }
5776
5777 if (newType != mType) {
5778 // Make sure to do the check for newType being NS_FORM_INPUT_FILE and
5779 // the corresponding SetValueInternal() call _before_ we set mType.
5780 // That way the logic in SetValueInternal() will work right (that logic
5781 // makes assumptions about our frame based on mType, but we won't have
5782 // had time to recreate frames yet -- that happens later in the
5783 // SetAttr() process).
5784 if (newType == NS_FORM_INPUT_FILE || mType == NS_FORM_INPUT_FILE) {
5785 // This call isn't strictly needed any more since we'll never
5786 // confuse values and filenames. However it's there for backwards
5787 // compat.
5788 ClearFiles(false);
5789 }
5790
5791 HandleTypeChange(newType);
5792 }
5793
5794 return success;
5795 }
5796 if (aAttribute == nsGkAtoms::width) {
5797 return aResult.ParseSpecialIntValue(aValue);
5798 }
5799 if (aAttribute == nsGkAtoms::height) {
5800 return aResult.ParseSpecialIntValue(aValue);
5801 }
5802 if (aAttribute == nsGkAtoms::maxlength) {
5803 return aResult.ParseNonNegativeIntValue(aValue);
5804 }
5805 if (aAttribute == nsGkAtoms::minlength) {
5806 return aResult.ParseNonNegativeIntValue(aValue);
5807 }
5808 if (aAttribute == nsGkAtoms::size) {
5809 return aResult.ParsePositiveIntValue(aValue);
5810 }
5811 if (aAttribute == nsGkAtoms::border) {
5812 return aResult.ParseIntWithBounds(aValue, 0);
5813 }
5814 if (aAttribute == nsGkAtoms::align) {
5815 return ParseAlignValue(aValue, aResult);
5816 }
5817 if (aAttribute == nsGkAtoms::formmethod) {
5818 return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
5819 }
5820 if (aAttribute == nsGkAtoms::formenctype) {
5821 return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
5822 }
5823 if (aAttribute == nsGkAtoms::autocomplete) {
5824 aResult.ParseAtomArray(aValue);
5825 return true;
5826 }
5827 if (aAttribute == nsGkAtoms::inputmode) {
5828 return aResult.ParseEnumValue(aValue, kInputInputmodeTable, false);
5829 }
5830 if (ParseImageAttribute(aAttribute, aValue, aResult)) {
5831 // We have to call |ParseImageAttribute| unconditionally since we
5832 // don't know if we're going to have a type="image" attribute yet,
5833 // (or could have it set dynamically in the future). See bug
5834 // 214077.
5835 return true;
5836 }
5837 }
5838
5839 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
5840 aResult);
5841 }
5842
5843 void
MapAttributesIntoRule(const nsMappedAttributes * aAttributes,nsRuleData * aData)5844 HTMLInputElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
5845 nsRuleData* aData)
5846 {
5847 const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::type);
5848 if (value && value->Type() == nsAttrValue::eEnum &&
5849 value->GetEnumValue() == NS_FORM_INPUT_IMAGE) {
5850 nsGenericHTMLFormElementWithState::MapImageBorderAttributeInto(aAttributes, aData);
5851 nsGenericHTMLFormElementWithState::MapImageMarginAttributeInto(aAttributes, aData);
5852 nsGenericHTMLFormElementWithState::MapImageSizeAttributesInto(aAttributes, aData);
5853 // Images treat align as "float"
5854 nsGenericHTMLFormElementWithState::MapImageAlignAttributeInto(aAttributes, aData);
5855 }
5856
5857 nsGenericHTMLFormElementWithState::MapCommonAttributesInto(aAttributes, aData);
5858 }
5859
5860 nsChangeHint
GetAttributeChangeHint(const nsIAtom * aAttribute,int32_t aModType) const5861 HTMLInputElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
5862 int32_t aModType) const
5863 {
5864 nsChangeHint retval =
5865 nsGenericHTMLFormElementWithState::GetAttributeChangeHint(aAttribute, aModType);
5866 if (aAttribute == nsGkAtoms::type ||
5867 // The presence or absence of the 'directory' attribute determines what
5868 // buttons we show for type=file.
5869 aAttribute == nsGkAtoms::allowdirs ||
5870 aAttribute == nsGkAtoms::webkitdirectory) {
5871 retval |= nsChangeHint_ReconstructFrame;
5872 } else if (mType == NS_FORM_INPUT_IMAGE &&
5873 (aAttribute == nsGkAtoms::alt ||
5874 aAttribute == nsGkAtoms::value)) {
5875 // We might need to rebuild our alt text. Just go ahead and
5876 // reconstruct our frame. This should be quite rare..
5877 retval |= nsChangeHint_ReconstructFrame;
5878 } else if (aAttribute == nsGkAtoms::value) {
5879 retval |= NS_STYLE_HINT_REFLOW;
5880 } else if (aAttribute == nsGkAtoms::size &&
5881 IsSingleLineTextControl(false)) {
5882 retval |= NS_STYLE_HINT_REFLOW;
5883 } else if (PlaceholderApplies() && aAttribute == nsGkAtoms::placeholder) {
5884 retval |= nsChangeHint_ReconstructFrame;
5885 }
5886 return retval;
5887 }
5888
NS_IMETHODIMP_(bool)5889 NS_IMETHODIMP_(bool)
5890 HTMLInputElement::IsAttributeMapped(const nsIAtom* aAttribute) const
5891 {
5892 static const MappedAttributeEntry attributes[] = {
5893 { &nsGkAtoms::align },
5894 { &nsGkAtoms::type },
5895 { nullptr },
5896 };
5897
5898 static const MappedAttributeEntry* const map[] = {
5899 attributes,
5900 sCommonAttributeMap,
5901 sImageMarginSizeAttributeMap,
5902 sImageBorderAttributeMap,
5903 };
5904
5905 return FindAttributeDependence(aAttribute, map);
5906 }
5907
5908 nsMapRuleToAttributesFunc
GetAttributeMappingFunction() const5909 HTMLInputElement::GetAttributeMappingFunction() const
5910 {
5911 return &MapAttributesIntoRule;
5912 }
5913
5914
5915 // Directory picking methods:
5916
5917 bool
IsFilesAndDirectoriesSupported() const5918 HTMLInputElement::IsFilesAndDirectoriesSupported() const
5919 {
5920 // This method is supposed to return true if a file and directory picker
5921 // supports the selection of both files and directories *at the same time*.
5922 // Only Mac currently supports that. We could implement it for Mac, but
5923 // currently we do not.
5924 return false;
5925 }
5926
5927 void
ChooseDirectory(ErrorResult & aRv)5928 HTMLInputElement::ChooseDirectory(ErrorResult& aRv)
5929 {
5930 if (mType != NS_FORM_INPUT_FILE) {
5931 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5932 return;
5933 }
5934 // Script can call this method directly, so even though we don't show the
5935 // "Pick Folder..." button on platforms that don't have a directory picker
5936 // we have to redirect to the file picker here.
5937 InitFilePicker(
5938 #if defined(ANDROID) || defined(MOZ_B2G)
5939 // No native directory picker - redirect to plain file picker
5940 FILE_PICKER_FILE
5941 #else
5942 FILE_PICKER_DIRECTORY
5943 #endif
5944 );
5945 }
5946
5947 already_AddRefed<Promise>
GetFilesAndDirectories(ErrorResult & aRv)5948 HTMLInputElement::GetFilesAndDirectories(ErrorResult& aRv)
5949 {
5950 if (mType != NS_FORM_INPUT_FILE) {
5951 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5952 return nullptr;
5953 }
5954
5955 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
5956 MOZ_ASSERT(global);
5957 if (!global) {
5958 return nullptr;
5959 }
5960
5961 RefPtr<Promise> p = Promise::Create(global, aRv);
5962 if (aRv.Failed()) {
5963 return nullptr;
5964 }
5965
5966 const nsTArray<OwningFileOrDirectory>& filesAndDirs =
5967 GetFilesOrDirectoriesInternal();
5968
5969 Sequence<OwningFileOrDirectory> filesAndDirsSeq;
5970
5971 if (!filesAndDirsSeq.SetLength(filesAndDirs.Length(),
5972 mozilla::fallible_t())) {
5973 p->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
5974 return p.forget();
5975 }
5976
5977 for (uint32_t i = 0; i < filesAndDirs.Length(); ++i) {
5978 if (filesAndDirs[i].IsDirectory()) {
5979 RefPtr<Directory> directory = filesAndDirs[i].GetAsDirectory();
5980
5981 // In future we could refactor SetFilePickerFiltersFromAccept to return a
5982 // semicolon separated list of file extensions and include that in the
5983 // filter string passed here.
5984 directory->SetContentFilters(NS_LITERAL_STRING("filter-out-sensitive"));
5985 filesAndDirsSeq[i].SetAsDirectory() = directory;
5986 } else {
5987 MOZ_ASSERT(filesAndDirs[i].IsFile());
5988
5989 // This file was directly selected by the user, so don't filter it.
5990 filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i].GetAsFile();
5991 }
5992 }
5993
5994 p->MaybeResolve(filesAndDirsSeq);
5995 return p.forget();
5996 }
5997
5998 already_AddRefed<Promise>
GetFiles(bool aRecursiveFlag,ErrorResult & aRv)5999 HTMLInputElement::GetFiles(bool aRecursiveFlag, ErrorResult& aRv)
6000 {
6001 if (mType != NS_FORM_INPUT_FILE) {
6002 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6003 return nullptr;
6004 }
6005
6006 GetFilesHelper* helper = GetOrCreateGetFilesHelper(aRecursiveFlag, aRv);
6007 if (NS_WARN_IF(aRv.Failed())) {
6008 return nullptr;
6009 }
6010 MOZ_ASSERT(helper);
6011
6012 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
6013 MOZ_ASSERT(global);
6014 if (!global) {
6015 return nullptr;
6016 }
6017
6018 RefPtr<Promise> p = Promise::Create(global, aRv);
6019 if (aRv.Failed()) {
6020 return nullptr;
6021 }
6022
6023 helper->AddPromise(p);
6024 return p.forget();
6025 }
6026
6027
6028 // Controllers Methods
6029
6030 nsIControllers*
GetControllers(ErrorResult & aRv)6031 HTMLInputElement::GetControllers(ErrorResult& aRv)
6032 {
6033 //XXX: what about type "file"?
6034 if (IsSingleLineTextControl(false))
6035 {
6036 if (!mControllers)
6037 {
6038 nsresult rv;
6039 mControllers = do_CreateInstance(kXULControllersCID, &rv);
6040 if (NS_FAILED(rv)) {
6041 aRv.Throw(rv);
6042 return nullptr;
6043 }
6044
6045 nsCOMPtr<nsIController>
6046 controller(do_CreateInstance("@mozilla.org/editor/editorcontroller;1",
6047 &rv));
6048 if (NS_FAILED(rv)) {
6049 aRv.Throw(rv);
6050 return nullptr;
6051 }
6052
6053 mControllers->AppendController(controller);
6054
6055 controller = do_CreateInstance("@mozilla.org/editor/editingcontroller;1",
6056 &rv);
6057 if (NS_FAILED(rv)) {
6058 aRv.Throw(rv);
6059 return nullptr;
6060 }
6061
6062 mControllers->AppendController(controller);
6063 }
6064 }
6065
6066 return mControllers;
6067 }
6068
6069 NS_IMETHODIMP
GetControllers(nsIControllers ** aResult)6070 HTMLInputElement::GetControllers(nsIControllers** aResult)
6071 {
6072 NS_ENSURE_ARG_POINTER(aResult);
6073
6074 ErrorResult rv;
6075 RefPtr<nsIControllers> controller = GetControllers(rv);
6076 controller.forget(aResult);
6077 return rv.StealNSResult();
6078 }
6079
6080 int32_t
GetTextLength(ErrorResult & aRv)6081 HTMLInputElement::GetTextLength(ErrorResult& aRv)
6082 {
6083 nsAutoString val;
6084 GetValue(val);
6085 return val.Length();
6086 }
6087
6088 NS_IMETHODIMP
GetTextLength(int32_t * aTextLength)6089 HTMLInputElement::GetTextLength(int32_t* aTextLength)
6090 {
6091 ErrorResult rv;
6092 *aTextLength = GetTextLength(rv);
6093 return rv.StealNSResult();
6094 }
6095
6096 void
SetSelectionRange(int32_t aSelectionStart,int32_t aSelectionEnd,const Optional<nsAString> & aDirection,ErrorResult & aRv)6097 HTMLInputElement::SetSelectionRange(int32_t aSelectionStart,
6098 int32_t aSelectionEnd,
6099 const Optional<nsAString>& aDirection,
6100 ErrorResult& aRv)
6101 {
6102 if (!SupportsTextSelection()) {
6103 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6104 return;
6105 }
6106
6107 nsresult rv = SetSelectionRange(aSelectionStart, aSelectionEnd,
6108 aDirection.WasPassed() ? aDirection.Value() : NullString());
6109
6110 if (NS_FAILED(rv)) {
6111 aRv.Throw(rv);
6112 }
6113 }
6114
6115 NS_IMETHODIMP
SetSelectionRange(int32_t aSelectionStart,int32_t aSelectionEnd,const nsAString & aDirection)6116 HTMLInputElement::SetSelectionRange(int32_t aSelectionStart,
6117 int32_t aSelectionEnd,
6118 const nsAString& aDirection)
6119 {
6120 nsresult rv = NS_OK;
6121 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
6122 nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
6123 if (textControlFrame) {
6124 // Default to forward, even if not specified.
6125 // Note that we don't currently support directionless selections, so
6126 // "none" is treated like "forward".
6127 nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eForward;
6128 if (!aDirection.IsEmpty() && aDirection.EqualsLiteral("backward")) {
6129 dir = nsITextControlFrame::eBackward;
6130 }
6131
6132 rv = textControlFrame->SetSelectionRange(aSelectionStart, aSelectionEnd, dir);
6133 if (NS_SUCCEEDED(rv)) {
6134 rv = textControlFrame->ScrollSelectionIntoView();
6135 RefPtr<AsyncEventDispatcher> asyncDispatcher =
6136 new AsyncEventDispatcher(this, NS_LITERAL_STRING("select"),
6137 true, false);
6138 asyncDispatcher->PostDOMEvent();
6139 }
6140 }
6141
6142 return rv;
6143 }
6144
6145 void
SetRangeText(const nsAString & aReplacement,ErrorResult & aRv)6146 HTMLInputElement::SetRangeText(const nsAString& aReplacement, ErrorResult& aRv)
6147 {
6148 if (!SupportsTextSelection()) {
6149 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6150 return;
6151 }
6152
6153 int32_t start, end;
6154 aRv = GetSelectionRange(&start, &end);
6155 if (aRv.Failed()) {
6156 nsTextEditorState* state = GetEditorState();
6157 if (state && state->IsSelectionCached()) {
6158 start = state->GetSelectionProperties().GetStart();
6159 end = state->GetSelectionProperties().GetEnd();
6160 aRv = NS_OK;
6161 }
6162 }
6163
6164 SetRangeText(aReplacement, start, end, mozilla::dom::SelectionMode::Preserve,
6165 aRv, start, end);
6166 }
6167
6168 void
SetRangeText(const nsAString & aReplacement,uint32_t aStart,uint32_t aEnd,const SelectionMode & aSelectMode,ErrorResult & aRv,int32_t aSelectionStart,int32_t aSelectionEnd)6169 HTMLInputElement::SetRangeText(const nsAString& aReplacement, uint32_t aStart,
6170 uint32_t aEnd, const SelectionMode& aSelectMode,
6171 ErrorResult& aRv, int32_t aSelectionStart,
6172 int32_t aSelectionEnd)
6173 {
6174 if (!SupportsTextSelection()) {
6175 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6176 return;
6177 }
6178
6179 if (aStart > aEnd) {
6180 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
6181 return;
6182 }
6183
6184 nsAutoString value;
6185 GetValueInternal(value);
6186 uint32_t inputValueLength = value.Length();
6187
6188 if (aStart > inputValueLength) {
6189 aStart = inputValueLength;
6190 }
6191
6192 if (aEnd > inputValueLength) {
6193 aEnd = inputValueLength;
6194 }
6195
6196 if (aSelectionStart == -1 && aSelectionEnd == -1) {
6197 aRv = GetSelectionRange(&aSelectionStart, &aSelectionEnd);
6198 if (aRv.Failed()) {
6199 nsTextEditorState* state = GetEditorState();
6200 if (state && state->IsSelectionCached()) {
6201 aSelectionStart = state->GetSelectionProperties().GetStart();
6202 aSelectionEnd = state->GetSelectionProperties().GetEnd();
6203 aRv = NS_OK;
6204 }
6205 }
6206 }
6207
6208 if (aStart <= aEnd) {
6209 value.Replace(aStart, aEnd - aStart, aReplacement);
6210 nsresult rv =
6211 SetValueInternal(value, nsTextEditorState::eSetValue_ByContent);
6212 if (NS_FAILED(rv)) {
6213 aRv.Throw(rv);
6214 return;
6215 }
6216 }
6217
6218 uint32_t newEnd = aStart + aReplacement.Length();
6219 int32_t delta = aReplacement.Length() - (aEnd - aStart);
6220
6221 switch (aSelectMode) {
6222 case mozilla::dom::SelectionMode::Select:
6223 {
6224 aSelectionStart = aStart;
6225 aSelectionEnd = newEnd;
6226 }
6227 break;
6228 case mozilla::dom::SelectionMode::Start:
6229 {
6230 aSelectionStart = aSelectionEnd = aStart;
6231 }
6232 break;
6233 case mozilla::dom::SelectionMode::End:
6234 {
6235 aSelectionStart = aSelectionEnd = newEnd;
6236 }
6237 break;
6238 case mozilla::dom::SelectionMode::Preserve:
6239 {
6240 if ((uint32_t)aSelectionStart > aEnd) {
6241 aSelectionStart += delta;
6242 } else if ((uint32_t)aSelectionStart > aStart) {
6243 aSelectionStart = aStart;
6244 }
6245
6246 if ((uint32_t)aSelectionEnd > aEnd) {
6247 aSelectionEnd += delta;
6248 } else if ((uint32_t)aSelectionEnd > aStart) {
6249 aSelectionEnd = newEnd;
6250 }
6251 }
6252 break;
6253 default:
6254 MOZ_CRASH("Unknown mode!");
6255 }
6256
6257 Optional<nsAString> direction;
6258 SetSelectionRange(aSelectionStart, aSelectionEnd, direction, aRv);
6259 }
6260
6261 Nullable<int32_t>
GetSelectionStart(ErrorResult & aRv)6262 HTMLInputElement::GetSelectionStart(ErrorResult& aRv)
6263 {
6264 if (!SupportsTextSelection()) {
6265 return Nullable<int32_t>();
6266 }
6267
6268 int32_t selStart;
6269 nsresult rv = GetSelectionStart(&selStart);
6270 if (NS_FAILED(rv)) {
6271 aRv.Throw(rv);
6272 }
6273
6274 return Nullable<int32_t>(selStart);
6275 }
6276
6277 NS_IMETHODIMP
GetSelectionStart(int32_t * aSelectionStart)6278 HTMLInputElement::GetSelectionStart(int32_t* aSelectionStart)
6279 {
6280 NS_ENSURE_ARG_POINTER(aSelectionStart);
6281
6282 int32_t selEnd, selStart;
6283 nsresult rv = GetSelectionRange(&selStart, &selEnd);
6284
6285 if (NS_FAILED(rv)) {
6286 nsTextEditorState* state = GetEditorState();
6287 if (state && state->IsSelectionCached()) {
6288 *aSelectionStart = state->GetSelectionProperties().GetStart();
6289 return NS_OK;
6290 }
6291 return rv;
6292 }
6293
6294 *aSelectionStart = selStart;
6295 return NS_OK;
6296 }
6297
6298 void
SetSelectionStart(const Nullable<int32_t> & aSelectionStart,ErrorResult & aRv)6299 HTMLInputElement::SetSelectionStart(const Nullable<int32_t>& aSelectionStart,
6300 ErrorResult& aRv)
6301 {
6302 if (!SupportsTextSelection()) {
6303 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6304 return;
6305 }
6306
6307 int32_t selStart = 0;
6308 if (!aSelectionStart.IsNull()) {
6309 selStart = aSelectionStart.Value();
6310 }
6311
6312 nsTextEditorState* state = GetEditorState();
6313 if (state && state->IsSelectionCached()) {
6314 state->GetSelectionProperties().SetStart(selStart);
6315 return;
6316 }
6317
6318 nsAutoString direction;
6319 aRv = GetSelectionDirection(direction);
6320 if (aRv.Failed()) {
6321 return;
6322 }
6323
6324 int32_t start, end;
6325 aRv = GetSelectionRange(&start, &end);
6326 if (aRv.Failed()) {
6327 return;
6328 }
6329
6330 start = selStart;
6331 if (end < start) {
6332 end = start;
6333 }
6334
6335 aRv = SetSelectionRange(start, end, direction);
6336 }
6337
6338 NS_IMETHODIMP
SetSelectionStart(int32_t aSelectionStart)6339 HTMLInputElement::SetSelectionStart(int32_t aSelectionStart)
6340 {
6341 ErrorResult rv;
6342 Nullable<int32_t> selStart(aSelectionStart);
6343 SetSelectionStart(selStart, rv);
6344 return rv.StealNSResult();
6345 }
6346
6347 Nullable<int32_t>
GetSelectionEnd(ErrorResult & aRv)6348 HTMLInputElement::GetSelectionEnd(ErrorResult& aRv)
6349 {
6350 if (!SupportsTextSelection()) {
6351 return Nullable<int32_t>();
6352 }
6353
6354 int32_t selEnd;
6355 nsresult rv = GetSelectionEnd(&selEnd);
6356 if (NS_FAILED(rv)) {
6357 aRv.Throw(rv);
6358 }
6359
6360 return Nullable<int32_t>(selEnd);
6361 }
6362
6363 NS_IMETHODIMP
GetSelectionEnd(int32_t * aSelectionEnd)6364 HTMLInputElement::GetSelectionEnd(int32_t* aSelectionEnd)
6365 {
6366 NS_ENSURE_ARG_POINTER(aSelectionEnd);
6367
6368 int32_t selEnd, selStart;
6369 nsresult rv = GetSelectionRange(&selStart, &selEnd);
6370
6371 if (NS_FAILED(rv)) {
6372 nsTextEditorState* state = GetEditorState();
6373 if (state && state->IsSelectionCached()) {
6374 *aSelectionEnd = state->GetSelectionProperties().GetEnd();
6375 return NS_OK;
6376 }
6377 return rv;
6378 }
6379
6380 *aSelectionEnd = selEnd;
6381 return NS_OK;
6382 }
6383
6384 void
SetSelectionEnd(const Nullable<int32_t> & aSelectionEnd,ErrorResult & aRv)6385 HTMLInputElement::SetSelectionEnd(const Nullable<int32_t>& aSelectionEnd,
6386 ErrorResult& aRv)
6387 {
6388 if (!SupportsTextSelection()) {
6389 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6390 return;
6391 }
6392
6393 int32_t selEnd = 0;
6394 if (!aSelectionEnd.IsNull()) {
6395 selEnd = aSelectionEnd.Value();
6396 }
6397
6398 nsTextEditorState* state = GetEditorState();
6399 if (state && state->IsSelectionCached()) {
6400 state->GetSelectionProperties().SetEnd(selEnd);
6401 return;
6402 }
6403
6404 nsAutoString direction;
6405 aRv = GetSelectionDirection(direction);
6406 if (aRv.Failed()) {
6407 return;
6408 }
6409
6410 int32_t start, end;
6411 aRv = GetSelectionRange(&start, &end);
6412 if (aRv.Failed()) {
6413 return;
6414 }
6415
6416 end = selEnd;
6417 if (start > end) {
6418 start = end;
6419 }
6420
6421 aRv = SetSelectionRange(start, end, direction);
6422 }
6423
6424 NS_IMETHODIMP
SetSelectionEnd(int32_t aSelectionEnd)6425 HTMLInputElement::SetSelectionEnd(int32_t aSelectionEnd)
6426 {
6427 ErrorResult rv;
6428 Nullable<int32_t> selEnd(aSelectionEnd);
6429 SetSelectionEnd(selEnd, rv);
6430 return rv.StealNSResult();
6431 }
6432
6433 NS_IMETHODIMP
GetFiles(nsIDOMFileList ** aFileList)6434 HTMLInputElement::GetFiles(nsIDOMFileList** aFileList)
6435 {
6436 RefPtr<FileList> list = GetFiles();
6437 list.forget(aFileList);
6438 return NS_OK;
6439 }
6440
6441 nsresult
GetSelectionRange(int32_t * aSelectionStart,int32_t * aSelectionEnd)6442 HTMLInputElement::GetSelectionRange(int32_t* aSelectionStart,
6443 int32_t* aSelectionEnd)
6444 {
6445 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
6446 nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
6447 if (textControlFrame) {
6448 return textControlFrame->GetSelectionRange(aSelectionStart, aSelectionEnd);
6449 }
6450
6451 return NS_ERROR_FAILURE;
6452 }
6453
6454 static void
DirectionToName(nsITextControlFrame::SelectionDirection dir,nsAString & aDirection)6455 DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection)
6456 {
6457 if (dir == nsITextControlFrame::eNone) {
6458 aDirection.AssignLiteral("none");
6459 } else if (dir == nsITextControlFrame::eForward) {
6460 aDirection.AssignLiteral("forward");
6461 } else if (dir == nsITextControlFrame::eBackward) {
6462 aDirection.AssignLiteral("backward");
6463 } else {
6464 NS_NOTREACHED("Invalid SelectionDirection value");
6465 }
6466 }
6467
6468 void
GetSelectionDirection(nsAString & aDirection,ErrorResult & aRv)6469 HTMLInputElement::GetSelectionDirection(nsAString& aDirection, ErrorResult& aRv)
6470 {
6471 if (!SupportsTextSelection()) {
6472 aDirection.SetIsVoid(true);
6473 return;
6474 }
6475
6476 nsresult rv = NS_ERROR_FAILURE;
6477 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
6478 nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
6479 if (textControlFrame) {
6480 nsITextControlFrame::SelectionDirection dir;
6481 rv = textControlFrame->GetSelectionRange(nullptr, nullptr, &dir);
6482 if (NS_SUCCEEDED(rv)) {
6483 DirectionToName(dir, aDirection);
6484 }
6485 }
6486
6487 if (NS_FAILED(rv)) {
6488 nsTextEditorState* state = GetEditorState();
6489 if (state && state->IsSelectionCached()) {
6490 DirectionToName(state->GetSelectionProperties().GetDirection(), aDirection);
6491 return;
6492 }
6493 }
6494
6495 if (NS_FAILED(rv)) {
6496 aRv.Throw(rv);
6497 }
6498 }
6499
6500 NS_IMETHODIMP
GetSelectionDirection(nsAString & aDirection)6501 HTMLInputElement::GetSelectionDirection(nsAString& aDirection)
6502 {
6503 ErrorResult rv;
6504 GetSelectionDirection(aDirection, rv);
6505 return rv.StealNSResult();
6506 }
6507
6508 void
SetSelectionDirection(const nsAString & aDirection,ErrorResult & aRv)6509 HTMLInputElement::SetSelectionDirection(const nsAString& aDirection, ErrorResult& aRv)
6510 {
6511 if (!SupportsTextSelection()) {
6512 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
6513 return;
6514 }
6515
6516 nsTextEditorState* state = GetEditorState();
6517 if (state && state->IsSelectionCached()) {
6518 nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eNone;
6519 if (aDirection.EqualsLiteral("forward")) {
6520 dir = nsITextControlFrame::eForward;
6521 } else if (aDirection.EqualsLiteral("backward")) {
6522 dir = nsITextControlFrame::eBackward;
6523 }
6524 state->GetSelectionProperties().SetDirection(dir);
6525 return;
6526 }
6527
6528 int32_t start, end;
6529 aRv = GetSelectionRange(&start, &end);
6530 if (!aRv.Failed()) {
6531 aRv = SetSelectionRange(start, end, aDirection);
6532 }
6533 }
6534
6535 NS_IMETHODIMP
SetSelectionDirection(const nsAString & aDirection)6536 HTMLInputElement::SetSelectionDirection(const nsAString& aDirection)
6537 {
6538 ErrorResult rv;
6539 SetSelectionDirection(aDirection, rv);
6540 return rv.StealNSResult();
6541 }
6542
6543 NS_IMETHODIMP
GetPhonetic(nsAString & aPhonetic)6544 HTMLInputElement::GetPhonetic(nsAString& aPhonetic)
6545 {
6546 aPhonetic.Truncate();
6547 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
6548 nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
6549 if (textControlFrame) {
6550 textControlFrame->GetPhonetic(aPhonetic);
6551 }
6552
6553 return NS_OK;
6554 }
6555
6556 #ifdef ACCESSIBILITY
6557 /*static*/ nsresult
FireEventForAccessibility(nsIDOMHTMLInputElement * aTarget,nsPresContext * aPresContext,const nsAString & aEventType)6558 FireEventForAccessibility(nsIDOMHTMLInputElement* aTarget,
6559 nsPresContext* aPresContext,
6560 const nsAString& aEventType)
6561 {
6562 nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aTarget);
6563 RefPtr<Event> event = NS_NewDOMEvent(element, aPresContext, nullptr);
6564 event->InitEvent(aEventType, true, true);
6565 event->SetTrusted(true);
6566
6567 EventDispatcher::DispatchDOMEvent(aTarget, nullptr, event, aPresContext,
6568 nullptr);
6569
6570 return NS_OK;
6571 }
6572 #endif
6573
6574 void
UpdateApzAwareFlag()6575 HTMLInputElement::UpdateApzAwareFlag()
6576 {
6577 #if !defined(ANDROID) && !defined(XP_MACOSX)
6578 if ((mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE)) {
6579 SetMayBeApzAware();
6580 }
6581 #endif
6582 }
6583
6584 nsresult
SetDefaultValueAsValue()6585 HTMLInputElement::SetDefaultValueAsValue()
6586 {
6587 NS_ASSERTION(GetValueMode() == VALUE_MODE_VALUE,
6588 "GetValueMode() should return VALUE_MODE_VALUE!");
6589
6590 // The element has a content attribute value different from it's value when
6591 // it's in the value mode value.
6592 nsAutoString resetVal;
6593 GetDefaultValue(resetVal);
6594
6595 // SetValueInternal is going to sanitize the value.
6596 return SetValueInternal(resetVal, nsTextEditorState::eSetValue_Internal);
6597 }
6598
6599 void
SetDirectionIfAuto(bool aAuto,bool aNotify)6600 HTMLInputElement::SetDirectionIfAuto(bool aAuto, bool aNotify)
6601 {
6602 if (aAuto) {
6603 SetHasDirAuto();
6604 if (IsSingleLineTextControl(true)) {
6605 nsAutoString value;
6606 GetValue(value);
6607 SetDirectionalityFromValue(this, value, aNotify);
6608 }
6609 } else {
6610 ClearHasDirAuto();
6611 }
6612 }
6613
6614 NS_IMETHODIMP
Reset()6615 HTMLInputElement::Reset()
6616 {
6617 // We should be able to reset all dirty flags regardless of the type.
6618 SetCheckedChanged(false);
6619 SetValueChanged(false);
6620 mLastValueChangeWasInteractive = false;
6621
6622 switch (GetValueMode()) {
6623 case VALUE_MODE_VALUE:
6624 return SetDefaultValueAsValue();
6625 case VALUE_MODE_DEFAULT_ON:
6626 DoSetChecked(DefaultChecked(), true, false);
6627 return NS_OK;
6628 case VALUE_MODE_FILENAME:
6629 ClearFiles(false);
6630 return NS_OK;
6631 case VALUE_MODE_DEFAULT:
6632 default:
6633 return NS_OK;
6634 }
6635 }
6636
6637 NS_IMETHODIMP
SubmitNamesValues(HTMLFormSubmission * aFormSubmission)6638 HTMLInputElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
6639 {
6640 // Disabled elements don't submit
6641 // For type=reset, and type=button, we just never submit, period.
6642 // For type=image and type=button, we only submit if we were the button
6643 // pressed
6644 // For type=radio and type=checkbox, we only submit if checked=true
6645 if (IsDisabled() || mType == NS_FORM_INPUT_RESET ||
6646 mType == NS_FORM_INPUT_BUTTON ||
6647 ((mType == NS_FORM_INPUT_SUBMIT || mType == NS_FORM_INPUT_IMAGE) &&
6648 aFormSubmission->GetOriginatingElement() != this) ||
6649 ((mType == NS_FORM_INPUT_RADIO || mType == NS_FORM_INPUT_CHECKBOX) &&
6650 !mChecked)) {
6651 return NS_OK;
6652 }
6653
6654 // Get the name
6655 nsAutoString name;
6656 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
6657
6658 // Submit .x, .y for input type=image
6659 if (mType == NS_FORM_INPUT_IMAGE) {
6660 // Get a property set by the frame to find out where it was clicked.
6661 nsIntPoint* lastClickedPoint =
6662 static_cast<nsIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
6663 int32_t x, y;
6664 if (lastClickedPoint) {
6665 // Convert the values to strings for submission
6666 x = lastClickedPoint->x;
6667 y = lastClickedPoint->y;
6668 } else {
6669 x = y = 0;
6670 }
6671
6672 nsAutoString xVal, yVal;
6673 xVal.AppendInt(x);
6674 yVal.AppendInt(y);
6675
6676 if (!name.IsEmpty()) {
6677 aFormSubmission->AddNameValuePair(name + NS_LITERAL_STRING(".x"), xVal);
6678 aFormSubmission->AddNameValuePair(name + NS_LITERAL_STRING(".y"), yVal);
6679 } else {
6680 // If the Image Element has no name, simply return x and y
6681 // to Nav and IE compatibility.
6682 aFormSubmission->AddNameValuePair(NS_LITERAL_STRING("x"), xVal);
6683 aFormSubmission->AddNameValuePair(NS_LITERAL_STRING("y"), yVal);
6684 }
6685
6686 return NS_OK;
6687 }
6688
6689 //
6690 // Submit name=value
6691 //
6692
6693 // If name not there, don't submit
6694 if (name.IsEmpty()) {
6695 return NS_OK;
6696 }
6697
6698 // Get the value
6699 nsAutoString value;
6700 nsresult rv = GetValue(value);
6701 if (NS_FAILED(rv)) {
6702 return rv;
6703 }
6704
6705 if (mType == NS_FORM_INPUT_SUBMIT && value.IsEmpty() &&
6706 !HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
6707 // Get our default value, which is the same as our default label
6708 nsXPIDLString defaultValue;
6709 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
6710 "Submit", defaultValue);
6711 value = defaultValue;
6712 }
6713
6714 //
6715 // Submit file if its input type=file and this encoding method accepts files
6716 //
6717 if (mType == NS_FORM_INPUT_FILE) {
6718 // Submit files
6719
6720 const nsTArray<OwningFileOrDirectory>& files =
6721 GetFilesOrDirectoriesInternal();
6722
6723 if (files.IsEmpty()) {
6724 aFormSubmission->AddNameBlobOrNullPair(name, nullptr);
6725 return NS_OK;
6726 }
6727
6728 for (uint32_t i = 0; i < files.Length(); ++i) {
6729 if (files[i].IsFile()) {
6730 aFormSubmission->AddNameBlobOrNullPair(name, files[i].GetAsFile());
6731 } else {
6732 MOZ_ASSERT(files[i].IsDirectory());
6733 aFormSubmission->AddNameDirectoryPair(name, files[i].GetAsDirectory());
6734 }
6735 }
6736
6737 return NS_OK;
6738 }
6739
6740 if (mType == NS_FORM_INPUT_HIDDEN && name.EqualsLiteral("_charset_")) {
6741 nsCString charset;
6742 aFormSubmission->GetCharset(charset);
6743 return aFormSubmission->AddNameValuePair(name,
6744 NS_ConvertASCIItoUTF16(charset));
6745 }
6746 if (IsSingleLineTextControl(true) &&
6747 name.EqualsLiteral("isindex") &&
6748 aFormSubmission->SupportsIsindexSubmission()) {
6749 return aFormSubmission->AddIsindex(value);
6750 }
6751 return aFormSubmission->AddNameValuePair(name, value);
6752 }
6753
6754
6755 NS_IMETHODIMP
SaveState()6756 HTMLInputElement::SaveState()
6757 {
6758 RefPtr<HTMLInputElementState> inputState;
6759 switch (GetValueMode()) {
6760 case VALUE_MODE_DEFAULT_ON:
6761 if (mCheckedChanged) {
6762 inputState = new HTMLInputElementState();
6763 inputState->SetChecked(mChecked);
6764 }
6765 break;
6766 case VALUE_MODE_FILENAME:
6767 if (!mFilesOrDirectories.IsEmpty()) {
6768 inputState = new HTMLInputElementState();
6769 inputState->SetFilesOrDirectories(mFilesOrDirectories);
6770 }
6771 break;
6772 case VALUE_MODE_VALUE:
6773 case VALUE_MODE_DEFAULT:
6774 // VALUE_MODE_DEFAULT shouldn't have their value saved except 'hidden',
6775 // mType shouldn't be NS_FORM_INPUT_PASSWORD and value should have changed.
6776 if ((GetValueMode() == VALUE_MODE_DEFAULT &&
6777 mType != NS_FORM_INPUT_HIDDEN) ||
6778 mType == NS_FORM_INPUT_PASSWORD || !mValueChanged) {
6779 break;
6780 }
6781
6782 inputState = new HTMLInputElementState();
6783 nsAutoString value;
6784 nsresult rv = GetValue(value);
6785 if (NS_FAILED(rv)) {
6786 return rv;
6787 }
6788
6789 if (!IsSingleLineTextControl(false)) {
6790 rv = nsLinebreakConverter::ConvertStringLineBreaks(
6791 value,
6792 nsLinebreakConverter::eLinebreakPlatform,
6793 nsLinebreakConverter::eLinebreakContent);
6794
6795 if (NS_FAILED(rv)) {
6796 NS_ERROR("Converting linebreaks failed!");
6797 return rv;
6798 }
6799 }
6800
6801 inputState->SetValue(value);
6802 break;
6803 }
6804
6805 if (inputState) {
6806 nsPresState* state = GetPrimaryPresState();
6807 if (state) {
6808 state->SetStateProperty(inputState);
6809 }
6810 }
6811
6812 if (mDisabledChanged) {
6813 nsPresState* state = GetPrimaryPresState();
6814 if (state) {
6815 // We do not want to save the real disabled state but the disabled
6816 // attribute.
6817 state->SetDisabled(HasAttr(kNameSpaceID_None, nsGkAtoms::disabled));
6818 }
6819 }
6820
6821 return NS_OK;
6822 }
6823
6824 void
DoneCreatingElement()6825 HTMLInputElement::DoneCreatingElement()
6826 {
6827 mDoneCreating = true;
6828
6829 //
6830 // Restore state as needed. Note that disabled state applies to all control
6831 // types.
6832 //
6833 bool restoredCheckedState =
6834 !mInhibitRestoration && NS_SUCCEEDED(GenerateStateKey()) && RestoreFormControlState();
6835
6836 //
6837 // If restore does not occur, we initialize .checked using the CHECKED
6838 // property.
6839 //
6840 if (!restoredCheckedState && mShouldInitChecked) {
6841 DoSetChecked(DefaultChecked(), false, true);
6842 DoSetCheckedChanged(false, false);
6843 }
6844
6845 // Sanitize the value.
6846 if (GetValueMode() == VALUE_MODE_VALUE) {
6847 nsAutoString aValue;
6848 GetValue(aValue);
6849 // TODO: What should we do if SetValueInternal fails? (The allocation
6850 // may potentially be big, but most likely we've failed to allocate
6851 // before the type change.)
6852 SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal);
6853 }
6854
6855 mShouldInitChecked = false;
6856 }
6857
6858 EventStates
IntrinsicState() const6859 HTMLInputElement::IntrinsicState() const
6860 {
6861 // If you add states here, and they're type-dependent, you need to add them
6862 // to the type case in AfterSetAttr.
6863
6864 EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
6865 if (mType == NS_FORM_INPUT_CHECKBOX || mType == NS_FORM_INPUT_RADIO) {
6866 // Check current checked state (:checked)
6867 if (mChecked) {
6868 state |= NS_EVENT_STATE_CHECKED;
6869 }
6870
6871 // Check current indeterminate state (:indeterminate)
6872 if (mType == NS_FORM_INPUT_CHECKBOX && mIndeterminate) {
6873 state |= NS_EVENT_STATE_INDETERMINATE;
6874 }
6875
6876 if (mType == NS_FORM_INPUT_RADIO) {
6877 nsCOMPtr<nsIDOMHTMLInputElement> selected = GetSelectedRadioButton();
6878 bool indeterminate = !selected && !mChecked;
6879
6880 if (indeterminate) {
6881 state |= NS_EVENT_STATE_INDETERMINATE;
6882 }
6883 }
6884
6885 // Check whether we are the default checked element (:default)
6886 if (DefaultChecked()) {
6887 state |= NS_EVENT_STATE_DEFAULT;
6888 }
6889 } else if (mType == NS_FORM_INPUT_IMAGE) {
6890 state |= nsImageLoadingContent::ImageState();
6891 }
6892
6893 if (DoesRequiredApply() && HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
6894 state |= NS_EVENT_STATE_REQUIRED;
6895 } else {
6896 state |= NS_EVENT_STATE_OPTIONAL;
6897 }
6898
6899 if (IsCandidateForConstraintValidation()) {
6900 if (IsValid()) {
6901 state |= NS_EVENT_STATE_VALID;
6902 } else {
6903 state |= NS_EVENT_STATE_INVALID;
6904
6905 if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
6906 (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
6907 (mCanShowInvalidUI && ShouldShowValidityUI()))) {
6908 state |= NS_EVENT_STATE_MOZ_UI_INVALID;
6909 }
6910 }
6911
6912 // :-moz-ui-valid applies if all of the following conditions are true:
6913 // 1. The element is not focused, or had either :-moz-ui-valid or
6914 // :-moz-ui-invalid applying before it was focused ;
6915 // 2. The element is either valid or isn't allowed to have
6916 // :-moz-ui-invalid applying ;
6917 // 3. The element has no form owner or its form owner doesn't have the
6918 // novalidate attribute set ;
6919 // 4. The element has already been modified or the user tried to submit the
6920 // form owner while invalid.
6921 if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
6922 (mCanShowValidUI && ShouldShowValidityUI() &&
6923 (IsValid() || (!state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
6924 !mCanShowInvalidUI)))) {
6925 state |= NS_EVENT_STATE_MOZ_UI_VALID;
6926 }
6927
6928 // :in-range and :out-of-range only apply if the element currently has a range
6929 if (mHasRange) {
6930 state |= (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) ||
6931 GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW))
6932 ? NS_EVENT_STATE_OUTOFRANGE
6933 : NS_EVENT_STATE_INRANGE;
6934 }
6935 }
6936
6937 if (PlaceholderApplies() &&
6938 HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
6939 IsValueEmpty()) {
6940 state |= NS_EVENT_STATE_PLACEHOLDERSHOWN;
6941 }
6942
6943 if (mForm && !mForm->GetValidity() && IsSubmitControl()) {
6944 state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
6945 }
6946
6947 return state;
6948 }
6949
6950 void
AddStates(EventStates aStates)6951 HTMLInputElement::AddStates(EventStates aStates)
6952 {
6953 if (mType == NS_FORM_INPUT_TEXT) {
6954 EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
6955 NS_EVENT_STATE_FOCUSRING));
6956 if (!focusStates.IsEmpty()) {
6957 HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
6958 if (ownerNumberControl) {
6959 ownerNumberControl->AddStates(focusStates);
6960 } else {
6961 HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
6962 if (ownerDateTimeControl) {
6963 ownerDateTimeControl->AddStates(focusStates);
6964 }
6965 }
6966 }
6967 }
6968 nsGenericHTMLFormElementWithState::AddStates(aStates);
6969 }
6970
6971 void
RemoveStates(EventStates aStates)6972 HTMLInputElement::RemoveStates(EventStates aStates)
6973 {
6974 if (mType == NS_FORM_INPUT_TEXT) {
6975 EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
6976 NS_EVENT_STATE_FOCUSRING));
6977 if (!focusStates.IsEmpty()) {
6978 HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
6979 if (ownerNumberControl) {
6980 ownerNumberControl->RemoveStates(focusStates);
6981 } else {
6982 HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
6983 if (ownerDateTimeControl) {
6984 ownerDateTimeControl->RemoveStates(focusStates);
6985 }
6986 }
6987 }
6988 }
6989 nsGenericHTMLFormElementWithState::RemoveStates(aStates);
6990 }
6991
6992 bool
RestoreState(nsPresState * aState)6993 HTMLInputElement::RestoreState(nsPresState* aState)
6994 {
6995 bool restoredCheckedState = false;
6996
6997 nsCOMPtr<HTMLInputElementState> inputState
6998 (do_QueryInterface(aState->GetStateProperty()));
6999
7000 if (inputState) {
7001 switch (GetValueMode()) {
7002 case VALUE_MODE_DEFAULT_ON:
7003 if (inputState->IsCheckedSet()) {
7004 restoredCheckedState = true;
7005 DoSetChecked(inputState->GetChecked(), true, true);
7006 }
7007 break;
7008 case VALUE_MODE_FILENAME:
7009 {
7010 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
7011 if (window) {
7012 nsTArray<OwningFileOrDirectory> array;
7013 inputState->GetFilesOrDirectories(window, array);
7014
7015 SetFilesOrDirectories(array, true);
7016 }
7017 }
7018 break;
7019 case VALUE_MODE_VALUE:
7020 case VALUE_MODE_DEFAULT:
7021 if (GetValueMode() == VALUE_MODE_DEFAULT &&
7022 mType != NS_FORM_INPUT_HIDDEN) {
7023 break;
7024 }
7025
7026 // TODO: What should we do if SetValueInternal fails? (The allocation
7027 // may potentially be big, but most likely we've failed to allocate
7028 // before the type change.)
7029 SetValueInternal(inputState->GetValue(),
7030 nsTextEditorState::eSetValue_Notify);
7031 break;
7032 }
7033 }
7034
7035 if (aState->IsDisabledSet()) {
7036 SetDisabled(aState->GetDisabled());
7037 }
7038
7039 return restoredCheckedState;
7040 }
7041
7042 bool
AllowDrop()7043 HTMLInputElement::AllowDrop()
7044 {
7045 // Allow drop on anything other than file inputs.
7046
7047 return mType != NS_FORM_INPUT_FILE;
7048 }
7049
7050 /*
7051 * Radio group stuff
7052 */
7053
7054 void
AddedToRadioGroup()7055 HTMLInputElement::AddedToRadioGroup()
7056 {
7057 // If the element is neither in a form nor a document, there is no group so we
7058 // should just stop here.
7059 if (!mForm && !IsInUncomposedDoc()) {
7060 return;
7061 }
7062
7063 // Make sure not to notify if we're still being created
7064 bool notify = mDoneCreating;
7065
7066 //
7067 // If the input element is checked, and we add it to the group, it will
7068 // deselect whatever is currently selected in that group
7069 //
7070 if (mChecked) {
7071 //
7072 // If it is checked, call "RadioSetChecked" to perform the selection/
7073 // deselection ritual. This has the side effect of repainting the
7074 // radio button, but as adding a checked radio button into the group
7075 // should not be that common an occurrence, I think we can live with
7076 // that.
7077 //
7078 RadioSetChecked(notify);
7079 }
7080
7081 //
7082 // For integrity purposes, we have to ensure that "checkedChanged" is
7083 // the same for this new element as for all the others in the group
7084 //
7085 bool checkedChanged = mCheckedChanged;
7086
7087 nsCOMPtr<nsIRadioVisitor> visitor =
7088 new nsRadioGetCheckedChangedVisitor(&checkedChanged, this);
7089 VisitGroup(visitor, notify);
7090
7091 SetCheckedChangedInternal(checkedChanged);
7092
7093 //
7094 // Add the radio to the radio group container.
7095 //
7096 nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
7097 if (container) {
7098 nsAutoString name;
7099 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
7100 container->AddToRadioGroup(name, static_cast<nsIFormControl*>(this));
7101
7102 // We initialize the validity of the element to the validity of the group
7103 // because we assume UpdateValueMissingState() will be called after.
7104 SetValidityState(VALIDITY_STATE_VALUE_MISSING,
7105 container->GetValueMissingState(name));
7106 }
7107 }
7108
7109 void
WillRemoveFromRadioGroup()7110 HTMLInputElement::WillRemoveFromRadioGroup()
7111 {
7112 nsIRadioGroupContainer* container = GetRadioGroupContainer();
7113 if (!container) {
7114 return;
7115 }
7116
7117 nsAutoString name;
7118 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
7119
7120 // If this button was checked, we need to notify the group that there is no
7121 // longer a selected radio button
7122 if (mChecked) {
7123 container->SetCurrentRadioButton(name, nullptr);
7124
7125 nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
7126 VisitGroup(visitor, true);
7127 }
7128
7129 // Remove this radio from its group in the container.
7130 // We need to call UpdateValueMissingValidityStateForRadio before to make sure
7131 // the group validity is updated (with this element being ignored).
7132 UpdateValueMissingValidityStateForRadio(true);
7133 container->RemoveFromRadioGroup(name, static_cast<nsIFormControl*>(this));
7134 }
7135
7136 bool
IsHTMLFocusable(bool aWithMouse,bool * aIsFocusable,int32_t * aTabIndex)7137 HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t* aTabIndex)
7138 {
7139 if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(aWithMouse, aIsFocusable,
7140 aTabIndex))
7141 {
7142 return true;
7143 }
7144
7145 if (IsDisabled()) {
7146 *aIsFocusable = false;
7147 return true;
7148 }
7149
7150 if (IsSingleLineTextControl(false) ||
7151 mType == NS_FORM_INPUT_RANGE) {
7152 *aIsFocusable = true;
7153 return false;
7154 }
7155
7156 #ifdef XP_MACOSX
7157 const bool defaultFocusable = !aWithMouse || nsFocusManager::sMouseFocusesFormControl;
7158 #else
7159 const bool defaultFocusable = true;
7160 #endif
7161
7162 if (mType == NS_FORM_INPUT_FILE ||
7163 mType == NS_FORM_INPUT_NUMBER ||
7164 mType == NS_FORM_INPUT_TIME) {
7165 if (aTabIndex) {
7166 // We only want our native anonymous child to be tabable to, not ourself.
7167 *aTabIndex = -1;
7168 }
7169 if (mType == NS_FORM_INPUT_NUMBER ||
7170 mType == NS_FORM_INPUT_TIME) {
7171 *aIsFocusable = true;
7172 } else {
7173 *aIsFocusable = defaultFocusable;
7174 }
7175 return true;
7176 }
7177
7178 if (mType == NS_FORM_INPUT_HIDDEN) {
7179 if (aTabIndex) {
7180 *aTabIndex = -1;
7181 }
7182 *aIsFocusable = false;
7183 return false;
7184 }
7185
7186 if (!aTabIndex) {
7187 // The other controls are all focusable
7188 *aIsFocusable = defaultFocusable;
7189 return false;
7190 }
7191
7192 if (mType != NS_FORM_INPUT_RADIO) {
7193 *aIsFocusable = defaultFocusable;
7194 return false;
7195 }
7196
7197 if (mChecked) {
7198 // Selected radio buttons are tabbable
7199 *aIsFocusable = defaultFocusable;
7200 return false;
7201 }
7202
7203 // Current radio button is not selected.
7204 // But make it tabbable if nothing in group is selected.
7205 nsIRadioGroupContainer* container = GetRadioGroupContainer();
7206 if (!container) {
7207 *aIsFocusable = defaultFocusable;
7208 return false;
7209 }
7210
7211 nsAutoString name;
7212 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
7213
7214 if (container->GetCurrentRadioButton(name)) {
7215 *aTabIndex = -1;
7216 }
7217 *aIsFocusable = defaultFocusable;
7218 return false;
7219 }
7220
7221 nsresult
VisitGroup(nsIRadioVisitor * aVisitor,bool aFlushContent)7222 HTMLInputElement::VisitGroup(nsIRadioVisitor* aVisitor, bool aFlushContent)
7223 {
7224 nsIRadioGroupContainer* container = GetRadioGroupContainer();
7225 if (container) {
7226 nsAutoString name;
7227 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
7228 return container->WalkRadioGroup(name, aVisitor, aFlushContent);
7229 }
7230
7231 aVisitor->Visit(this);
7232 return NS_OK;
7233 }
7234
7235 HTMLInputElement::ValueModeType
GetValueMode() const7236 HTMLInputElement::GetValueMode() const
7237 {
7238 switch (mType)
7239 {
7240 case NS_FORM_INPUT_HIDDEN:
7241 case NS_FORM_INPUT_SUBMIT:
7242 case NS_FORM_INPUT_BUTTON:
7243 case NS_FORM_INPUT_RESET:
7244 case NS_FORM_INPUT_IMAGE:
7245 return VALUE_MODE_DEFAULT;
7246 case NS_FORM_INPUT_CHECKBOX:
7247 case NS_FORM_INPUT_RADIO:
7248 return VALUE_MODE_DEFAULT_ON;
7249 case NS_FORM_INPUT_FILE:
7250 return VALUE_MODE_FILENAME;
7251 #ifdef DEBUG
7252 case NS_FORM_INPUT_TEXT:
7253 case NS_FORM_INPUT_PASSWORD:
7254 case NS_FORM_INPUT_SEARCH:
7255 case NS_FORM_INPUT_TEL:
7256 case NS_FORM_INPUT_EMAIL:
7257 case NS_FORM_INPUT_URL:
7258 case NS_FORM_INPUT_NUMBER:
7259 case NS_FORM_INPUT_RANGE:
7260 case NS_FORM_INPUT_DATE:
7261 case NS_FORM_INPUT_TIME:
7262 case NS_FORM_INPUT_COLOR:
7263 case NS_FORM_INPUT_MONTH:
7264 case NS_FORM_INPUT_WEEK:
7265 case NS_FORM_INPUT_DATETIME_LOCAL:
7266 return VALUE_MODE_VALUE;
7267 default:
7268 NS_NOTYETIMPLEMENTED("Unexpected input type in GetValueMode()");
7269 return VALUE_MODE_VALUE;
7270 #else // DEBUG
7271 default:
7272 return VALUE_MODE_VALUE;
7273 #endif // DEBUG
7274 }
7275 }
7276
7277 bool
IsMutable() const7278 HTMLInputElement::IsMutable() const
7279 {
7280 return !IsDisabled() &&
7281 !(DoesReadOnlyApply() &&
7282 HasAttr(kNameSpaceID_None, nsGkAtoms::readonly));
7283 }
7284
7285 bool
DoesReadOnlyApply() const7286 HTMLInputElement::DoesReadOnlyApply() const
7287 {
7288 switch (mType)
7289 {
7290 case NS_FORM_INPUT_HIDDEN:
7291 case NS_FORM_INPUT_BUTTON:
7292 case NS_FORM_INPUT_IMAGE:
7293 case NS_FORM_INPUT_RESET:
7294 case NS_FORM_INPUT_SUBMIT:
7295 case NS_FORM_INPUT_RADIO:
7296 case NS_FORM_INPUT_FILE:
7297 case NS_FORM_INPUT_CHECKBOX:
7298 case NS_FORM_INPUT_RANGE:
7299 case NS_FORM_INPUT_COLOR:
7300 return false;
7301 #ifdef DEBUG
7302 case NS_FORM_INPUT_TEXT:
7303 case NS_FORM_INPUT_PASSWORD:
7304 case NS_FORM_INPUT_SEARCH:
7305 case NS_FORM_INPUT_TEL:
7306 case NS_FORM_INPUT_EMAIL:
7307 case NS_FORM_INPUT_URL:
7308 case NS_FORM_INPUT_NUMBER:
7309 case NS_FORM_INPUT_DATE:
7310 case NS_FORM_INPUT_TIME:
7311 case NS_FORM_INPUT_MONTH:
7312 case NS_FORM_INPUT_WEEK:
7313 case NS_FORM_INPUT_DATETIME_LOCAL:
7314 return true;
7315 default:
7316 NS_NOTYETIMPLEMENTED("Unexpected input type in DoesReadOnlyApply()");
7317 return true;
7318 #else // DEBUG
7319 default:
7320 return true;
7321 #endif // DEBUG
7322 }
7323 }
7324
7325 bool
DoesRequiredApply() const7326 HTMLInputElement::DoesRequiredApply() const
7327 {
7328 switch (mType)
7329 {
7330 case NS_FORM_INPUT_HIDDEN:
7331 case NS_FORM_INPUT_BUTTON:
7332 case NS_FORM_INPUT_IMAGE:
7333 case NS_FORM_INPUT_RESET:
7334 case NS_FORM_INPUT_SUBMIT:
7335 case NS_FORM_INPUT_RANGE:
7336 case NS_FORM_INPUT_COLOR:
7337 return false;
7338 #ifdef DEBUG
7339 case NS_FORM_INPUT_RADIO:
7340 case NS_FORM_INPUT_CHECKBOX:
7341 case NS_FORM_INPUT_FILE:
7342 case NS_FORM_INPUT_TEXT:
7343 case NS_FORM_INPUT_PASSWORD:
7344 case NS_FORM_INPUT_SEARCH:
7345 case NS_FORM_INPUT_TEL:
7346 case NS_FORM_INPUT_EMAIL:
7347 case NS_FORM_INPUT_URL:
7348 case NS_FORM_INPUT_NUMBER:
7349 case NS_FORM_INPUT_DATE:
7350 case NS_FORM_INPUT_TIME:
7351 case NS_FORM_INPUT_MONTH:
7352 case NS_FORM_INPUT_WEEK:
7353 case NS_FORM_INPUT_DATETIME_LOCAL:
7354 return true;
7355 default:
7356 NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
7357 return true;
7358 #else // DEBUG
7359 default:
7360 return true;
7361 #endif // DEBUG
7362 }
7363 }
7364
7365 bool
PlaceholderApplies() const7366 HTMLInputElement::PlaceholderApplies() const
7367 {
7368 if (IsDateTimeInputType(mType)) {
7369 return false;
7370 }
7371
7372 return IsSingleLineTextControl(false);
7373 }
7374
7375 bool
DoesPatternApply() const7376 HTMLInputElement::DoesPatternApply() const
7377 {
7378 // TODO: temporary until bug 773205 is fixed.
7379 if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
7380 return false;
7381 }
7382
7383 return IsSingleLineTextControl(false);
7384 }
7385
7386 bool
DoesMinMaxApply() const7387 HTMLInputElement::DoesMinMaxApply() const
7388 {
7389 switch (mType)
7390 {
7391 case NS_FORM_INPUT_NUMBER:
7392 case NS_FORM_INPUT_DATE:
7393 case NS_FORM_INPUT_TIME:
7394 case NS_FORM_INPUT_RANGE:
7395 case NS_FORM_INPUT_MONTH:
7396 case NS_FORM_INPUT_WEEK:
7397 case NS_FORM_INPUT_DATETIME_LOCAL:
7398 return true;
7399 #ifdef DEBUG
7400 case NS_FORM_INPUT_RESET:
7401 case NS_FORM_INPUT_SUBMIT:
7402 case NS_FORM_INPUT_IMAGE:
7403 case NS_FORM_INPUT_BUTTON:
7404 case NS_FORM_INPUT_HIDDEN:
7405 case NS_FORM_INPUT_RADIO:
7406 case NS_FORM_INPUT_CHECKBOX:
7407 case NS_FORM_INPUT_FILE:
7408 case NS_FORM_INPUT_TEXT:
7409 case NS_FORM_INPUT_PASSWORD:
7410 case NS_FORM_INPUT_SEARCH:
7411 case NS_FORM_INPUT_TEL:
7412 case NS_FORM_INPUT_EMAIL:
7413 case NS_FORM_INPUT_URL:
7414 case NS_FORM_INPUT_COLOR:
7415 return false;
7416 default:
7417 NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
7418 return false;
7419 #else // DEBUG
7420 default:
7421 return false;
7422 #endif // DEBUG
7423 }
7424 }
7425
7426 bool
DoesAutocompleteApply() const7427 HTMLInputElement::DoesAutocompleteApply() const
7428 {
7429 switch (mType)
7430 {
7431 case NS_FORM_INPUT_HIDDEN:
7432 case NS_FORM_INPUT_TEXT:
7433 case NS_FORM_INPUT_SEARCH:
7434 case NS_FORM_INPUT_URL:
7435 case NS_FORM_INPUT_TEL:
7436 case NS_FORM_INPUT_EMAIL:
7437 case NS_FORM_INPUT_PASSWORD:
7438 case NS_FORM_INPUT_DATE:
7439 case NS_FORM_INPUT_TIME:
7440 case NS_FORM_INPUT_NUMBER:
7441 case NS_FORM_INPUT_RANGE:
7442 case NS_FORM_INPUT_COLOR:
7443 case NS_FORM_INPUT_MONTH:
7444 case NS_FORM_INPUT_WEEK:
7445 case NS_FORM_INPUT_DATETIME_LOCAL:
7446 return true;
7447 #ifdef DEBUG
7448 case NS_FORM_INPUT_RESET:
7449 case NS_FORM_INPUT_SUBMIT:
7450 case NS_FORM_INPUT_IMAGE:
7451 case NS_FORM_INPUT_BUTTON:
7452 case NS_FORM_INPUT_RADIO:
7453 case NS_FORM_INPUT_CHECKBOX:
7454 case NS_FORM_INPUT_FILE:
7455 return false;
7456 default:
7457 NS_NOTYETIMPLEMENTED("Unexpected input type in DoesAutocompleteApply()");
7458 return false;
7459 #else // DEBUG
7460 default:
7461 return false;
7462 #endif // DEBUG
7463 }
7464 }
7465
7466 Decimal
GetStep() const7467 HTMLInputElement::GetStep() const
7468 {
7469 MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
7470
7471 if (!HasAttr(kNameSpaceID_None, nsGkAtoms::step)) {
7472 return GetDefaultStep() * GetStepScaleFactor();
7473 }
7474
7475 nsAutoString stepStr;
7476 GetAttr(kNameSpaceID_None, nsGkAtoms::step, stepStr);
7477
7478 if (stepStr.LowerCaseEqualsLiteral("any")) {
7479 // The element can't suffer from step mismatch if there is no step.
7480 return kStepAny;
7481 }
7482
7483 Decimal step = StringToDecimal(stepStr);
7484 if (!step.isFinite() || step <= Decimal(0)) {
7485 step = GetDefaultStep();
7486 }
7487
7488 // For input type=date, we round the step value to have a rounded day.
7489 if (mType == NS_FORM_INPUT_DATE || mType == NS_FORM_INPUT_MONTH ||
7490 mType == NS_FORM_INPUT_WEEK) {
7491 step = std::max(step.round(), Decimal(1));
7492 }
7493
7494 return step * GetStepScaleFactor();
7495 }
7496
7497 // nsIConstraintValidation
7498
7499 NS_IMETHODIMP
SetCustomValidity(const nsAString & aError)7500 HTMLInputElement::SetCustomValidity(const nsAString& aError)
7501 {
7502 nsIConstraintValidation::SetCustomValidity(aError);
7503
7504 UpdateState(true);
7505
7506 return NS_OK;
7507 }
7508
7509 bool
IsTooLong()7510 HTMLInputElement::IsTooLong()
7511 {
7512 if (!mValueChanged ||
7513 !mLastValueChangeWasInteractive ||
7514 !MinOrMaxLengthApplies() ||
7515 !HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength)) {
7516 return false;
7517 }
7518
7519 int32_t maxLength = MaxLength();
7520
7521 // Maxlength of -1 means parsing error.
7522 if (maxLength == -1) {
7523 return false;
7524 }
7525
7526 int32_t textLength = -1;
7527 GetTextLength(&textLength);
7528
7529 return textLength > maxLength;
7530 }
7531
7532 bool
IsTooShort()7533 HTMLInputElement::IsTooShort()
7534 {
7535 if (!mValueChanged ||
7536 !mLastValueChangeWasInteractive ||
7537 !MinOrMaxLengthApplies() ||
7538 !HasAttr(kNameSpaceID_None, nsGkAtoms::minlength)) {
7539 return false;
7540 }
7541
7542 int32_t minLength = MinLength();
7543
7544 // Minlength of -1 means parsing error.
7545 if (minLength == -1) {
7546 return false;
7547 }
7548
7549 int32_t textLength = -1;
7550 GetTextLength(&textLength);
7551
7552 return textLength && textLength < minLength;
7553 }
7554
7555 bool
IsValueMissing() const7556 HTMLInputElement::IsValueMissing() const
7557 {
7558 // Should use UpdateValueMissingValidityStateForRadio() for type radio.
7559 MOZ_ASSERT(mType != NS_FORM_INPUT_RADIO);
7560
7561 if (!HasAttr(kNameSpaceID_None, nsGkAtoms::required) ||
7562 !DoesRequiredApply()) {
7563 return false;
7564 }
7565
7566 if (!IsMutable()) {
7567 return false;
7568 }
7569
7570 switch (GetValueMode()) {
7571 case VALUE_MODE_VALUE:
7572 return IsValueEmpty();
7573
7574 case VALUE_MODE_FILENAME:
7575 return GetFilesOrDirectoriesInternal().IsEmpty();
7576
7577 case VALUE_MODE_DEFAULT_ON:
7578 // This should not be used for type radio.
7579 // See the MOZ_ASSERT at the beginning of the method.
7580 return !mChecked;
7581
7582 case VALUE_MODE_DEFAULT:
7583 default:
7584 return false;
7585 }
7586 }
7587
7588 bool
HasTypeMismatch() const7589 HTMLInputElement::HasTypeMismatch() const
7590 {
7591 if (mType != NS_FORM_INPUT_EMAIL && mType != NS_FORM_INPUT_URL) {
7592 return false;
7593 }
7594
7595 nsAutoString value;
7596 NS_ENSURE_SUCCESS(GetValueInternal(value), false);
7597
7598 if (value.IsEmpty()) {
7599 return false;
7600 }
7601
7602 if (mType == NS_FORM_INPUT_EMAIL) {
7603 return HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)
7604 ? !IsValidEmailAddressList(value) : !IsValidEmailAddress(value);
7605 } else if (mType == NS_FORM_INPUT_URL) {
7606 /**
7607 * TODO:
7608 * The URL is not checked as the HTML5 specifications want it to be because
7609 * there is no code to check for a valid URI/IRI according to 3986 and 3987
7610 * RFC's at the moment, see bug 561586.
7611 *
7612 * RFC 3987 (IRI) implementation: bug 42899
7613 *
7614 * HTML5 specifications:
7615 * http://dev.w3.org/html5/spec/infrastructure.html#valid-url
7616 */
7617 nsCOMPtr<nsIIOService> ioService = do_GetIOService();
7618 nsCOMPtr<nsIURI> uri;
7619
7620 return !NS_SUCCEEDED(ioService->NewURI(NS_ConvertUTF16toUTF8(value), nullptr,
7621 nullptr, getter_AddRefs(uri)));
7622 }
7623
7624 return false;
7625 }
7626
7627 bool
HasPatternMismatch() const7628 HTMLInputElement::HasPatternMismatch() const
7629 {
7630 if (!DoesPatternApply() ||
7631 !HasAttr(kNameSpaceID_None, nsGkAtoms::pattern)) {
7632 return false;
7633 }
7634
7635 nsAutoString pattern;
7636 GetAttr(kNameSpaceID_None, nsGkAtoms::pattern, pattern);
7637
7638 nsAutoString value;
7639 NS_ENSURE_SUCCESS(GetValueInternal(value), false);
7640
7641 if (value.IsEmpty()) {
7642 return false;
7643 }
7644
7645 nsIDocument* doc = OwnerDoc();
7646
7647 return !nsContentUtils::IsPatternMatching(value, pattern, doc);
7648 }
7649
7650 bool
IsRangeOverflow() const7651 HTMLInputElement::IsRangeOverflow() const
7652 {
7653 // TODO: this is temporary until bug 888331 is fixed.
7654 if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
7655 return false;
7656 }
7657
7658 Decimal maximum = GetMaximum();
7659 if (maximum.isNaN()) {
7660 return false;
7661 }
7662
7663 Decimal value = GetValueAsDecimal();
7664 if (value.isNaN()) {
7665 return false;
7666 }
7667
7668 return value > maximum;
7669 }
7670
7671 bool
IsRangeUnderflow() const7672 HTMLInputElement::IsRangeUnderflow() const
7673 {
7674 // TODO: this is temporary until bug 888331 is fixed.
7675 if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
7676 return false;
7677 }
7678
7679 Decimal minimum = GetMinimum();
7680 if (minimum.isNaN()) {
7681 return false;
7682 }
7683
7684 Decimal value = GetValueAsDecimal();
7685 if (value.isNaN()) {
7686 return false;
7687 }
7688
7689 return value < minimum;
7690 }
7691
7692 bool
HasStepMismatch(bool aUseZeroIfValueNaN) const7693 HTMLInputElement::HasStepMismatch(bool aUseZeroIfValueNaN) const
7694 {
7695 if (!DoesStepApply()) {
7696 return false;
7697 }
7698
7699 Decimal value = GetValueAsDecimal();
7700 if (value.isNaN()) {
7701 if (aUseZeroIfValueNaN) {
7702 value = Decimal(0);
7703 } else {
7704 // The element can't suffer from step mismatch if it's value isn't a number.
7705 return false;
7706 }
7707 }
7708
7709 Decimal step = GetStep();
7710 if (step == kStepAny) {
7711 return false;
7712 }
7713
7714 // Value has to be an integral multiple of step.
7715 return NS_floorModulo(value - GetStepBase(), step) != Decimal(0);
7716 }
7717
7718 /**
7719 * Takes aEmail and attempts to convert everything after the first "@"
7720 * character (if anything) to punycode before returning the complete result via
7721 * the aEncodedEmail out-param. The aIndexOfAt out-param is set to the index of
7722 * the "@" character.
7723 *
7724 * If no "@" is found in aEmail, aEncodedEmail is simply set to aEmail and
7725 * the aIndexOfAt out-param is set to kNotFound.
7726 *
7727 * Returns true in all cases unless an attempt to punycode encode fails. If
7728 * false is returned, aEncodedEmail has not been set.
7729 *
7730 * This function exists because ConvertUTF8toACE() splits on ".", meaning that
7731 * for 'user.name@sld.tld' it would treat "name@sld" as a label. We want to
7732 * encode the domain part only.
7733 */
PunycodeEncodeEmailAddress(const nsAString & aEmail,nsAutoCString & aEncodedEmail,uint32_t * aIndexOfAt)7734 static bool PunycodeEncodeEmailAddress(const nsAString& aEmail,
7735 nsAutoCString& aEncodedEmail,
7736 uint32_t* aIndexOfAt)
7737 {
7738 nsAutoCString value = NS_ConvertUTF16toUTF8(aEmail);
7739 *aIndexOfAt = (uint32_t)value.FindChar('@');
7740
7741 if (*aIndexOfAt == (uint32_t)kNotFound ||
7742 *aIndexOfAt == value.Length() - 1) {
7743 aEncodedEmail = value;
7744 return true;
7745 }
7746
7747 nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
7748 if (!idnSrv) {
7749 NS_ERROR("nsIIDNService isn't present!");
7750 return false;
7751 }
7752
7753 uint32_t indexOfDomain = *aIndexOfAt + 1;
7754
7755 const nsDependentCSubstring domain = Substring(value, indexOfDomain);
7756 bool ace;
7757 if (NS_SUCCEEDED(idnSrv->IsACE(domain, &ace)) && !ace) {
7758 nsAutoCString domainACE;
7759 if (NS_FAILED(idnSrv->ConvertUTF8toACE(domain, domainACE))) {
7760 return false;
7761 }
7762 value.Replace(indexOfDomain, domain.Length(), domainACE);
7763 }
7764
7765 aEncodedEmail = value;
7766 return true;
7767 }
7768
7769 bool
HasBadInput() const7770 HTMLInputElement::HasBadInput() const
7771 {
7772 if (mType == NS_FORM_INPUT_NUMBER) {
7773 nsAutoString value;
7774 GetValueInternal(value);
7775 if (!value.IsEmpty()) {
7776 // The input can't be bad, otherwise it would have been sanitized to the
7777 // empty string.
7778 NS_ASSERTION(!GetValueAsDecimal().isNaN(), "Should have sanitized");
7779 return false;
7780 }
7781 nsNumberControlFrame* numberControlFrame =
7782 do_QueryFrame(GetPrimaryFrame());
7783 if (numberControlFrame &&
7784 !numberControlFrame->AnonTextControlIsEmpty()) {
7785 // The input the user entered failed to parse as a number.
7786 return true;
7787 }
7788 return false;
7789 }
7790 if (mType == NS_FORM_INPUT_EMAIL) {
7791 // With regards to suffering from bad input the spec says that only the
7792 // punycode conversion works, so we don't care whether the email address is
7793 // valid or not here. (If the email address is invalid then we will be
7794 // suffering from a type mismatch.)
7795 nsAutoString value;
7796 nsAutoCString unused;
7797 uint32_t unused2;
7798 NS_ENSURE_SUCCESS(GetValueInternal(value), false);
7799 HTMLSplitOnSpacesTokenizer tokenizer(value, ',');
7800 while (tokenizer.hasMoreTokens()) {
7801 if (!PunycodeEncodeEmailAddress(tokenizer.nextToken(), unused, &unused2)) {
7802 return true;
7803 }
7804 }
7805 return false;
7806 }
7807 return false;
7808 }
7809
7810 void
UpdateTooLongValidityState()7811 HTMLInputElement::UpdateTooLongValidityState()
7812 {
7813 SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
7814 }
7815
7816 void
UpdateTooShortValidityState()7817 HTMLInputElement::UpdateTooShortValidityState()
7818 {
7819 SetValidityState(VALIDITY_STATE_TOO_SHORT, IsTooShort());
7820 }
7821
7822 void
UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf)7823 HTMLInputElement::UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf)
7824 {
7825 bool notify = mDoneCreating;
7826 nsCOMPtr<nsIDOMHTMLInputElement> selection = GetSelectedRadioButton();
7827
7828 aIgnoreSelf = aIgnoreSelf || !IsMutable();
7829
7830 // If there is no selection, that might mean the radio is not in a group.
7831 // In that case, we can look for the checked state of the radio.
7832 bool selected = selection || (!aIgnoreSelf && mChecked);
7833 bool required = !aIgnoreSelf && HasAttr(kNameSpaceID_None, nsGkAtoms::required);
7834 bool valueMissing = false;
7835
7836 nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
7837
7838 if (!container) {
7839 SetValidityState(VALIDITY_STATE_VALUE_MISSING,
7840 IsMutable() && required && !selected);
7841 return;
7842 }
7843
7844 nsAutoString name;
7845 GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
7846
7847 // If the current radio is required and not ignored, we can assume the entire
7848 // group is required.
7849 if (!required) {
7850 required = (aIgnoreSelf && HasAttr(kNameSpaceID_None, nsGkAtoms::required))
7851 ? container->GetRequiredRadioCount(name) - 1
7852 : container->GetRequiredRadioCount(name);
7853 }
7854
7855 valueMissing = required && !selected;
7856
7857 if (container->GetValueMissingState(name) != valueMissing) {
7858 container->SetValueMissingState(name, valueMissing);
7859
7860 SetValidityState(VALIDITY_STATE_VALUE_MISSING, valueMissing);
7861
7862 // nsRadioSetValueMissingState will call ContentStateChanged while visiting.
7863 nsAutoScriptBlocker scriptBlocker;
7864 nsCOMPtr<nsIRadioVisitor> visitor =
7865 new nsRadioSetValueMissingState(this, valueMissing, notify);
7866 VisitGroup(visitor, notify);
7867 }
7868 }
7869
7870 void
UpdateValueMissingValidityState()7871 HTMLInputElement::UpdateValueMissingValidityState()
7872 {
7873 if (mType == NS_FORM_INPUT_RADIO) {
7874 UpdateValueMissingValidityStateForRadio(false);
7875 return;
7876 }
7877
7878 SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
7879 }
7880
7881 void
UpdateTypeMismatchValidityState()7882 HTMLInputElement::UpdateTypeMismatchValidityState()
7883 {
7884 SetValidityState(VALIDITY_STATE_TYPE_MISMATCH, HasTypeMismatch());
7885 }
7886
7887 void
UpdatePatternMismatchValidityState()7888 HTMLInputElement::UpdatePatternMismatchValidityState()
7889 {
7890 SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH, HasPatternMismatch());
7891 }
7892
7893 void
UpdateRangeOverflowValidityState()7894 HTMLInputElement::UpdateRangeOverflowValidityState()
7895 {
7896 SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, IsRangeOverflow());
7897 }
7898
7899 void
UpdateRangeUnderflowValidityState()7900 HTMLInputElement::UpdateRangeUnderflowValidityState()
7901 {
7902 SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW, IsRangeUnderflow());
7903 }
7904
7905 void
UpdateStepMismatchValidityState()7906 HTMLInputElement::UpdateStepMismatchValidityState()
7907 {
7908 SetValidityState(VALIDITY_STATE_STEP_MISMATCH, HasStepMismatch());
7909 }
7910
7911 void
UpdateBadInputValidityState()7912 HTMLInputElement::UpdateBadInputValidityState()
7913 {
7914 SetValidityState(VALIDITY_STATE_BAD_INPUT, HasBadInput());
7915 }
7916
7917 void
UpdateAllValidityStates(bool aNotify)7918 HTMLInputElement::UpdateAllValidityStates(bool aNotify)
7919 {
7920 bool validBefore = IsValid();
7921 UpdateTooLongValidityState();
7922 UpdateTooShortValidityState();
7923 UpdateValueMissingValidityState();
7924 UpdateTypeMismatchValidityState();
7925 UpdatePatternMismatchValidityState();
7926 UpdateRangeOverflowValidityState();
7927 UpdateRangeUnderflowValidityState();
7928 UpdateStepMismatchValidityState();
7929 UpdateBadInputValidityState();
7930
7931 if (validBefore != IsValid()) {
7932 UpdateState(aNotify);
7933 }
7934 }
7935
7936 void
UpdateBarredFromConstraintValidation()7937 HTMLInputElement::UpdateBarredFromConstraintValidation()
7938 {
7939 SetBarredFromConstraintValidation(mType == NS_FORM_INPUT_HIDDEN ||
7940 mType == NS_FORM_INPUT_BUTTON ||
7941 mType == NS_FORM_INPUT_RESET ||
7942 HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) ||
7943 IsDisabled());
7944 }
7945
7946 void
GetValidationMessage(nsAString & aValidationMessage,ErrorResult & aRv)7947 HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
7948 ErrorResult& aRv)
7949 {
7950 aRv = GetValidationMessage(aValidationMessage);
7951 }
7952
7953 nsresult
GetValidationMessage(nsAString & aValidationMessage,ValidityStateType aType)7954 HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
7955 ValidityStateType aType)
7956 {
7957 nsresult rv = NS_OK;
7958
7959 switch (aType)
7960 {
7961 case VALIDITY_STATE_TOO_LONG:
7962 {
7963 nsXPIDLString message;
7964 int32_t maxLength = MaxLength();
7965 int32_t textLength = -1;
7966 nsAutoString strMaxLength;
7967 nsAutoString strTextLength;
7968
7969 GetTextLength(&textLength);
7970
7971 strMaxLength.AppendInt(maxLength);
7972 strTextLength.AppendInt(textLength);
7973
7974 const char16_t* params[] = { strMaxLength.get(), strTextLength.get() };
7975 rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
7976 "FormValidationTextTooLong",
7977 params, message);
7978 aValidationMessage = message;
7979 break;
7980 }
7981 case VALIDITY_STATE_TOO_SHORT:
7982 {
7983 nsXPIDLString message;
7984 int32_t minLength = MinLength();
7985 int32_t textLength = -1;
7986 nsAutoString strMinLength;
7987 nsAutoString strTextLength;
7988
7989 GetTextLength(&textLength);
7990
7991 strMinLength.AppendInt(minLength);
7992 strTextLength.AppendInt(textLength);
7993
7994 const char16_t* params[] = { strMinLength.get(), strTextLength.get() };
7995 rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
7996 "FormValidationTextTooShort",
7997 params, message);
7998 aValidationMessage = message;
7999 break;
8000 }
8001 case VALIDITY_STATE_VALUE_MISSING:
8002 {
8003 nsXPIDLString message;
8004 nsAutoCString key;
8005 switch (mType)
8006 {
8007 case NS_FORM_INPUT_FILE:
8008 key.AssignLiteral("FormValidationFileMissing");
8009 break;
8010 case NS_FORM_INPUT_CHECKBOX:
8011 key.AssignLiteral("FormValidationCheckboxMissing");
8012 break;
8013 case NS_FORM_INPUT_RADIO:
8014 key.AssignLiteral("FormValidationRadioMissing");
8015 break;
8016 case NS_FORM_INPUT_NUMBER:
8017 key.AssignLiteral("FormValidationBadInputNumber");
8018 break;
8019 default:
8020 key.AssignLiteral("FormValidationValueMissing");
8021 }
8022 rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8023 key.get(), message);
8024 aValidationMessage = message;
8025 break;
8026 }
8027 case VALIDITY_STATE_TYPE_MISMATCH:
8028 {
8029 nsXPIDLString message;
8030 nsAutoCString key;
8031 if (mType == NS_FORM_INPUT_EMAIL) {
8032 key.AssignLiteral("FormValidationInvalidEmail");
8033 } else if (mType == NS_FORM_INPUT_URL) {
8034 key.AssignLiteral("FormValidationInvalidURL");
8035 } else {
8036 return NS_ERROR_UNEXPECTED;
8037 }
8038 rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8039 key.get(), message);
8040 aValidationMessage = message;
8041 break;
8042 }
8043 case VALIDITY_STATE_PATTERN_MISMATCH:
8044 {
8045 nsXPIDLString message;
8046 nsAutoString title;
8047 GetAttr(kNameSpaceID_None, nsGkAtoms::title, title);
8048 if (title.IsEmpty()) {
8049 rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8050 "FormValidationPatternMismatch",
8051 message);
8052 } else {
8053 if (title.Length() > nsIConstraintValidation::sContentSpecifiedMaxLengthMessage) {
8054 title.Truncate(nsIConstraintValidation::sContentSpecifiedMaxLengthMessage);
8055 }
8056 const char16_t* params[] = { title.get() };
8057 rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8058 "FormValidationPatternMismatchWithTitle",
8059 params, message);
8060 }
8061 aValidationMessage = message;
8062 break;
8063 }
8064 case VALIDITY_STATE_RANGE_OVERFLOW:
8065 {
8066 static const char kNumberOverTemplate[] = "FormValidationNumberRangeOverflow";
8067 static const char kDateOverTemplate[] = "FormValidationDateRangeOverflow";
8068 static const char kTimeOverTemplate[] = "FormValidationTimeRangeOverflow";
8069
8070 const char* msgTemplate;
8071 nsXPIDLString message;
8072
8073 nsAutoString maxStr;
8074 if (mType == NS_FORM_INPUT_NUMBER ||
8075 mType == NS_FORM_INPUT_RANGE) {
8076 msgTemplate = kNumberOverTemplate;
8077
8078 //We want to show the value as parsed when it's a number
8079 Decimal maximum = GetMaximum();
8080 MOZ_ASSERT(!maximum.isNaN());
8081
8082 char buf[32];
8083 DebugOnly<bool> ok = maximum.toString(buf, ArrayLength(buf));
8084 maxStr.AssignASCII(buf);
8085 MOZ_ASSERT(ok, "buf not big enough");
8086 } else if (IsDateTimeInputType(mType)) {
8087 msgTemplate = mType == NS_FORM_INPUT_TIME ? kTimeOverTemplate : kDateOverTemplate;
8088 GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
8089 } else {
8090 msgTemplate = kNumberOverTemplate;
8091 NS_NOTREACHED("Unexpected input type");
8092 }
8093
8094 const char16_t* params[] = { maxStr.get() };
8095 rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8096 msgTemplate,
8097 params, message);
8098 aValidationMessage = message;
8099 break;
8100 }
8101 case VALIDITY_STATE_RANGE_UNDERFLOW:
8102 {
8103 static const char kNumberUnderTemplate[] = "FormValidationNumberRangeUnderflow";
8104 static const char kDateUnderTemplate[] = "FormValidationDateRangeUnderflow";
8105 static const char kTimeUnderTemplate[] = "FormValidationTimeRangeUnderflow";
8106
8107 const char* msgTemplate;
8108 nsXPIDLString message;
8109
8110 nsAutoString minStr;
8111 if (mType == NS_FORM_INPUT_NUMBER ||
8112 mType == NS_FORM_INPUT_RANGE) {
8113 msgTemplate = kNumberUnderTemplate;
8114
8115 Decimal minimum = GetMinimum();
8116 MOZ_ASSERT(!minimum.isNaN());
8117
8118 char buf[32];
8119 DebugOnly<bool> ok = minimum.toString(buf, ArrayLength(buf));
8120 minStr.AssignASCII(buf);
8121 MOZ_ASSERT(ok, "buf not big enough");
8122 } else if (IsDateTimeInputType(mType)) {
8123 msgTemplate = mType == NS_FORM_INPUT_TIME ? kTimeUnderTemplate : kDateUnderTemplate;
8124 GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
8125 } else {
8126 msgTemplate = kNumberUnderTemplate;
8127 NS_NOTREACHED("Unexpected input type");
8128 }
8129
8130 const char16_t* params[] = { minStr.get() };
8131 rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8132 msgTemplate,
8133 params, message);
8134 aValidationMessage = message;
8135 break;
8136 }
8137 case VALIDITY_STATE_STEP_MISMATCH:
8138 {
8139 nsXPIDLString message;
8140
8141 Decimal value = GetValueAsDecimal();
8142 MOZ_ASSERT(!value.isNaN());
8143
8144 Decimal step = GetStep();
8145 MOZ_ASSERT(step != kStepAny && step > Decimal(0));
8146
8147 Decimal stepBase = GetStepBase();
8148
8149 Decimal valueLow = value - NS_floorModulo(value - stepBase, step);
8150 Decimal valueHigh = value + step - NS_floorModulo(value - stepBase, step);
8151
8152 Decimal maximum = GetMaximum();
8153
8154 if (maximum.isNaN() || valueHigh <= maximum) {
8155 nsAutoString valueLowStr, valueHighStr;
8156 ConvertNumberToString(valueLow, valueLowStr);
8157 ConvertNumberToString(valueHigh, valueHighStr);
8158
8159 if (valueLowStr.Equals(valueHighStr)) {
8160 const char16_t* params[] = { valueLowStr.get() };
8161 rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8162 "FormValidationStepMismatchOneValue",
8163 params, message);
8164 } else {
8165 const char16_t* params[] = { valueLowStr.get(), valueHighStr.get() };
8166 rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8167 "FormValidationStepMismatch",
8168 params, message);
8169 }
8170 } else {
8171 nsAutoString valueLowStr;
8172 ConvertNumberToString(valueLow, valueLowStr);
8173
8174 const char16_t* params[] = { valueLowStr.get() };
8175 rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8176 "FormValidationStepMismatchOneValue",
8177 params, message);
8178 }
8179
8180 aValidationMessage = message;
8181 break;
8182 }
8183 case VALIDITY_STATE_BAD_INPUT:
8184 {
8185 nsXPIDLString message;
8186 nsAutoCString key;
8187 if (mType == NS_FORM_INPUT_NUMBER) {
8188 key.AssignLiteral("FormValidationBadInputNumber");
8189 } else if (mType == NS_FORM_INPUT_EMAIL) {
8190 key.AssignLiteral("FormValidationInvalidEmail");
8191 } else {
8192 return NS_ERROR_UNEXPECTED;
8193 }
8194 rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
8195 key.get(), message);
8196 aValidationMessage = message;
8197 break;
8198 }
8199 default:
8200 rv = nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType);
8201 }
8202
8203 return rv;
8204 }
8205
8206 //static
8207 bool
IsValidEmailAddressList(const nsAString & aValue)8208 HTMLInputElement::IsValidEmailAddressList(const nsAString& aValue)
8209 {
8210 HTMLSplitOnSpacesTokenizer tokenizer(aValue, ',');
8211
8212 while (tokenizer.hasMoreTokens()) {
8213 if (!IsValidEmailAddress(tokenizer.nextToken())) {
8214 return false;
8215 }
8216 }
8217
8218 return !tokenizer.separatorAfterCurrentToken();
8219 }
8220
8221 //static
8222 bool
IsValidEmailAddress(const nsAString & aValue)8223 HTMLInputElement::IsValidEmailAddress(const nsAString& aValue)
8224 {
8225 // Email addresses can't be empty and can't end with a '.' or '-'.
8226 if (aValue.IsEmpty() || aValue.Last() == '.' || aValue.Last() == '-') {
8227 return false;
8228 }
8229
8230 uint32_t atPos;
8231 nsAutoCString value;
8232 if (!PunycodeEncodeEmailAddress(aValue, value, &atPos) ||
8233 atPos == (uint32_t)kNotFound || atPos == 0 || atPos == value.Length() - 1) {
8234 // Could not encode, or "@" was not found, or it was at the start or end
8235 // of the input - in all cases, not a valid email address.
8236 return false;
8237 }
8238
8239 uint32_t length = value.Length();
8240 uint32_t i = 0;
8241
8242 // Parsing the username.
8243 for (; i < atPos; ++i) {
8244 char16_t c = value[i];
8245
8246 // The username characters have to be in this list to be valid.
8247 if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
8248 c == '.' || c == '!' || c == '#' || c == '$' || c == '%' ||
8249 c == '&' || c == '\''|| c == '*' || c == '+' || c == '-' ||
8250 c == '/' || c == '=' || c == '?' || c == '^' || c == '_' ||
8251 c == '`' || c == '{' || c == '|' || c == '}' || c == '~' )) {
8252 return false;
8253 }
8254 }
8255
8256 // Skip the '@'.
8257 ++i;
8258
8259 // The domain name can't begin with a dot or a dash.
8260 if (value[i] == '.' || value[i] == '-') {
8261 return false;
8262 }
8263
8264 // Parsing the domain name.
8265 for (; i < length; ++i) {
8266 char16_t c = value[i];
8267
8268 if (c == '.') {
8269 // A dot can't follow a dot or a dash.
8270 if (value[i-1] == '.' || value[i-1] == '-') {
8271 return false;
8272 }
8273 } else if (c == '-'){
8274 // A dash can't follow a dot.
8275 if (value[i-1] == '.') {
8276 return false;
8277 }
8278 } else if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
8279 c == '-')) {
8280 // The domain characters have to be in this list to be valid.
8281 return false;
8282 }
8283 }
8284
8285 return true;
8286 }
8287
NS_IMETHODIMP_(bool)8288 NS_IMETHODIMP_(bool)
8289 HTMLInputElement::IsSingleLineTextControl() const
8290 {
8291 return IsSingleLineTextControl(false);
8292 }
8293
NS_IMETHODIMP_(bool)8294 NS_IMETHODIMP_(bool)
8295 HTMLInputElement::IsTextArea() const
8296 {
8297 return false;
8298 }
8299
NS_IMETHODIMP_(bool)8300 NS_IMETHODIMP_(bool)
8301 HTMLInputElement::IsPlainTextControl() const
8302 {
8303 // need to check our HTML attribute and/or CSS.
8304 return true;
8305 }
8306
NS_IMETHODIMP_(bool)8307 NS_IMETHODIMP_(bool)
8308 HTMLInputElement::IsPasswordTextControl() const
8309 {
8310 return mType == NS_FORM_INPUT_PASSWORD;
8311 }
8312
NS_IMETHODIMP_(int32_t)8313 NS_IMETHODIMP_(int32_t)
8314 HTMLInputElement::GetCols()
8315 {
8316 // Else we know (assume) it is an input with size attr
8317 const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::size);
8318 if (attr && attr->Type() == nsAttrValue::eInteger) {
8319 int32_t cols = attr->GetIntegerValue();
8320 if (cols > 0) {
8321 return cols;
8322 }
8323 }
8324
8325 return DEFAULT_COLS;
8326 }
8327
NS_IMETHODIMP_(int32_t)8328 NS_IMETHODIMP_(int32_t)
8329 HTMLInputElement::GetWrapCols()
8330 {
8331 return 0; // only textarea's can have wrap cols
8332 }
8333
NS_IMETHODIMP_(int32_t)8334 NS_IMETHODIMP_(int32_t)
8335 HTMLInputElement::GetRows()
8336 {
8337 return DEFAULT_ROWS;
8338 }
8339
NS_IMETHODIMP_(void)8340 NS_IMETHODIMP_(void)
8341 HTMLInputElement::GetDefaultValueFromContent(nsAString& aValue)
8342 {
8343 nsTextEditorState *state = GetEditorState();
8344 if (state) {
8345 GetDefaultValue(aValue);
8346 // This is called by the frame to show the value.
8347 // We have to sanitize it when needed.
8348 if (mDoneCreating) {
8349 SanitizeValue(aValue);
8350 }
8351 }
8352 }
8353
NS_IMETHODIMP_(bool)8354 NS_IMETHODIMP_(bool)
8355 HTMLInputElement::ValueChanged() const
8356 {
8357 return mValueChanged;
8358 }
8359
NS_IMETHODIMP_(void)8360 NS_IMETHODIMP_(void)
8361 HTMLInputElement::GetTextEditorValue(nsAString& aValue,
8362 bool aIgnoreWrap) const
8363 {
8364 nsTextEditorState* state = GetEditorState();
8365 if (state) {
8366 state->GetValue(aValue, aIgnoreWrap);
8367 }
8368 }
8369
NS_IMETHODIMP_(void)8370 NS_IMETHODIMP_(void)
8371 HTMLInputElement::InitializeKeyboardEventListeners()
8372 {
8373 nsTextEditorState* state = GetEditorState();
8374 if (state) {
8375 state->InitializeKeyboardEventListeners();
8376 }
8377 }
8378
NS_IMETHODIMP_(void)8379 NS_IMETHODIMP_(void)
8380 HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
8381 {
8382 mLastValueChangeWasInteractive = aWasInteractiveUserChange;
8383
8384 UpdateAllValidityStates(aNotify);
8385
8386 if (HasDirAuto()) {
8387 SetDirectionIfAuto(true, aNotify);
8388 }
8389
8390 // :placeholder-shown pseudo-class may change when the value changes.
8391 // However, we don't want to waste cycles if the state doesn't apply.
8392 if (PlaceholderApplies() &&
8393 HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
8394 UpdateState(aNotify);
8395 }
8396 }
8397
NS_IMETHODIMP_(bool)8398 NS_IMETHODIMP_(bool)
8399 HTMLInputElement::HasCachedSelection()
8400 {
8401 bool isCached = false;
8402 nsTextEditorState* state = GetEditorState();
8403 if (state) {
8404 isCached = state->IsSelectionCached() &&
8405 state->HasNeverInitializedBefore() &&
8406 !state->GetSelectionProperties().IsDefault();
8407 if (isCached) {
8408 state->WillInitEagerly();
8409 }
8410 }
8411 return isCached;
8412 }
8413
8414 void
FieldSetDisabledChanged(bool aNotify)8415 HTMLInputElement::FieldSetDisabledChanged(bool aNotify)
8416 {
8417 UpdateValueMissingValidityState();
8418 UpdateBarredFromConstraintValidation();
8419
8420 nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
8421 }
8422
8423 void
SetFilePickerFiltersFromAccept(nsIFilePicker * filePicker)8424 HTMLInputElement::SetFilePickerFiltersFromAccept(nsIFilePicker* filePicker)
8425 {
8426 // We always add |filterAll|
8427 filePicker->AppendFilters(nsIFilePicker::filterAll);
8428
8429 NS_ASSERTION(HasAttr(kNameSpaceID_None, nsGkAtoms::accept),
8430 "You should not call SetFilePickerFiltersFromAccept if the"
8431 " element has no accept attribute!");
8432
8433 // Services to retrieve image/*, audio/*, video/* filters
8434 nsCOMPtr<nsIStringBundleService> stringService =
8435 mozilla::services::GetStringBundleService();
8436 if (!stringService) {
8437 return;
8438 }
8439 nsCOMPtr<nsIStringBundle> filterBundle;
8440 if (NS_FAILED(stringService->CreateBundle("chrome://global/content/filepicker.properties",
8441 getter_AddRefs(filterBundle)))) {
8442 return;
8443 }
8444
8445 // Service to retrieve mime type information for mime types filters
8446 nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
8447 if (!mimeService) {
8448 return;
8449 }
8450
8451 nsAutoString accept;
8452 GetAttr(kNameSpaceID_None, nsGkAtoms::accept, accept);
8453
8454 HTMLSplitOnSpacesTokenizer tokenizer(accept, ',');
8455
8456 nsTArray<nsFilePickerFilter> filters;
8457 nsString allExtensionsList;
8458
8459 bool allMimeTypeFiltersAreValid = true;
8460 bool atLeastOneFileExtensionFilter = false;
8461
8462 // Retrieve all filters
8463 while (tokenizer.hasMoreTokens()) {
8464 const nsDependentSubstring& token = tokenizer.nextToken();
8465
8466 if (token.IsEmpty()) {
8467 continue;
8468 }
8469
8470 int32_t filterMask = 0;
8471 nsString filterName;
8472 nsString extensionListStr;
8473
8474 // First, check for image/audio/video filters...
8475 if (token.EqualsLiteral("image/*")) {
8476 filterMask = nsIFilePicker::filterImages;
8477 filterBundle->GetStringFromName(u"imageFilter",
8478 getter_Copies(extensionListStr));
8479 } else if (token.EqualsLiteral("audio/*")) {
8480 filterMask = nsIFilePicker::filterAudio;
8481 filterBundle->GetStringFromName(u"audioFilter",
8482 getter_Copies(extensionListStr));
8483 } else if (token.EqualsLiteral("video/*")) {
8484 filterMask = nsIFilePicker::filterVideo;
8485 filterBundle->GetStringFromName(u"videoFilter",
8486 getter_Copies(extensionListStr));
8487 } else if (token.First() == '.') {
8488 if (token.Contains(';') || token.Contains('*')) {
8489 // Ignore this filter as it contains reserved characters
8490 continue;
8491 }
8492 extensionListStr = NS_LITERAL_STRING("*") + token;
8493 filterName = extensionListStr;
8494 atLeastOneFileExtensionFilter = true;
8495 } else {
8496 //... if no image/audio/video filter is found, check mime types filters
8497 nsCOMPtr<nsIMIMEInfo> mimeInfo;
8498 if (NS_FAILED(mimeService->GetFromTypeAndExtension(
8499 NS_ConvertUTF16toUTF8(token),
8500 EmptyCString(), // No extension
8501 getter_AddRefs(mimeInfo))) ||
8502 !mimeInfo) {
8503 allMimeTypeFiltersAreValid = false;
8504 continue;
8505 }
8506
8507 // Get a name for the filter: first try the description, then the mime type
8508 // name if there is no description
8509 mimeInfo->GetDescription(filterName);
8510 if (filterName.IsEmpty()) {
8511 nsCString mimeTypeName;
8512 mimeInfo->GetType(mimeTypeName);
8513 CopyUTF8toUTF16(mimeTypeName, filterName);
8514 }
8515
8516 // Get extension list
8517 nsCOMPtr<nsIUTF8StringEnumerator> extensions;
8518 mimeInfo->GetFileExtensions(getter_AddRefs(extensions));
8519
8520 bool hasMore;
8521 while (NS_SUCCEEDED(extensions->HasMore(&hasMore)) && hasMore) {
8522 nsCString extension;
8523 if (NS_FAILED(extensions->GetNext(extension))) {
8524 continue;
8525 }
8526 if (!extensionListStr.IsEmpty()) {
8527 extensionListStr.AppendLiteral("; ");
8528 }
8529 extensionListStr += NS_LITERAL_STRING("*.") +
8530 NS_ConvertUTF8toUTF16(extension);
8531 }
8532 }
8533
8534 if (!filterMask && (extensionListStr.IsEmpty() || filterName.IsEmpty())) {
8535 // No valid filter found
8536 allMimeTypeFiltersAreValid = false;
8537 continue;
8538 }
8539
8540 // If we arrived here, that means we have a valid filter: let's create it
8541 // and add it to our list, if no similar filter is already present
8542 nsFilePickerFilter filter;
8543 if (filterMask) {
8544 filter = nsFilePickerFilter(filterMask);
8545 } else {
8546 filter = nsFilePickerFilter(filterName, extensionListStr);
8547 }
8548
8549 if (!filters.Contains(filter)) {
8550 if (!allExtensionsList.IsEmpty()) {
8551 allExtensionsList.AppendLiteral("; ");
8552 }
8553 allExtensionsList += extensionListStr;
8554 filters.AppendElement(filter);
8555 }
8556 }
8557
8558 // Remove similar filters
8559 // Iterate over a copy, as we might modify the original filters list
8560 nsTArray<nsFilePickerFilter> filtersCopy;
8561 filtersCopy = filters;
8562 for (uint32_t i = 0; i < filtersCopy.Length(); ++i) {
8563 const nsFilePickerFilter& filterToCheck = filtersCopy[i];
8564 if (filterToCheck.mFilterMask) {
8565 continue;
8566 }
8567 for (uint32_t j = 0; j < filtersCopy.Length(); ++j) {
8568 if (i == j) {
8569 continue;
8570 }
8571 // Check if this filter's extension list is a substring of the other one.
8572 // e.g. if filters are "*.jpeg" and "*.jpeg; *.jpg" the first one should
8573 // be removed.
8574 // Add an extra "; " to be sure the check will work and avoid cases like
8575 // "*.xls" being a subtring of "*.xslx" while those are two differents
8576 // filters and none should be removed.
8577 if (FindInReadable(filterToCheck.mFilter + NS_LITERAL_STRING(";"),
8578 filtersCopy[j].mFilter + NS_LITERAL_STRING(";"))) {
8579 // We already have a similar, less restrictive filter (i.e.
8580 // filterToCheck extensionList is just a subset of another filter
8581 // extension list): remove this one
8582 filters.RemoveElement(filterToCheck);
8583 }
8584 }
8585 }
8586
8587 // Add "All Supported Types" filter
8588 if (filters.Length() > 1) {
8589 nsXPIDLString title;
8590 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
8591 "AllSupportedTypes", title);
8592 filePicker->AppendFilter(title, allExtensionsList);
8593 }
8594
8595 // Add each filter
8596 for (uint32_t i = 0; i < filters.Length(); ++i) {
8597 const nsFilePickerFilter& filter = filters[i];
8598 if (filter.mFilterMask) {
8599 filePicker->AppendFilters(filter.mFilterMask);
8600 } else {
8601 filePicker->AppendFilter(filter.mTitle, filter.mFilter);
8602 }
8603 }
8604
8605 if (filters.Length() >= 1 &&
8606 (allMimeTypeFiltersAreValid || atLeastOneFileExtensionFilter)) {
8607 // |filterAll| will always use index=0 so we need to set index=1 as the
8608 // current filter.
8609 filePicker->SetFilterIndex(1);
8610 }
8611 }
8612
8613 Decimal
GetStepScaleFactor() const8614 HTMLInputElement::GetStepScaleFactor() const
8615 {
8616 MOZ_ASSERT(DoesStepApply());
8617
8618 switch (mType) {
8619 case NS_FORM_INPUT_DATE:
8620 return kStepScaleFactorDate;
8621 case NS_FORM_INPUT_NUMBER:
8622 case NS_FORM_INPUT_RANGE:
8623 return kStepScaleFactorNumberRange;
8624 case NS_FORM_INPUT_TIME:
8625 return kStepScaleFactorTime;
8626 case NS_FORM_INPUT_MONTH:
8627 return kStepScaleFactorMonth;
8628 case NS_FORM_INPUT_WEEK:
8629 return kStepScaleFactorWeek;
8630 default:
8631 MOZ_ASSERT(false, "Unrecognized input type");
8632 return Decimal::nan();
8633 }
8634 }
8635
8636 Decimal
GetDefaultStep() const8637 HTMLInputElement::GetDefaultStep() const
8638 {
8639 MOZ_ASSERT(DoesStepApply());
8640
8641 switch (mType) {
8642 case NS_FORM_INPUT_DATE:
8643 case NS_FORM_INPUT_MONTH:
8644 case NS_FORM_INPUT_WEEK:
8645 case NS_FORM_INPUT_NUMBER:
8646 case NS_FORM_INPUT_RANGE:
8647 return kDefaultStep;
8648 case NS_FORM_INPUT_TIME:
8649 return kDefaultStepTime;
8650 default:
8651 MOZ_ASSERT(false, "Unrecognized input type");
8652 return Decimal::nan();
8653 }
8654 }
8655
8656 void
UpdateValidityUIBits(bool aIsFocused)8657 HTMLInputElement::UpdateValidityUIBits(bool aIsFocused)
8658 {
8659 if (aIsFocused) {
8660 // If the invalid UI is shown, we should show it while focusing (and
8661 // update). Otherwise, we should not.
8662 mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
8663
8664 // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
8665 // UI while typing.
8666 mCanShowValidUI = ShouldShowValidityUI();
8667 } else {
8668 mCanShowInvalidUI = true;
8669 mCanShowValidUI = true;
8670 }
8671 }
8672
8673 void
UpdateHasRange()8674 HTMLInputElement::UpdateHasRange()
8675 {
8676 /*
8677 * There is a range if min/max applies for the type and if the element
8678 * currently have a valid min or max.
8679 */
8680
8681 mHasRange = false;
8682
8683 // TODO: this is temporary until bug 888331 is fixed.
8684 if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
8685 return;
8686 }
8687
8688 Decimal minimum = GetMinimum();
8689 if (!minimum.isNaN()) {
8690 mHasRange = true;
8691 return;
8692 }
8693
8694 Decimal maximum = GetMaximum();
8695 if (!maximum.isNaN()) {
8696 mHasRange = true;
8697 return;
8698 }
8699 }
8700
8701 void
PickerClosed()8702 HTMLInputElement::PickerClosed()
8703 {
8704 mPickerRunning = false;
8705 }
8706
8707 JSObject*
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)8708 HTMLInputElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
8709 {
8710 return HTMLInputElementBinding::Wrap(aCx, this, aGivenProto);
8711 }
8712
8713 void
ClearGetFilesHelpers()8714 HTMLInputElement::ClearGetFilesHelpers()
8715 {
8716 if (mGetFilesRecursiveHelper) {
8717 mGetFilesRecursiveHelper->Unlink();
8718 mGetFilesRecursiveHelper = nullptr;
8719 }
8720
8721 if (mGetFilesNonRecursiveHelper) {
8722 mGetFilesNonRecursiveHelper->Unlink();
8723 mGetFilesNonRecursiveHelper = nullptr;
8724 }
8725 }
8726
8727 GetFilesHelper*
GetOrCreateGetFilesHelper(bool aRecursiveFlag,ErrorResult & aRv)8728 HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag,
8729 ErrorResult& aRv)
8730 {
8731 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
8732 MOZ_ASSERT(global);
8733 if (!global) {
8734 aRv.Throw(NS_ERROR_FAILURE);
8735 return nullptr;
8736 }
8737
8738 if (aRecursiveFlag) {
8739 if (!mGetFilesRecursiveHelper) {
8740 mGetFilesRecursiveHelper =
8741 GetFilesHelper::Create(global,
8742 GetFilesOrDirectoriesInternal(),
8743 aRecursiveFlag, aRv);
8744 if (NS_WARN_IF(aRv.Failed())) {
8745 return nullptr;
8746 }
8747 }
8748
8749 return mGetFilesRecursiveHelper;
8750 }
8751
8752 if (!mGetFilesNonRecursiveHelper) {
8753 mGetFilesNonRecursiveHelper =
8754 GetFilesHelper::Create(global,
8755 GetFilesOrDirectoriesInternal(),
8756 aRecursiveFlag, aRv);
8757 if (NS_WARN_IF(aRv.Failed())) {
8758 return nullptr;
8759 }
8760 }
8761
8762 return mGetFilesNonRecursiveHelper;
8763 }
8764
8765 void
UpdateEntries(const nsTArray<OwningFileOrDirectory> & aFilesOrDirectories)8766 HTMLInputElement::UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
8767 {
8768 MOZ_ASSERT(mEntries.IsEmpty());
8769
8770 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
8771 MOZ_ASSERT(global);
8772
8773 RefPtr<FileSystem> fs = FileSystem::Create(global);
8774 if (NS_WARN_IF(!fs)) {
8775 return;
8776 }
8777
8778 Sequence<RefPtr<FileSystemEntry>> entries;
8779 for (uint32_t i = 0; i < aFilesOrDirectories.Length(); ++i) {
8780 RefPtr<FileSystemEntry> entry =
8781 FileSystemEntry::Create(global, aFilesOrDirectories[i], fs);
8782 MOZ_ASSERT(entry);
8783
8784 if (!entries.AppendElement(entry, fallible)) {
8785 return;
8786 }
8787 }
8788
8789 // The root fileSystem is a DirectoryEntry object that contains only the
8790 // dropped fileEntry and directoryEntry objects.
8791 fs->CreateRoot(entries);
8792
8793 mEntries.SwapElements(entries);
8794 }
8795
8796 void
GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>> & aSequence)8797 HTMLInputElement::GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>>& aSequence)
8798 {
8799 Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED, true);
8800 aSequence.AppendElements(mEntries);
8801 }
8802
8803 } // namespace dom
8804 } // namespace mozilla
8805
8806 #undef NS_ORIGINAL_CHECKED_VALUE
8807