Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/Directory.h"
#include "mozilla/dom/DocumentOrShadowRoot.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/FileSystemUtils.h"
#include "mozilla/dom/GetFilesHelper.h"
#include "mozilla/dom/HTMLFormSubmission.h"
#include "mozilla/dom/InputType.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/MutationEventBinding.h"
#include "mozilla/dom/WheelEventBinding.h"
#include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/TextUtils.h"
#include "nsAttrValueInlines.h"
#include "nsCRTGlue.h"
#include "nsQueryObject.h"
#include "nsIRadioVisitor.h"
#include "HTMLFormSubmissionConstants.h"
#include "mozilla/Telemetry.h"
#include "nsBaseCommandController.h"
#include "nsIStringBundle.h"
#include "nsFocusManager.h"
#include "nsColorControlFrame.h"
#include "nsNumberControlFrame.h"
#include "nsSearchControlFrame.h"
#include "nsPIDOMWindow.h"
#include "nsRepeatService.h"
#include "nsContentCID.h"
#include "mozilla/dom/ProgressEvent.h"
#include "nsGkAtoms.h"
#include "nsStyleConsts.h"
#include "nsPresContext.h"
#include "nsMappedAttributes.h"
#include "nsIFormControl.h"
#include "mozilla/dom/Document.h"
#include "nsIFormControlFrame.h"
#include "nsITextControlFrame.h"
#include "nsIFrame.h"
#include "nsRangeFrame.h"
#include "nsError.h"
#include "nsIEditor.h"
#include "nsAttrValueOrString.h"
#include "mozilla/PresState.h"
#include "nsLinebreakConverter.h" //to strip out carriage returns
#include "nsReadableUtils.h"
#include "nsUnicharUtils.h"
#include "nsLayoutUtils.h"
#include "nsVariant.h"
#include "mozilla/ContentEvents.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStates.h"
#include "mozilla/MappedDeclarations.h"
#include "mozilla/InternalMutationEvent.h"
#include "mozilla/TextControlState.h"
#include "mozilla/TextEditor.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TouchEvents.h"
#include <algorithm>
// input type=radio
#include "nsIRadioGroupContainer.h"
// input type=file
#include "mozilla/dom/FileSystemEntry.h"
#include "mozilla/dom/FileSystem.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FileList.h"
#include "nsIFile.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIContentPrefService2.h"
#include "nsIMIMEService.h"
#include "nsIObserverService.h"
#include "nsGlobalWindow.h"
// input type=image
#include "nsImageLoadingContent.h"
#include "imgRequestProxy.h"
#include "mozAutoDocUpdate.h"
#include "nsContentCreatorFunctions.h"
#include "nsContentUtils.h"
#include "mozilla/dom/DirectionalityUtils.h"
#include "nsRadioVisitor.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Preferences.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/TextUtils.h"
#include <limits>
#include "nsIColorPicker.h"
#include "nsIStringEnumerator.h"
#include "HTMLSplitOnSpacesTokenizer.h"
#include "nsIMIMEInfo.h"
#include "nsFrameSelection.h"
#include "nsBaseCommandController.h"
#include "nsXULControllers.h"
// input type=date
#include "js/Date.h"
NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
// XXX align=left, hspace, vspace, border? other nav4 attrs
namespace mozilla {
namespace dom {
// First bits are needed for the control type.
#define NS_OUTER_ACTIVATE_EVENT (1 << 9)
#define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
// (1 << 11 is unused)
#define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
#define NS_PRE_HANDLE_BLUR_EVENT (1 << 13)
#define NS_IN_SUBMIT_CLICK (1 << 15)
#define NS_CONTROL_TYPE(bits) \
((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | \
NS_ORIGINAL_INDETERMINATE_VALUE | NS_PRE_HANDLE_BLUR_EVENT | \
NS_IN_SUBMIT_CLICK))
// whether textfields should be selected once focused:
// -1: no, 1: yes, 0: uninitialized
static int32_t gSelectTextFieldOnFocus;
UploadLastDir* HTMLInputElement::gUploadLastDir;
static const nsAttrValue::EnumTable kInputTypeTable[] = {
{"button", NS_FORM_INPUT_BUTTON},
{"checkbox", NS_FORM_INPUT_CHECKBOX},
{"color", NS_FORM_INPUT_COLOR},
{"date", NS_FORM_INPUT_DATE},
{"datetime-local", NS_FORM_INPUT_DATETIME_LOCAL},
{"email", NS_FORM_INPUT_EMAIL},
{"file", NS_FORM_INPUT_FILE},
{"hidden", NS_FORM_INPUT_HIDDEN},
{"reset", NS_FORM_INPUT_RESET},
{"image", NS_FORM_INPUT_IMAGE},
{"month", NS_FORM_INPUT_MONTH},
{"number", NS_FORM_INPUT_NUMBER},
{"password", NS_FORM_INPUT_PASSWORD},
{"radio", NS_FORM_INPUT_RADIO},
{"range", NS_FORM_INPUT_RANGE},
{"search", NS_FORM_INPUT_SEARCH},
{"submit", NS_FORM_INPUT_SUBMIT},
{"tel", NS_FORM_INPUT_TEL},
{"time", NS_FORM_INPUT_TIME},
{"url", NS_FORM_INPUT_URL},
{"week", NS_FORM_INPUT_WEEK},
// "text" must be last for ParseAttribute to work right. If you add things
// before it, please update kInputDefaultType.
{"text", NS_FORM_INPUT_TEXT},
{nullptr, 0}};
// Default type is 'text'.
static const nsAttrValue::EnumTable* kInputDefaultType =
&kInputTypeTable[ArrayLength(kInputTypeTable) - 2];
static const nsAttrValue::EnumTable kCaptureTable[] = {
{"user", static_cast<int16_t>(nsIFilePicker::captureUser)},
{"environment", static_cast<int16_t>(nsIFilePicker::captureEnv)},
{"", static_cast<int16_t>(nsIFilePicker::captureDefault)},
{nullptr, static_cast<int16_t>(nsIFilePicker::captureNone)}};
static const nsAttrValue::EnumTable* kCaptureDefault = &kCaptureTable[2];
const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000);
const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
const Decimal HTMLInputElement::kStepScaleFactorMonth = Decimal(1);
const Decimal HTMLInputElement::kStepScaleFactorWeek = Decimal(7 * 86400000);
const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0);
const Decimal HTMLInputElement::kDefaultStepBaseWeek = Decimal(-259200000);
const Decimal HTMLInputElement::kDefaultStep = Decimal(1);
const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60);
const Decimal HTMLInputElement::kStepAny = Decimal(0);
const double HTMLInputElement::kMinimumYear = 1;
const double HTMLInputElement::kMaximumYear = 275760;
const double HTMLInputElement::kMaximumWeekInMaximumYear = 37;
const double HTMLInputElement::kMaximumDayInMaximumYear = 13;
const double HTMLInputElement::kMaximumMonthInMaximumYear = 9;
const double HTMLInputElement::kMaximumWeekInYear = 53;
const double HTMLInputElement::kMsPerDay = 24 * 60 * 60 * 1000;
#define NS_INPUT_ELEMENT_STATE_IID \
{ /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */ \
0xdc3b3d14, 0x23e2, 0x4479, { \
0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0 \
} \
}
// An helper class for the dispatching of the 'change' event.
// This class is used when the FilePicker finished its task (or when files and
// directories are set by some chrome/test only method).
// The task of this class is to postpone the dispatching of 'change' and 'input'
// events at the end of the exploration of the directories.
class DispatchChangeEventCallback final : public GetFilesCallback {
public:
explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement)
: mInputElement(aInputElement) {
MOZ_ASSERT(aInputElement);
}
virtual void Callback(
nsresult aStatus,
const FallibleTArray<RefPtr<BlobImpl>>& aBlobImpls) override {
nsTArray<OwningFileOrDirectory> array;
for (uint32_t i = 0; i < aBlobImpls.Length(); ++i) {
OwningFileOrDirectory* element = array.AppendElement();
RefPtr<File> file =
File::Create(mInputElement->GetOwnerGlobal(), aBlobImpls[i]);
if (NS_WARN_IF(!file)) {
break;
}
element->SetAsFile() = file;
}
mInputElement->SetFilesOrDirectories(array, true);
Unused << NS_WARN_IF(NS_FAILED(DispatchEvents()));
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY
nsresult DispatchEvents() {
RefPtr<HTMLInputElement> inputElement(mInputElement);
nsresult rv = nsContentUtils::DispatchInputEvent(inputElement);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch input event");
rv = nsContentUtils::DispatchTrustedEvent(
mInputElement->OwnerDoc(), static_cast<Element*>(mInputElement.get()),
u"change"_ns, CanBubble::eYes, Cancelable::eNo);
return rv;
}
private:
RefPtr<HTMLInputElement> mInputElement;
};
struct HTMLInputElement::FileData {
/**
* The value of the input if it is a file input. This is the list of files or
* directories DOM objects used when uploading a file. It is vital that this
* is kept separate from mValue so that it won't be possible to 'leak' the
* value from a text-input to a file-input. Additionally, the logic for this
* value is kept as simple as possible to avoid accidental errors where the
* wrong filename is used. Therefor the list of filenames is always owned by
* this member, never by the frame. Whenever the frame wants to change the
* filename it has to call SetFilesOrDirectories to update this member.
*/
nsTArray<OwningFileOrDirectory> mFilesOrDirectories;
RefPtr<GetFilesHelper> mGetFilesRecursiveHelper;
RefPtr<GetFilesHelper> mGetFilesNonRecursiveHelper;
/**
* Hack for bug 1086684: Stash the .value when we're a file picker.
*/
nsString mFirstFilePath;
RefPtr<FileList> mFileList;
Sequence<RefPtr<FileSystemEntry>> mEntries;
nsString mStaticDocFileList;
void ClearGetFilesHelpers() {
if (mGetFilesRecursiveHelper) {
mGetFilesRecursiveHelper->Unlink();
mGetFilesRecursiveHelper = nullptr;
}
if (mGetFilesNonRecursiveHelper) {
mGetFilesNonRecursiveHelper->Unlink();
mGetFilesNonRecursiveHelper = nullptr;
}
}
// Cycle Collection support.
void Traverse(nsCycleCollectionTraversalCallback& cb) {
FileData* tmp = this;
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries)
if (mGetFilesRecursiveHelper) {
mGetFilesRecursiveHelper->Traverse(cb);
}
if (mGetFilesNonRecursiveHelper) {
mGetFilesNonRecursiveHelper->Traverse(cb);
}
}
void Unlink() {
FileData* tmp = this;
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries)
ClearGetFilesHelpers();
}
};
HTMLInputElement::nsFilePickerShownCallback::nsFilePickerShownCallback(
HTMLInputElement* aInput, nsIFilePicker* aFilePicker)
: mFilePicker(aFilePicker), mInput(aInput) {}
NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback, nsIContentPrefCallback2)
NS_IMETHODIMP
UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason) {
nsCOMPtr<nsIFile> localFile;
nsAutoString prefStr;
if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || !mResult) {
Preferences::GetString("dom.input.fallbackUploadDir", prefStr);
}
if (prefStr.IsEmpty() && mResult) {
nsCOMPtr<nsIVariant> pref;
mResult->GetValue(getter_AddRefs(pref));
pref->GetAsAString(prefStr);
}
if (!prefStr.IsEmpty()) {
localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
if (localFile && NS_WARN_IF(NS_FAILED(localFile->InitWithPath(prefStr)))) {
localFile = nullptr;
}
}
if (localFile) {
mFilePicker->SetDisplayDirectory(localFile);
} else {
// If no custom directory was set through the pref, default to
// "desktop" directory for each platform.
mFilePicker->SetDisplaySpecialDirectory(
NS_LITERAL_STRING_FROM_CSTRING(NS_OS_DESKTOP_DIR));
}
mFilePicker->Open(mFpCallback);
return NS_OK;
}
NS_IMETHODIMP
UploadLastDir::ContentPrefCallback::HandleResult(nsIContentPref* pref) {
mResult = pref;
return NS_OK;
}
NS_IMETHODIMP
UploadLastDir::ContentPrefCallback::HandleError(nsresult error) {
// HandleCompletion is always called (even with HandleError was called),
// so we don't need to do anything special here.
return NS_OK;
}
namespace {
/**
* This may return nullptr if the DOM File's implementation of
* File::mozFullPathInternal does not successfully return a non-empty
* string that is a valid path. This can happen on Firefox OS, for example,
* where the file picker can create Blobs.
*/
static already_AddRefed<nsIFile> LastUsedDirectory(
const OwningFileOrDirectory& aData) {
if (aData.IsFile()) {
nsAutoString path;
ErrorResult error;
aData.GetAsFile()->GetMozFullPathInternal(path, error);
if (error.Failed() || path.IsEmpty()) {
error.SuppressException();
return nullptr;
}
nsCOMPtr<nsIFile> localFile;
nsresult rv = NS_NewLocalFile(path, true, getter_AddRefs(localFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
nsCOMPtr<nsIFile> parentFile;
rv = localFile->GetParent(getter_AddRefs(parentFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
return parentFile.forget();
}
MOZ_ASSERT(aData.IsDirectory());
nsCOMPtr<nsIFile> localFile = aData.GetAsDirectory()->GetInternalNsIFile();
MOZ_ASSERT(localFile);
return localFile.forget();
}
void GetDOMFileOrDirectoryName(const OwningFileOrDirectory& aData,
nsAString& aName) {
if (aData.IsFile()) {
aData.GetAsFile()->GetName(aName);
} else {
MOZ_ASSERT(aData.IsDirectory());
ErrorResult rv;
aData.GetAsDirectory()->GetName(aName, rv);
if (NS_WARN_IF(rv.Failed())) {
rv.SuppressException();
}
}
}
void GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData,
nsAString& aPath, ErrorResult& aRv) {
if (aData.IsFile()) {
aData.GetAsFile()->GetMozFullPathInternal(aPath, aRv);
} else {
MOZ_ASSERT(aData.IsDirectory());
aData.GetAsDirectory()->GetFullRealPath(aPath);
}
}
} // namespace
NS_IMETHODIMP
HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult) {
mInput->PickerClosed();
if (aResult == nsIFilePicker::returnCancel) {
return NS_OK;
}
int16_t mode;
mFilePicker->GetMode(&mode);
// Collect new selected filenames
nsTArray<OwningFileOrDirectory> newFilesOrDirectories;
if (mode == static_cast<int16_t>(nsIFilePicker::modeOpenMultiple)) {
nsCOMPtr<nsISimpleEnumerator> iter;
nsresult rv =
mFilePicker->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter));
NS_ENSURE_SUCCESS(rv, rv);
if (!iter) {
return NS_OK;
}
nsCOMPtr<nsISupports> tmp;
bool hasMore = true;
while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
iter->GetNext(getter_AddRefs(tmp));
RefPtr<Blob> domBlob = do_QueryObject(tmp);
MOZ_ASSERT(domBlob,
"Null file object from FilePicker's file enumerator?");
if (!domBlob) {
continue;
}
OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
element->SetAsFile() = domBlob->ToFile();
}
} else {
MOZ_ASSERT(mode == static_cast<int16_t>(nsIFilePicker::modeOpen) ||
mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder));
nsCOMPtr<nsISupports> tmp;
nsresult rv = mFilePicker->GetDomFileOrDirectory(getter_AddRefs(tmp));
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<Blob> blob = do_QueryObject(tmp);
if (blob) {
RefPtr<File> file = blob->ToFile();
MOZ_ASSERT(file);
OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
element->SetAsFile() = file;
} else if (tmp) {
RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
element->SetAsDirectory() = directory;
}
}
if (newFilesOrDirectories.IsEmpty()) {
return NS_OK;
}
// Store the last used directory using the content pref service:
nsCOMPtr<nsIFile> lastUsedDir = LastUsedDirectory(newFilesOrDirectories[0]);
if (lastUsedDir) {
HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(mInput->OwnerDoc(),
lastUsedDir);
}
// The text control frame (if there is one) isn't going to send a change
// event because it will think this is done by a script.
// So, we can safely send one by ourself.
mInput->SetFilesOrDirectories(newFilesOrDirectories, true);
RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
new DispatchChangeEventCallback(mInput);
if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
mInput->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
ErrorResult error;
GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
if (NS_WARN_IF(error.Failed())) {
return error.StealNSResult();
}
helper->AddCallback(dispatchChangeEventCallback);
return NS_OK;
}
return dispatchChangeEventCallback->DispatchEvents();
}
NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback,
nsIFilePickerShownCallback)
class nsColorPickerShownCallback final : public nsIColorPickerShownCallback {
~nsColorPickerShownCallback() = default;
public:
nsColorPickerShownCallback(HTMLInputElement* aInput,
nsIColorPicker* aColorPicker)
: mInput(aInput), mColorPicker(aColorPicker), mValueChanged(false) {}
NS_DECL_ISUPPORTS
MOZ_CAN_RUN_SCRIPT_BOUNDARY
NS_IMETHOD Update(const nsAString& aColor) override;
MOZ_CAN_RUN_SCRIPT_BOUNDARY
NS_IMETHOD Done(const nsAString& aColor) override;
private:
/**
* Updates the internals of the object using aColor as the new value.
* If aTrustedUpdate is true, it will consider that aColor is a new value.
* Otherwise, it will check that aColor is different from the current value.
*/
MOZ_CAN_RUN_SCRIPT
nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
RefPtr<HTMLInputElement> mInput;
nsCOMPtr<nsIColorPicker> mColorPicker;
bool mValueChanged;
};
nsresult nsColorPickerShownCallback::UpdateInternal(const nsAString& aColor,
bool aTrustedUpdate) {
bool valueChanged = false;
nsAutoString oldValue;
if (aTrustedUpdate) {
valueChanged = true;
} else {
mInput->GetValue(oldValue, CallerType::System);
}
mInput->SetValue(aColor, CallerType::System, IgnoreErrors());
if (!aTrustedUpdate) {
nsAutoString newValue;
mInput->GetValue(newValue, CallerType::System);
if (!oldValue.Equals(newValue)) {
valueChanged = true;
}
}
if (!valueChanged) {
return NS_OK;
}
mValueChanged = true;
RefPtr<HTMLInputElement> input(mInput);
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(input);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Failed to dispatch input event");
return NS_OK;
}
NS_IMETHODIMP
nsColorPickerShownCallback::Update(const nsAString& aColor) {
return UpdateInternal(aColor, true);
}
NS_IMETHODIMP
nsColorPickerShownCallback::Done(const nsAString& aColor) {
/**
* When Done() is called, we might be at the end of a serie of Update() calls
* in which case mValueChanged is set to true and a change event will have to
* be fired but we might also be in a one shot Done() call situation in which
* case we should fire a change event iif the value actually changed.
* UpdateInternal(bool) is taking care of that logic for us.
*/
nsresult rv = NS_OK;
mInput->PickerClosed();
if (!aColor.IsEmpty()) {
UpdateInternal(aColor, false);
}
if (mValueChanged) {
rv = nsContentUtils::DispatchTrustedEvent(
mInput->OwnerDoc(), static_cast<Element*>(mInput.get()), u"change"_ns,
CanBubble::eYes, Cancelable::eNo);
}
return rv;
}
NS_IMPL_ISUPPORTS(nsColorPickerShownCallback, nsIColorPickerShownCallback)
bool HTMLInputElement::IsPopupBlocked() const {
nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow();
MOZ_ASSERT(win, "window should not be null");
if (!win) {
return true;
}
// Check if page can open a popup without abuse regardless of allowed events
if (PopupBlocker::GetPopupControlState() <= PopupBlocker::openBlocked) {
return !PopupBlocker::TryUsePopupOpeningToken(OwnerDoc()->NodePrincipal());
}
return !PopupBlocker::CanShowPopupByPermission(OwnerDoc()->NodePrincipal());
}
nsresult HTMLInputElement::InitColorPicker() {
if (mPickerRunning) {
NS_WARNING("Just one nsIColorPicker is allowed");
return NS_ERROR_FAILURE;
}
nsCOMPtr<Document> doc = OwnerDoc();
nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
if (!win) {
return NS_ERROR_FAILURE;
}
if (IsPopupBlocked()) {
return NS_OK;
}
// Get Loc title
nsAutoString title;
nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
"ColorPicker", title);
nsCOMPtr<nsIColorPicker> colorPicker =
do_CreateInstance("@mozilla.org/colorpicker;1");
if (!colorPicker) {
return NS_ERROR_FAILURE;
}
nsAutoString initialValue;
GetNonFileValueInternal(initialValue);
nsresult rv = colorPicker->Init(win, title, initialValue);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIColorPickerShownCallback> callback =
new nsColorPickerShownCallback(this, colorPicker);
rv = colorPicker->Open(callback);
if (NS_SUCCEEDED(rv)) {
mPickerRunning = true;
}
return rv;
}
nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
if (mPickerRunning) {
NS_WARNING("Just one nsIFilePicker is allowed");
return NS_ERROR_FAILURE;
}
// Get parent nsPIDOMWindow object.
nsCOMPtr<Document> doc = OwnerDoc();
nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
if (!win) {
return NS_ERROR_FAILURE;
}
if (IsPopupBlocked()) {
return NS_OK;
}
// Get Loc title
nsAutoString title;
nsAutoString okButtonLabel;
if (aType == FILE_PICKER_DIRECTORY) {
nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
"DirectoryUpload", OwnerDoc(),
title);
nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
"DirectoryPickerOkButtonLabel",
OwnerDoc(), okButtonLabel);
} else {
nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
"FileUpload", OwnerDoc(), title);
}
nsCOMPtr<nsIFilePicker> filePicker =
do_CreateInstance("@mozilla.org/filepicker;1");
if (!filePicker) return NS_ERROR_FAILURE;
int16_t mode;
if (aType == FILE_PICKER_DIRECTORY) {
mode = static_cast<int16_t>(nsIFilePicker::modeGetFolder);
} else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
mode = static_cast<int16_t>(nsIFilePicker::modeOpenMultiple);
} else {
mode = static_cast<int16_t>(nsIFilePicker::modeOpen);
}
nsresult rv = filePicker->Init(win, title, mode);
NS_ENSURE_SUCCESS(rv, rv);
if (!okButtonLabel.IsEmpty()) {
filePicker->SetOkButtonLabel(okButtonLabel);
}
// Native directory pickers ignore file type filters, so we don't spend
// cycles adding them for FILE_PICKER_DIRECTORY.
if (HasAttr(kNameSpaceID_None, nsGkAtoms::accept) &&
aType != FILE_PICKER_DIRECTORY) {
SetFilePickerFiltersFromAccept(filePicker);
if (StaticPrefs::dom_capture_enabled()) {
const nsAttrValue* captureVal =
GetParsedAttr(nsGkAtoms::capture, kNameSpaceID_None);
if (captureVal) {
filePicker->SetCapture(captureVal->GetEnumValue());
}
}
} else {
filePicker->AppendFilters(nsIFilePicker::filterAll);
}
// Set default directory and filename
nsAutoString defaultName;
const nsTArray<OwningFileOrDirectory>& oldFiles =
GetFilesOrDirectoriesInternal();
nsCOMPtr<nsIFilePickerShownCallback> callback =
new HTMLInputElement::nsFilePickerShownCallback(this, filePicker);
if (!oldFiles.IsEmpty() && aType != FILE_PICKER_DIRECTORY) {
nsAutoString path;
nsCOMPtr<nsIFile> parentFile = LastUsedDirectory(oldFiles[0]);
if (parentFile) {
filePicker->SetDisplayDirectory(parentFile);
}
// Unfortunately nsIFilePicker doesn't allow multiple files to be
// default-selected, so only select something by default if exactly
// one file was selected before.
if (oldFiles.Length() == 1) {
nsAutoString leafName;
GetDOMFileOrDirectoryName(oldFiles[0], leafName);
if (!leafName.IsEmpty()) {
filePicker->SetDefaultString(leafName);
}
}
rv = filePicker->Open(callback);
if (NS_SUCCEEDED(rv)) {
mPickerRunning = true;
}
return rv;
}
HTMLInputElement::gUploadLastDir->FetchDirectoryAndDisplayPicker(
doc, filePicker, callback);
mPickerRunning = true;
return NS_OK;
}
#define CPS_PREF_NAME u"browser.upload.lastDir"_ns
NS_IMPL_ISUPPORTS(UploadLastDir, nsIObserver, nsISupportsWeakReference)
void HTMLInputElement::InitUploadLastDir() {
gUploadLastDir = new UploadLastDir();
NS_ADDREF(gUploadLastDir);
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService && gUploadLastDir) {
observerService->AddObserver(gUploadLastDir,
"browser:purge-session-history", true);
}
}
void HTMLInputElement::DestroyUploadLastDir() { NS_IF_RELEASE(gUploadLastDir); }
nsresult UploadLastDir::FetchDirectoryAndDisplayPicker(
Document* aDoc, nsIFilePicker* aFilePicker,
nsIFilePickerShownCallback* aFpCallback) {
MOZ_ASSERT(aDoc, "aDoc is null");
MOZ_ASSERT(aFilePicker, "aFilePicker is null");
MOZ_ASSERT(aFpCallback, "aFpCallback is null");
nsIURI* docURI = aDoc->GetDocumentURI();
MOZ_ASSERT(docURI, "docURI is null");
nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
nsCOMPtr<nsIContentPrefCallback2> prefCallback =
new UploadLastDir::ContentPrefCallback(aFilePicker, aFpCallback);
// Attempt to get the CPS, if it's not present we'll fallback to use the
// Desktop folder
nsCOMPtr<nsIContentPrefService2> contentPrefService =
do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
if (!contentPrefService) {
prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
return NS_OK;
}
nsAutoCString cstrSpec;
docURI->GetSpec(cstrSpec);
NS_ConvertUTF8toUTF16 spec(cstrSpec);
contentPrefService->GetByDomainAndName(spec, CPS_PREF_NAME, loadContext,
prefCallback);
return NS_OK;
}
nsresult UploadLastDir::StoreLastUsedDirectory(Document* aDoc, nsIFile* aDir) {
MOZ_ASSERT(aDoc, "aDoc is null");
if (!aDir) {
return NS_OK;
}
nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
MOZ_ASSERT(docURI, "docURI is null");
// Attempt to get the CPS, if it's not present we'll just return
nsCOMPtr<nsIContentPrefService2> contentPrefService =
do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
if (!contentPrefService) return NS_ERROR_NOT_AVAILABLE;
nsAutoCString cstrSpec;
docURI->GetSpec(cstrSpec);
NS_ConvertUTF8toUTF16 spec(cstrSpec);
// Find the parent of aFile, and store it
nsString unicodePath;
aDir->GetPath(unicodePath);
if (unicodePath.IsEmpty()) // nothing to do
return NS_OK;
RefPtr<nsVariantCC> prefValue = new nsVariantCC();
prefValue->SetAsAString(unicodePath);
// Use the document's current load context to ensure that the content pref
// service doesn't persistently store this directory for this domain if the
// user is using private browsing:
nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
return contentPrefService->Set(spec, CPS_PREF_NAME, prefValue, loadContext,
nullptr);
}
NS_IMETHODIMP
UploadLastDir::Observe(nsISupports* aSubject, char const* aTopic,
char16_t const* aData) {
if (strcmp(aTopic, "browser:purge-session-history") == 0) {
nsCOMPtr<nsIContentPrefService2> contentPrefService =
do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
if (contentPrefService)
contentPrefService->RemoveByName(CPS_PREF_NAME, nullptr, nullptr);
}
return NS_OK;
}
#ifdef ACCESSIBILITY
// Helper method
static nsresult FireEventForAccessibility(HTMLInputElement* aTarget,
EventMessage aEventMessage);
#endif
//
// construction, destruction
//
HTMLInputElement::HTMLInputElement(
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
FromParser aFromParser, FromClone aFromClone)
: TextControlElement(std::move(aNodeInfo), aFromParser,
kInputDefaultType->value),
mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown),
mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown),
mDisabledChanged(false),
mValueChanged(false),
mLastValueChangeWasInteractive(false),
mCheckedChanged(false),
mChecked(false),
mHandlingSelectEvent(false),
mShouldInitChecked(false),
mDoneCreating(aFromParser == NOT_FROM_PARSER &&
aFromClone == FromClone::no),
mInInternalActivate(false),
mCheckedIsToggled(false),
mIndeterminate(false),
mInhibitRestoration(aFromParser & FROM_PARSER_FRAGMENT),
mCanShowValidUI(true),
mCanShowInvalidUI(true),
mHasRange(false),
mIsDraggingRange(false),
mNumberControlSpinnerIsSpinning(false),
mNumberControlSpinnerSpinsUp(false),
mPickerRunning(false),
mIsPreviewEnabled(false),
mHasBeenTypePassword(false),
mHasPatternAttribute(false) {
// If size is above 512, mozjemalloc allocates 1kB, see
// memory/build/mozjemalloc.cpp
static_assert(sizeof(HTMLInputElement) <= 512,
"Keep the size of HTMLInputElement under 512 to avoid "
"performance regression!");
// We are in a type=text so we now we currenty need a TextControlState.
mInputData.mState = TextControlState::Construct(this);
void* memory = mInputTypeMem;
mInputType = InputType::Create(this, mType, memory);
if (!gUploadLastDir) HTMLInputElement::InitUploadLastDir();
// Set up our default state. By default we're enabled (since we're
// a control type that can be disabled but not actually disabled
// right now), optional, and valid. We are NOT readwrite by default
// until someone calls UpdateEditableState on us, apparently! Also
// by default we don't have to show validity UI and so forth.
AddStatesSilently(NS_EVENT_STATE_ENABLED | NS_EVENT_STATE_OPTIONAL |
NS_EVENT_STATE_VALID);
UpdateApzAwareFlag();
}
HTMLInputElement::~HTMLInputElement() {
if (mNumberControlSpinnerIsSpinning) {
StopNumberControlSpinnerSpin(eDisallowDispatchingEvents);
}
nsImageLoadingContent::Destroy();
FreeData();
}
void HTMLInputElement::FreeData() {
if (!IsSingleLineTextControl(false)) {
free(mInputData.mValue);
mInputData.mValue = nullptr;
} else {
UnbindFromFrame(nullptr);
mInputData.mState->Destroy();
mInputData.mState = nullptr;
}
if (mInputType) {
mInputType->DropReference();
mInputType = nullptr;
}
}
TextControlState* HTMLInputElement::GetEditorState() const {
if (!IsSingleLineTextControl(false)) {
return nullptr;
}
MOZ_ASSERT(mInputData.mState,
"Single line text controls need to have a state"
" associated with them");
return mInputData.mState;
}
// nsISupports
NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
TextControlElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
if (tmp->IsSingleLineTextControl(false)) {
tmp->mInputData.mState->Traverse(cb);
}
if (tmp->mFileData) {
tmp->mFileData->Traverse(cb);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
TextControlElement)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
if (tmp->IsSingleLineTextControl(false)) {
tmp->mInputData.mState->Unlink();
}
if (tmp->mFileData) {
tmp->mFileData->Unlink();
}
// XXX should unlink more?
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLInputElement,
TextControlElement,
imgINotificationObserver,
nsIImageLoadingContent,
nsIConstraintValidation)
// nsINode
nsresult HTMLInputElement::Clone(dom::NodeInfo* aNodeInfo,
nsINode** aResult) const {
*aResult = nullptr;
RefPtr<HTMLInputElement> it = new (aNodeInfo->NodeInfoManager())
HTMLInputElement(do_AddRef(aNodeInfo), NOT_FROM_PARSER, FromClone::yes);
nsresult rv = const_cast<HTMLInputElement*>(this)->CopyInnerTo(it);
NS_ENSURE_SUCCESS(rv, rv);
switch (GetValueMode()) {
case VALUE_MODE_VALUE:
if (mValueChanged) {
// We don't have our default value anymore. Set our value on
// the clone.
nsAutoString value;
GetNonFileValueInternal(value);
// SetValueInternal handles setting the VALUE_CHANGED bit for us
rv = it->SetValueInternal(value, TextControlState::eSetValue_Notify);
NS_ENSURE_SUCCESS(rv, rv);
}
break;
case VALUE_MODE_FILENAME:
if (it->OwnerDoc()->IsStaticDocument()) {
// We're going to be used in print preview. Since the doc is static
// we can just grab the pretty string and use it as wallpaper
GetDisplayFileName(it->mFileData->mStaticDocFileList);
} else {
it->mFileData->ClearGetFilesHelpers();
it->mFileData->mFilesOrDirectories.Clear();
it->mFileData->mFilesOrDirectories.AppendElements(
mFileData->mFilesOrDirectories);
}
break;
case VALUE_MODE_DEFAULT_ON:
if (mCheckedChanged) {
// We no longer have our original checked state. Set our
// checked state on the clone.
it->DoSetChecked(mChecked, false, true);
// Then tell DoneCreatingElement() not to overwrite:
it->mShouldInitChecked = false;
}
break;
case VALUE_MODE_DEFAULT:
break;
}
it->DoneCreatingElement();
it->mLastValueChangeWasInteractive = mLastValueChangeWasInteractive;
it.forget(aResult);
return NS_OK;
}
nsresult HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
const nsAttrValueOrString* aValue,
bool aNotify) {
if (aNameSpaceID == kNameSpaceID_None) {
//
// When name or type changes, radio should be removed from radio group.
// (type changes are handled in the form itself currently)
// If we are not done creating the radio, we also should not do it.
//
if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
mType == NS_FORM_INPUT_RADIO && (mForm || mDoneCreating)) {
WillRemoveFromRadioGroup();
} else if (aNotify && aName == nsGkAtoms::disabled) {
mDisabledChanged = true;
} else if (mType == NS_FORM_INPUT_RADIO && aName == nsGkAtoms::required) {
nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
if (container && ((aValue && !HasAttr(aNameSpaceID, aName)) ||
(!aValue && HasAttr(aNameSpaceID, aName)))) {
nsAutoString name;
GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
container->RadioRequiredWillChange(name, !!aValue);
}
}
if (aName == nsGkAtoms::webkitdirectory) {
Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED, true);
}
}
return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
aValue, aNotify);
}
nsresult HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
const nsAttrValue* aValue,
const nsAttrValue* aOldValue,
nsIPrincipal* aSubjectPrincipal,
bool aNotify) {
if (aNameSpaceID == kNameSpaceID_None) {
//
// When name or type changes, radio should be added to radio group.
// (type changes are handled in the form itself currently)
// If we are not done creating the radio, we also should not do it.
//
if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
mType == NS_FORM_INPUT_RADIO && (mForm || mDoneCreating)) {
AddedToRadioGroup();
UpdateValueMissingValidityStateForRadio(false);
}
if (aName == nsGkAtoms::src) {
mSrcTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
this, aValue ? aValue->GetStringValue() : EmptyString(),
aSubjectPrincipal);
if (aNotify && mType == NS_FORM_INPUT_IMAGE) {
if (aValue) {
// Mark channel as urgent-start before load image if the image load is
// initiated by a user interaction.
mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
LoadImage(aValue->GetStringValue(), true, aNotify,
eImageLoadType_Normal, mSrcTriggeringPrincipal);
} else {
// Null value means the attr got unset; drop the image
CancelImageRequests(aNotify);
}
}
}
// If @value is changed and BF_VALUE_CHANGED is false, @value is the value
// of the element so, if the value of the element is different than @value,
// we have to re-set it. This is only the case when GetValueMode() returns
// VALUE_MODE_VALUE.
if (aName == nsGkAtoms::value) {
if (!mValueChanged && GetValueMode() == VALUE_MODE_VALUE) {
SetDefaultValueAsValue();
}
// GetStepBase() depends on the `value` attribute if `min` is not present,
// even if the value doesn't change.
UpdateStepMismatchValidityState();
}
//
// Checked must be set no matter what type of control it is, since
// mChecked must reflect the new value
if (aName == nsGkAtoms::checked && !mCheckedChanged) {
// Delay setting checked if we are creating this element (wait
// until everything is set)
if (!mDoneCreating) {
mShouldInitChecked = true;
} else {
DoSetChecked(DefaultChecked(), true, false);
}
}
if (aName == nsGkAtoms::type) {
uint8_t newType;
if (!aValue) {
// We're now a text input.
newType = kInputDefaultType->value;
} else {
newType = aValue->GetEnumValue();
}
if (newType != mType) {
HandleTypeChange(newType, aNotify);
}
}
if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
aName == nsGkAtoms::readonly) {
if (aName == nsGkAtoms::disabled) {
// This *has* to be called *before* validity state check because
// UpdateBarredFromConstraintValidation and
// UpdateValueMissingValidityState depend on our disabled state.
UpdateDisabledState(aNotify);
}
if (aName == nsGkAtoms::required && DoesRequiredApply()) {
// This *has* to be called *before* UpdateValueMissingValidityState
// because UpdateValueMissingValidityState depends on our required
// state.
UpdateRequiredState(!!aValue, aNotify);
}
UpdateValueMissingValidityState();
// This *has* to be called *after* validity has changed.
if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
UpdateBarredFromConstraintValidation();
}
} else if (aName == nsGkAtoms::maxlength) {
UpdateTooLongValidityState();
} else if (aName == nsGkAtoms::minlength) {
UpdateTooShortValidityState();
} else if (aName == nsGkAtoms::pattern) {
// Although pattern attribute only applies to single line text controls,
// we set this flag for all input types to save having to check the type
// here.
mHasPatternAttribute = !!aValue;
if (mDoneCreating) {
UpdatePatternMismatchValidityState();
}
} else if (aName == nsGkAtoms::multiple) {
UpdateTypeMismatchValidityState();
} else if (aName == nsGkAtoms::max) {
UpdateHasRange();
nsresult rv = mInputType->MinMaxStepAttrChanged();
NS_ENSURE_SUCCESS(rv, rv);
// Validity state must be updated *after* the UpdateValueDueToAttrChange
// call above or else the following assert will not be valid.
// We don't assert the state of underflow during creation since
// DoneCreatingElement sanitizes.
UpdateRangeOverflowValidityState();
MOZ_ASSERT(!mDoneCreating || mType != NS_FORM_INPUT_RANGE ||
!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
"HTML5 spec does not allow underflow for type=range");
} else if (aName == nsGkAtoms::min) {
UpdateHasRange();
nsresult rv = mInputType->MinMaxStepAttrChanged();
NS_ENSURE_SUCCESS(rv, rv);
// See corresponding @max comment
UpdateRangeUnderflowValidityState();
UpdateStepMismatchValidityState();
MOZ_ASSERT(!mDoneCreating || mType != NS_FORM_INPUT_RANGE ||
!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
"HTML5 spec does not allow underflow for type=range");
} else if (aName == nsGkAtoms::step) {
nsresult rv = mInputType->MinMaxStepAttrChanged();
NS_ENSURE_SUCCESS(rv, rv);
// See corresponding @max comment
UpdateStepMismatchValidityState();
MOZ_ASSERT(!mDoneCreating || mType != NS_FORM_INPUT_RANGE ||
!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
"HTML5 spec does not allow underflow for type=range");
} else if (aName == nsGkAtoms::dir && aValue &&
aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
SetDirectionFromValue(aNotify);
} else if (aName == nsGkAtoms::lang) {
// FIXME(emilio, bug 1651070): This doesn't account for lang changes on
// ancestors.
if (mType == NS_FORM_INPUT_NUMBER) {
// The validity of our value may have changed based on the locale.
UpdateValidityState();
}
} else if (aName == nsGkAtoms::autocomplete) {
// Clear the cached @autocomplete attribute and autocompleteInfo state.
mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown;
} else if (aName == nsGkAtoms::placeholder) {
// Full addition / removals of the attribute reconstruct right now.
if (nsTextControlFrame* f = do_QueryFrame(GetPrimaryFrame())) {
f->PlaceholderChanged(aOldValue, aValue);
}
}
if (CreatesDateTimeWidget()) {
if (aName == nsGkAtoms::value || aName == nsGkAtoms::readonly ||
aName == nsGkAtoms::tabindex || aName == nsGkAtoms::required ||
aName == nsGkAtoms::disabled) {
// If original target is this and not the inner text control, we should
// pass the focus to the inner text control.
if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
dateTimeBoxElement,
aName == nsGkAtoms::value ? u"MozDateTimeValueChanged"_ns
: u"MozDateTimeAttributeChanged"_ns,
CanBubble::eNo, ChromeOnlyDispatch::eNo);
dispatcher->RunDOMEventWhenSafe();
}
}
}
}
return nsGenericHTMLFormElementWithState::AfterSetAttr(
aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
}
void HTMLInputElement::BeforeSetForm(bool aBindToTree) {
// No need to remove from radio group if we are just binding to tree.
if (mType == NS_FORM_INPUT_RADIO && !aBindToTree) {
WillRemoveFromRadioGroup();
}
}
void HTMLInputElement::AfterClearForm(bool aUnbindOrDelete) {
MOZ_ASSERT(!mForm);
// Do not add back to radio group if we are releasing or unbinding from tree.
if (mType == NS_FORM_INPUT_RADIO && !aUnbindOrDelete) {
AddedToRadioGroup();
UpdateValueMissingValidityStateForRadio(false);
}
}
void HTMLInputElement::ResultForDialogSubmit(nsAString& aResult) {
if (mType == NS_FORM_INPUT_IMAGE) {
// Get a property set by the frame to find out where it was clicked.
nsIntPoint* lastClickedPoint =
static_cast<nsIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
int32_t x, y;
if (lastClickedPoint) {
x = lastClickedPoint->x;
y = lastClickedPoint->y;
} else {
x = y = 0;
}
aResult.AppendInt(x);
aResult.AppendLiteral(",");
aResult.AppendInt(y);
} else {
GetAttr(kNameSpaceID_None, nsGkAtoms::value, aResult);
}
}
void HTMLInputElement::GetAutocomplete(nsAString& aValue) {
if (!DoesAutocompleteApply()) {
return;
}
aValue.Truncate();
const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(
attributeVal, aValue, mAutocompleteAttrState);
}
void HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo) {
if (!DoesAutocompleteApply()) {
aInfo.SetNull();
return;
}
const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
mAutocompleteInfoState = nsContentUtils::SerializeAutocompleteAttribute(
attributeVal, aInfo.SetValue(), mAutocompleteInfoState, true);
}
void HTMLInputElement::GetCapture(nsAString& aValue) {
GetEnumAttr(nsGkAtoms::capture, kCaptureDefault->tag, aValue);
}
void HTMLInputElement::GetFormEnctype(nsAString& aValue) {
GetEnumAttr(nsGkAtoms::formenctype, "", kFormDefaultEnctype->tag, aValue);
}
void HTMLInputElement::GetFormMethod(nsAString& aValue) {
GetEnumAttr(nsGkAtoms::formmethod, "", kFormDefaultMethod->tag, aValue);
}
void HTMLInputElement::GetType(nsAString& aValue) {
GetEnumAttr(nsGkAtoms::type, kInputDefaultType->tag, aValue);
}
int32_t HTMLInputElement::TabIndexDefault() { return 0; }
uint32_t HTMLInputElement::Height() {
if (mType != NS_FORM_INPUT_IMAGE) {
return 0;
}
return GetWidthHeightForImage().height;
}
void HTMLInputElement::SetIndeterminateInternal(bool aValue,
bool aShouldInvalidate) {
mIndeterminate = aValue;
if (aShouldInvalidate) {
// Repaint the frame
nsIFrame* frame = GetPrimaryFrame();
if (frame) frame->InvalidateFrameSubtree();
}
UpdateState(true);
}
void HTMLInputElement::SetIndeterminate(bool aValue) {
SetIndeterminateInternal(aValue, true);
}
uint32_t HTMLInputElement::Width() {
if (mType != NS_FORM_INPUT_IMAGE) {
return 0;
}
return GetWidthHeightForImage().width;
}
bool HTMLInputElement::SanitizesOnValueGetter() const {
// Don't return non-sanitized value for datetime types or number.
return mType == NS_FORM_INPUT_NUMBER || IsDateTimeInputType(mType);
}
void HTMLInputElement::GetValue(nsAString& aValue, CallerType aCallerType) {
GetValueInternal(aValue, aCallerType);
if (SanitizesOnValueGetter()) {
SanitizeValue(aValue, ForValueGetter::Yes);
}
}
void HTMLInputElement::GetValueInternal(nsAString& aValue,
CallerType aCallerType) const {
if (mType != NS_FORM_INPUT_FILE) {
GetNonFileValueInternal(aValue);
return;
}
if (aCallerType == CallerType::System) {
aValue.Assign(mFileData->mFirstFilePath);
return;
}
if (mFileData->mFilesOrDirectories.IsEmpty()) {
aValue.Truncate();
return;
}
nsAutoString file;
GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], file);
if (file.IsEmpty()) {
aValue.Truncate();
return;
}
aValue.AssignLiteral("C:\\fakepath\\");
aValue.Append(file);
}
void HTMLInputElement::GetNonFileValueInternal(nsAString& aValue) const {
switch (GetValueMode()) {
case VALUE_MODE_VALUE:
if (IsSingleLineTextControl(false)) {
mInputData.mState->GetValue(aValue, true);
} else if (!aValue.Assign(mInputData.mValue, fallible)) {
aValue.Truncate();
}
return;
case VALUE_MODE_FILENAME:
MOZ_ASSERT_UNREACHABLE("Someone screwed up here");
// We'll just return empty string if someone does screw up.
aValue.Truncate();
return;
case VALUE_MODE_DEFAULT:
// Treat defaultValue as value.
GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue);
return;
case VALUE_MODE_DEFAULT_ON:
// Treat default value as value and returns "on" if no value.
if (!GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue)) {
aValue.AssignLiteral("on");
}
return;
}
}
bool HTMLInputElement::IsValueEmpty() const {
if (GetValueMode() == VALUE_MODE_VALUE && IsSingleLineTextControl(false)) {
return !mInputData.mState->HasNonEmptyValue();
}
nsAutoString value;
GetNonFileValueInternal(value);
return value.IsEmpty();
}
void HTMLInputElement::ClearFiles(bool aSetValueChanged) {
nsTArray<OwningFileOrDirectory> data;
SetFilesOrDirectories(data, aSetValueChanged);
}
int32_t HTMLInputElement::MonthsSinceJan1970(uint32_t aYear,
uint32_t aMonth) const {
return (aYear - 1970) * 12 + aMonth - 1;
}
/* static */
Decimal HTMLInputElement::StringToDecimal(const nsAString& aValue) {
if (!IsAscii(aValue)) {
return Decimal::nan();
}
NS_LossyConvertUTF16toASCII asciiString(aValue);
std::string stdString = asciiString.get();
return Decimal::fromString(stdString);
}
Decimal HTMLInputElement::GetValueAsDecimal() const {
Decimal decimalValue;
nsAutoString stringValue;
GetNonFileValueInternal(stringValue);
return !mInputType->ConvertStringToNumber(stringValue, decimalValue)
? Decimal::nan()
: decimalValue;
}
void HTMLInputElement::SetValue(const nsAString& aValue, CallerType aCallerType,
ErrorResult& aRv) {
// check security. Note that setting the value to the empty string is always
// OK and gives pages a way to clear a file input if necessary.
if (mType == NS_FORM_INPUT_FILE) {
if (!aValue.IsEmpty()) {
if (aCallerType != CallerType::System) {
// setting the value of a "FILE" input widget requires
// chrome privilege
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
Sequence<nsString> list;
if (!list.AppendElement(aValue, fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
MozSetFileNameArray(list, aRv);
return;
}
ClearFiles(true);
} else {
if (MayFireChangeOnBlur()) {
// If the value has been set by a script, we basically want to keep the
// current change event state. If the element is ready to fire a change
// event, we should keep it that way. Otherwise, we should make sure the
// element will not fire any event because of the script interaction.
//
// NOTE: this is currently quite expensive work (too much string
// manipulation). We should probably optimize that.
nsAutoString currentValue;
GetValue(currentValue, aCallerType);
// Some types sanitize value, so GetValue doesn't return pure
// previous value correctly.
//
// FIXME(emilio): Shouldn't above just use GetNonFileValueInternal() to
// get the unsanitized value?
nsresult rv = SetValueInternal(
aValue, SanitizesOnValueGetter() ? nullptr : &currentValue,
TextControlState::eSetValue_ByContent |
TextControlState::eSetValue_Notify |
TextControlState::eSetValue_MoveCursorToEndIfValueChanged);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return;
}
if (mFocusedValue.Equals(currentValue)) {
GetValue(mFocusedValue, aCallerType);
}
} else {
nsresult rv = SetValueInternal(
aValue,
TextControlState::eSetValue_ByContent |
TextControlState::eSetValue_Notify |
TextControlState::eSetValue_MoveCursorToEndIfValueChanged);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return;
}
}
}
}
nsGenericHTMLElement* HTMLInputElement::GetList() const {
nsAutoString dataListId;
GetAttr(kNameSpaceID_None, nsGkAtoms::list_, dataListId);
if (dataListId.IsEmpty()) {
return nullptr;
}
DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
if (!docOrShadow) {
return nullptr;
}
Element* element = docOrShadow->GetElementById(dataListId);
if (!element || !element->IsHTMLElement(nsGkAtoms::datalist)) {
return nullptr;
}
return static_cast<nsGenericHTMLElement*>(element);
}
void HTMLInputElement::SetValue(Decimal aValue, CallerType aCallerType) {
MOZ_ASSERT(!aValue.isInfinity(), "aValue must not be Infinity!");
if (aValue.isNaN()) {
SetValue(u""_ns, aCallerType, IgnoreErrors());
return;
}
nsAutoString value;
mInputType->ConvertNumberToString(aValue, value);
SetValue(value, aCallerType, IgnoreErrors());
}
void HTMLInputElement::GetValueAsDate(JSContext* aCx,
JS::MutableHandle<JSObject*> aObject,
ErrorResult& aRv) {
aObject.set(nullptr);
if (!IsDateTimeInputType(mType)) {
return;
}
Maybe<JS::ClippedTime> time;
switch (mType) {
case NS_FORM_INPUT_DATE: {
uint32_t year, month, day;
nsAutoString value;
GetNonFileValueInternal(value);
if (!ParseDate(value, &year, &month, &day)) {
return;
}
time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, day)));
break;
}
case NS_FORM_INPUT_TIME: {
uint32_t millisecond;
nsAutoString value;
GetNonFileValueInternal(value);
if (!ParseTime(value, &millisecond)) {
return;
}
time.emplace(JS::TimeClip(millisecond));
MOZ_ASSERT(time->toDouble() == millisecond,
"HTML times are restricted to the day after the epoch and "
"never clip");
break;
}
case NS_FORM_INPUT_MONTH: {
uint32_t year, month;
nsAutoString value;
GetNonFileValueInternal(value);
if (!ParseMonth(value, &year, &month)) {
return;
}
time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, 1)));
break;
}
case NS_FORM_INPUT_WEEK: {
uint32_t year, week;
nsAutoString value;
GetNonFileValueInternal(value);
if (!ParseWeek(value, &year, &week)) {
return;
}
double days = DaysSinceEpochFromWeek(year, week);
time.emplace(JS::TimeClip(days * kMsPerDay));
break;
}
case NS_FORM_INPUT_DATETIME_LOCAL: {
uint32_t year, month, day, timeInMs;
nsAutoString value;
GetNonFileValueInternal(value);
if (!ParseDateTimeLocal(value, &year, &month, &day, &timeInMs)) {
return;
}
time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, day, timeInMs)));
break;
}
}
if (time) {
aObject.set(JS::NewDateObject(aCx, *time));
if (!aObject) {
aRv.NoteJSContextException(aCx);
}
return;
}
MOZ_ASSERT(false, "Unrecognized input type");
aRv.Throw(NS_ERROR_UNEXPECTED);
}
void HTMLInputElement::SetValueAsDate(JSContext* aCx,
JS::Handle<JSObject*> aObj,
ErrorResult& aRv) {
if (!IsDateTimeInputType(mType)) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (aObj) {
bool isDate;
if (!JS::ObjectIsDate(aCx, aObj, &isDate)) {
aRv.NoteJSContextException(aCx);
return;
}
if (!isDate) {
aRv.ThrowTypeError("Value being assigned is not a date.");
return;
}
}
double milliseconds;
if (aObj) {
if (!js::DateGetMsecSinceEpoch(aCx, aObj, &milliseconds)) {
aRv.NoteJSContextException(aCx);
return;
}
} else {
milliseconds = UnspecifiedNaN<double>();
}
// At this point we know we're not a file input, so we can just pass "not
// system" as the caller type, since the caller type only matters in the file
// input case.
if (IsNaN(milliseconds)) {
SetValue(u""_ns, CallerType::NonSystem, aRv);
return;
}
if (mType != NS_FORM_INPUT_MONTH) {
SetValue(Decimal::fromDouble(milliseconds), CallerType::NonSystem);
return;
}
// type=month expects the value to be number of months.
double year = JS::YearFromTime(milliseconds);
double month = JS::MonthFromTime(milliseconds);
if (IsNaN(year) || IsNaN(month)) {
SetValue(u""_ns, CallerType::NonSystem, aRv);
return;
}
int32_t months = MonthsSinceJan1970(year, month + 1);
SetValue(Decimal(int32_t(months)), CallerType::NonSystem);
}
void HTMLInputElement::SetValueAsNumber(double aValueAsNumber,
ErrorResult& aRv) {
// TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
if (IsInfinite(aValueAsNumber)) {
aRv.Throw(NS_ERROR_INVALID_ARG);
return;
}
if (!DoesValueAsNumberApply()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
// At this point we know we're not a file input, so we can just pass "not
// system" as the caller type, since the caller type only matters in the file
// input case.
SetValue(Decimal::fromDouble(aValueAsNumber), CallerType::NonSystem);
}
Decimal HTMLInputElement::GetMinimum() const {
MOZ_ASSERT(
DoesValueAsNumberApply(),
"GetMinimum() should only be used for types that allow .valueAsNumber");
// Only type=range has a default minimum
Decimal defaultMinimum =
mType == NS_FORM_INPUT_RANGE ? Decimal(0) : Decimal::nan();
if (!HasAttr(kNameSpaceID_None, nsGkAtoms::min)) {
return defaultMinimum;
}
nsAutoString minStr;
GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
Decimal min;
return mInputType->ConvertStringToNumber(minStr, min) ? min : defaultMinimum;
}
Decimal HTMLInputElement::GetMaximum() const {
MOZ_ASSERT(
DoesValueAsNumberApply(),
"GetMaximum() should only be used for types that allow .valueAsNumber");
// Only type=range has a default maximum
Decimal defaultMaximum =
mType == NS_FORM_INPUT_RANGE ? Decimal(100) : Decimal::nan();
if (!HasAttr(kNameSpaceID_None, nsGkAtoms::max)) {
return defaultMaximum;
}
nsAutoString maxStr;
GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
Decimal max;
return mInputType->ConvertStringToNumber(maxStr, max) ? max : defaultMaximum;
}
Decimal HTMLInputElement::GetStepBase() const {
MOZ_ASSERT(IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_NUMBER ||
mType == NS_FORM_INPUT_RANGE,
"Check that kDefaultStepBase is correct for this new type");
Decimal stepBase;
// Do NOT use GetMinimum here - the spec says to use "the min content
// attribute", not "the minimum".
nsAutoString minStr;
if (GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr) &&
mInputType->ConvertStringToNumber(minStr, stepBase)) {
return stepBase;
}
// If @min is not a double, we should use @value.
nsAutoString