Mirror of roytam1's Pale Moon fork just in case Moonchild and Tobin decide to go after him
https://github.com/roytam1/palemoon27
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
7408 lines
222 KiB
7408 lines
222 KiB
/* -*- 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/DebugOnly.h" |
|
#include "mozilla/dom/Date.h" |
|
#include "mozilla/dom/Directory.h" |
|
#include "mozilla/dom/FileSystemUtils.h" |
|
#include "mozilla/dom/OSFileSystem.h" |
|
#include "nsAttrValueInlines.h" |
|
#include "nsCRTGlue.h" |
|
|
|
#include "nsIDOMHTMLInputElement.h" |
|
#include "nsITextControlElement.h" |
|
#include "nsIDOMNSEditableElement.h" |
|
#include "nsIRadioVisitor.h" |
|
#include "nsIPhonetic.h" |
|
|
|
#include "mozilla/Telemetry.h" |
|
#include "nsIControllers.h" |
|
#include "nsIStringBundle.h" |
|
#include "nsFocusManager.h" |
|
#include "nsColorControlFrame.h" |
|
#include "nsNumberControlFrame.h" |
|
#include "nsPIDOMWindow.h" |
|
#include "nsRepeatService.h" |
|
#include "nsContentCID.h" |
|
#include "nsIComponentManager.h" |
|
#include "nsIDOMHTMLFormElement.h" |
|
#include "mozilla/dom/ProgressEvent.h" |
|
#include "nsGkAtoms.h" |
|
#include "nsStyleConsts.h" |
|
#include "nsPresContext.h" |
|
#include "nsMappedAttributes.h" |
|
#include "nsIFormControl.h" |
|
#include "nsIForm.h" |
|
#include "nsFormSubmission.h" |
|
#include "nsFormSubmissionConstants.h" |
|
#include "nsIDocument.h" |
|
#include "nsIPresShell.h" |
|
#include "nsIFormControlFrame.h" |
|
#include "nsITextControlFrame.h" |
|
#include "nsIFrame.h" |
|
#include "nsRangeFrame.h" |
|
#include "nsIServiceManager.h" |
|
#include "nsError.h" |
|
#include "nsIEditor.h" |
|
#include "nsIIOService.h" |
|
#include "nsDocument.h" |
|
#include "nsAttrValueOrString.h" |
|
|
|
#include "nsPresState.h" |
|
#include "nsIDOMEvent.h" |
|
#include "nsIDOMNodeList.h" |
|
#include "nsIDOMHTMLCollection.h" |
|
#include "nsLinebreakConverter.h" //to strip out carriage returns |
|
#include "nsReadableUtils.h" |
|
#include "nsUnicharUtils.h" |
|
#include "nsLayoutUtils.h" |
|
|
|
#include "nsIDOMMutationEvent.h" |
|
#include "mozilla/ContentEvents.h" |
|
#include "mozilla/EventDispatcher.h" |
|
#include "mozilla/EventStates.h" |
|
#include "mozilla/InternalMutationEvent.h" |
|
#include "mozilla/TextEvents.h" |
|
#include "mozilla/TouchEvents.h" |
|
|
|
#include "nsRuleData.h" |
|
#include <algorithm> |
|
|
|
// input type=radio |
|
#include "nsIRadioGroupContainer.h" |
|
|
|
// input type=file |
|
#include "mozilla/dom/File.h" |
|
#include "mozilla/dom/FileList.h" |
|
#include "nsIFile.h" |
|
#include "nsNetCID.h" |
|
#include "nsNetUtil.h" |
|
#include "nsDirectoryServiceDefs.h" |
|
#include "nsIContentPrefService.h" |
|
#include "nsIMIMEService.h" |
|
#include "nsIObserverService.h" |
|
#include "nsIPopupWindowManager.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 "nsTextEditorState.h" |
|
|
|
#include "mozilla/LookAndFeel.h" |
|
#include "mozilla/Preferences.h" |
|
#include "mozilla/MathAlgorithms.h" |
|
|
|
#include "nsIIDNService.h" |
|
|
|
#include <limits> |
|
|
|
#include "nsIColorPicker.h" |
|
#include "nsIStringEnumerator.h" |
|
#include "HTMLSplitOnSpacesTokenizer.h" |
|
#include "nsIController.h" |
|
#include "nsIMIMEInfo.h" |
|
#include "nsFrameSelection.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 |
|
|
|
static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID); |
|
|
|
// This must come outside of any namespace, or else it won't overload with the |
|
// double based version in nsMathUtils.h |
|
inline mozilla::Decimal |
|
NS_floorModulo(mozilla::Decimal x, mozilla::Decimal y) |
|
{ |
|
return (x - y * (x / y).floor()); |
|
} |
|
|
|
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) |
|
#define NS_NO_CONTENT_DISPATCH (1 << 11) |
|
#define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12) |
|
#define NS_CONTROL_TYPE(bits) ((bits) & ~( \ |
|
NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | NS_NO_CONTENT_DISPATCH | \ |
|
NS_ORIGINAL_INDETERMINATE_VALUE)) |
|
|
|
// 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 }, |
|
{ "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 }, |
|
{ "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 }, |
|
{ "text", NS_FORM_INPUT_TEXT }, |
|
{ "time", NS_FORM_INPUT_TIME }, |
|
{ "url", NS_FORM_INPUT_URL }, |
|
{ 0 } |
|
}; |
|
|
|
// Default type is 'text'. |
|
static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[16]; |
|
|
|
static const uint8_t NS_INPUT_INPUTMODE_AUTO = 0; |
|
static const uint8_t NS_INPUT_INPUTMODE_NUMERIC = 1; |
|
static const uint8_t NS_INPUT_INPUTMODE_DIGIT = 2; |
|
static const uint8_t NS_INPUT_INPUTMODE_UPPERCASE = 3; |
|
static const uint8_t NS_INPUT_INPUTMODE_LOWERCASE = 4; |
|
static const uint8_t NS_INPUT_INPUTMODE_TITLECASE = 5; |
|
static const uint8_t NS_INPUT_INPUTMODE_AUTOCAPITALIZED = 6; |
|
|
|
static const nsAttrValue::EnumTable kInputInputmodeTable[] = { |
|
{ "auto", NS_INPUT_INPUTMODE_AUTO }, |
|
{ "numeric", NS_INPUT_INPUTMODE_NUMERIC }, |
|
{ "digit", NS_INPUT_INPUTMODE_DIGIT }, |
|
{ "uppercase", NS_INPUT_INPUTMODE_UPPERCASE }, |
|
{ "lowercase", NS_INPUT_INPUTMODE_LOWERCASE }, |
|
{ "titlecase", NS_INPUT_INPUTMODE_TITLECASE }, |
|
{ "autocapitalized", NS_INPUT_INPUTMODE_AUTOCAPITALIZED }, |
|
{ 0 } |
|
}; |
|
|
|
// Default inputmode value is "auto". |
|
static const nsAttrValue::EnumTable* kInputDefaultInputmode = &kInputInputmodeTable[0]; |
|
|
|
const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000); |
|
const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1); |
|
const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000); |
|
const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0); |
|
const Decimal HTMLInputElement::kDefaultStep = Decimal(1); |
|
const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60); |
|
const Decimal HTMLInputElement::kStepAny = Decimal(0); |
|
|
|
#define NS_INPUT_ELEMENT_STATE_IID \ |
|
{ /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */ \ |
|
0xdc3b3d14, \ |
|
0x23e2, \ |
|
0x4479, \ |
|
{0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \ |
|
} |
|
|
|
#define PROGRESS_STR "progress" |
|
static const uint32_t kProgressEventInterval = 50; // ms |
|
|
|
class HTMLInputElementState final : public nsISupports |
|
{ |
|
public: |
|
NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID) |
|
NS_DECL_ISUPPORTS |
|
|
|
bool IsCheckedSet() |
|
{ |
|
return mCheckedSet; |
|
} |
|
|
|
bool GetChecked() |
|
{ |
|
return mChecked; |
|
} |
|
|
|
void SetChecked(bool aChecked) |
|
{ |
|
mChecked = aChecked; |
|
mCheckedSet = true; |
|
} |
|
|
|
const nsString& GetValue() |
|
{ |
|
return mValue; |
|
} |
|
|
|
void SetValue(const nsAString& aValue) |
|
{ |
|
mValue = aValue; |
|
} |
|
|
|
const nsTArray<nsRefPtr<BlobImpl>>& GetBlobImpls() |
|
{ |
|
return mBlobImpls; |
|
} |
|
|
|
void SetBlobImpls(const nsTArray<nsRefPtr<File>>& aFile) |
|
{ |
|
mBlobImpls.Clear(); |
|
for (uint32_t i = 0, len = aFile.Length(); i < len; ++i) { |
|
mBlobImpls.AppendElement(aFile[i]->Impl()); |
|
} |
|
} |
|
|
|
HTMLInputElementState() |
|
: mValue() |
|
, mChecked(false) |
|
, mCheckedSet(false) |
|
{} |
|
|
|
protected: |
|
~HTMLInputElementState() {} |
|
|
|
nsString mValue; |
|
nsTArray<nsRefPtr<BlobImpl>> mBlobImpls; |
|
bool mChecked; |
|
bool mCheckedSet; |
|
}; |
|
|
|
NS_DEFINE_STATIC_IID_ACCESSOR(HTMLInputElementState, NS_INPUT_ELEMENT_STATE_IID) |
|
|
|
NS_IMPL_ISUPPORTS(HTMLInputElementState, HTMLInputElementState) |
|
|
|
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 = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); |
|
NS_ENSURE_STATE(localFile); |
|
|
|
if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || |
|
!mResult) { |
|
// Default to "desktop" directory for each platform |
|
nsCOMPtr<nsIFile> homeDir; |
|
NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(homeDir)); |
|
localFile = do_QueryInterface(homeDir); |
|
} else { |
|
nsAutoString prefStr; |
|
nsCOMPtr<nsIVariant> pref; |
|
mResult->GetValue(getter_AddRefs(pref)); |
|
pref->GetAsAString(prefStr); |
|
localFile->InitWithPath(prefStr); |
|
} |
|
|
|
mFilePicker->SetDisplayDirectory(localFile); |
|
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 aDomFile'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> |
|
DOMFileToLocalFile(File* aDomFile) |
|
{ |
|
nsString path; |
|
ErrorResult rv; |
|
aDomFile->GetMozFullPathInternal(path, rv); |
|
if (rv.Failed() || path.IsEmpty()) { |
|
rv.SuppressException(); |
|
return nullptr; |
|
} |
|
|
|
nsCOMPtr<nsIFile> localFile; |
|
rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true, |
|
getter_AddRefs(localFile)); |
|
if (NS_WARN_IF(rv.Failed())) { |
|
rv.SuppressException(); |
|
return nullptr; |
|
} |
|
|
|
return localFile.forget(); |
|
} |
|
|
|
} // 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<nsRefPtr<File>> newFiles; |
|
if (mode == static_cast<int16_t>(nsIFilePicker::modeOpenMultiple) || |
|
mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder)) { |
|
nsCOMPtr<nsISimpleEnumerator> iter; |
|
nsresult rv = mFilePicker->GetDomfiles(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)); |
|
nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(tmp); |
|
NS_WARN_IF_FALSE(domBlob, |
|
"Null file object from FilePicker's file enumerator?"); |
|
if (domBlob) { |
|
newFiles.AppendElement(static_cast<File*>(domBlob.get())); |
|
} |
|
} |
|
} else { |
|
MOZ_ASSERT(mode == static_cast<int16_t>(nsIFilePicker::modeOpen)); |
|
nsCOMPtr<nsISupports> tmp; |
|
nsresult rv = mFilePicker->GetDomfile(getter_AddRefs(tmp)); |
|
NS_ENSURE_SUCCESS(rv, rv); |
|
|
|
nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(tmp); |
|
if (blob) { |
|
nsRefPtr<File> file = static_cast<Blob*>(blob.get())->ToFile(); |
|
newFiles.AppendElement(file); |
|
} |
|
} |
|
|
|
if (newFiles.IsEmpty()) { |
|
return NS_OK; |
|
} |
|
|
|
// Store the last used directory using the content pref service: |
|
nsCOMPtr<nsIFile> file = DOMFileToLocalFile(newFiles[0]); |
|
if (file) { |
|
nsCOMPtr<nsIFile> lastUsedDir; |
|
file->GetParent(getter_AddRefs(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->SetFiles(newFiles, true); |
|
return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(), |
|
static_cast<nsIDOMHTMLInputElement*>(mInput.get()), |
|
NS_LITERAL_STRING("change"), true, |
|
false); |
|
} |
|
|
|
NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback, |
|
nsIFilePickerShownCallback) |
|
|
|
class nsColorPickerShownCallback final |
|
: public nsIColorPickerShownCallback |
|
{ |
|
~nsColorPickerShownCallback() {} |
|
|
|
public: |
|
nsColorPickerShownCallback(HTMLInputElement* aInput, |
|
nsIColorPicker* aColorPicker) |
|
: mInput(aInput) |
|
, mColorPicker(aColorPicker) |
|
, mValueChanged(false) |
|
{} |
|
|
|
NS_DECL_ISUPPORTS |
|
|
|
NS_IMETHOD Update(const nsAString& aColor) override; |
|
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. |
|
*/ |
|
nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate); |
|
|
|
nsRefPtr<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); |
|
} |
|
|
|
mInput->SetValue(aColor); |
|
|
|
if (!aTrustedUpdate) { |
|
nsAutoString newValue; |
|
mInput->GetValue(newValue); |
|
if (!oldValue.Equals(newValue)) { |
|
valueChanged = true; |
|
} |
|
} |
|
|
|
if (valueChanged) { |
|
mValueChanged = true; |
|
return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(), |
|
static_cast<nsIDOMHTMLInputElement*>(mInput.get()), |
|
NS_LITERAL_STRING("input"), true, |
|
false); |
|
} |
|
|
|
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<nsIDOMHTMLInputElement*>(mInput.get()), |
|
NS_LITERAL_STRING("change"), true, |
|
false); |
|
} |
|
|
|
return rv; |
|
} |
|
|
|
NS_IMPL_ISUPPORTS(nsColorPickerShownCallback, nsIColorPickerShownCallback) |
|
|
|
bool |
|
HTMLInputElement::IsPopupBlocked() const |
|
{ |
|
nsCOMPtr<nsPIDOMWindow> win = OwnerDoc()->GetWindow(); |
|
MOZ_ASSERT(win, "window should not be null"); |
|
if (!win) { |
|
return true; |
|
} |
|
|
|
// Check if page is allowed to open the popup |
|
if (win->GetPopupControlState() <= openControlled) { |
|
return false; |
|
} |
|
|
|
nsCOMPtr<nsIPopupWindowManager> pm = do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID); |
|
if (!pm) { |
|
return true; |
|
} |
|
|
|
uint32_t permission; |
|
pm->TestPermission(OwnerDoc()->NodePrincipal(), &permission); |
|
return permission == nsIPopupWindowManager::DENY_POPUP; |
|
} |
|
|
|
nsresult |
|
HTMLInputElement::InitColorPicker() |
|
{ |
|
if (mPickerRunning) { |
|
NS_WARNING("Just one nsIColorPicker is allowed"); |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
nsCOMPtr<nsIDocument> doc = OwnerDoc(); |
|
|
|
nsCOMPtr<nsPIDOMWindow> win = doc->GetWindow(); |
|
if (!win) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
if (IsPopupBlocked()) { |
|
win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString()); |
|
return NS_OK; |
|
} |
|
|
|
// Get Loc title |
|
nsXPIDLString 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; |
|
GetValueInternal(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<nsIDocument> doc = OwnerDoc(); |
|
|
|
nsCOMPtr<nsPIDOMWindow> win = doc->GetWindow(); |
|
if (!win) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
if (IsPopupBlocked()) { |
|
win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString()); |
|
return NS_OK; |
|
} |
|
|
|
// Get Loc title |
|
nsXPIDLString title; |
|
nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES, |
|
"FileUpload", 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); |
|
|
|
// 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); |
|
} else { |
|
filePicker->AppendFilters(nsIFilePicker::filterAll); |
|
} |
|
|
|
// Set default directry and filename |
|
nsAutoString defaultName; |
|
|
|
const nsTArray<nsRefPtr<File>>& oldFiles = GetFilesInternal(); |
|
|
|
nsCOMPtr<nsIFilePickerShownCallback> callback = |
|
new HTMLInputElement::nsFilePickerShownCallback(this, filePicker); |
|
|
|
if (!oldFiles.IsEmpty() && |
|
aType != FILE_PICKER_DIRECTORY) { |
|
nsString path; |
|
|
|
ErrorResult error; |
|
oldFiles[0]->GetMozFullPathInternal(path, error); |
|
if (NS_WARN_IF(error.Failed())) { |
|
return error.StealNSResult(); |
|
} |
|
|
|
nsCOMPtr<nsIFile> localFile; |
|
rv = NS_NewLocalFile(path, false, getter_AddRefs(localFile)); |
|
|
|
if (NS_SUCCEEDED(rv)) { |
|
nsCOMPtr<nsIFile> parentFile; |
|
rv = localFile->GetParent(getter_AddRefs(parentFile)); |
|
if (NS_SUCCEEDED(rv)) { |
|
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; |
|
oldFiles[0]->GetName(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 NS_LITERAL_STRING("browser.upload.lastDir") |
|
|
|
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(nsIDocument* aDoc, |
|
nsIFilePicker* aFilePicker, |
|
nsIFilePickerShownCallback* aFpCallback) |
|
{ |
|
NS_PRECONDITION(aDoc, "aDoc is null"); |
|
NS_PRECONDITION(aFilePicker, "aFilePicker is null"); |
|
NS_PRECONDITION(aFpCallback, "aFpCallback is null"); |
|
|
|
nsIURI* docURI = aDoc->GetDocumentURI(); |
|
NS_PRECONDITION(docURI, "docURI is null"); |
|
|
|
nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext(); |
|
nsCOMPtr<nsIContentPrefCallback2> prefCallback = |
|
new UploadLastDir::ContentPrefCallback(aFilePicker, aFpCallback); |
|
|
|
#ifdef MOZ_B2G |
|
if (XRE_IsContentProcess()) { |
|
prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR); |
|
return NS_OK; |
|
} |
|
#endif |
|
|
|
// 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(nsIDocument* aDoc, nsIFile* aDir) |
|
{ |
|
NS_PRECONDITION(aDoc, "aDoc is null"); |
|
if (!aDir) { |
|
return NS_OK; |
|
} |
|
|
|
#ifdef MOZ_B2G |
|
if (XRE_IsContentProcess()) { |
|
return NS_OK; |
|
} |
|
#endif |
|
|
|
nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI(); |
|
NS_PRECONDITION(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; |
|
nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID); |
|
if (!prefValue) |
|
return NS_ERROR_OUT_OF_MEMORY; |
|
prefValue->SetAsAString(unicodePath); |
|
|
|
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(nsIDOMHTMLInputElement* aTarget, |
|
nsPresContext* aPresContext, |
|
const nsAString& aEventType); |
|
#endif |
|
|
|
// |
|
// construction, destruction |
|
// |
|
|
|
HTMLInputElement::HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo, |
|
FromParser aFromParser) |
|
: nsGenericHTMLFormElementWithState(aNodeInfo) |
|
, mType(kInputDefaultType->value) |
|
, mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown) |
|
, mDisabledChanged(false) |
|
, mValueChanged(false) |
|
, mCheckedChanged(false) |
|
, mChecked(false) |
|
, mHandlingSelectEvent(false) |
|
, mShouldInitChecked(false) |
|
, mParserCreating(aFromParser != NOT_FROM_PARSER) |
|
, 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) |
|
, mSelectionCached(true) |
|
, mHasPatternAttribute(false) |
|
{ |
|
// We are in a type=text so we now we currenty need a nsTextEditorState. |
|
mInputData.mState = new nsTextEditorState(this); |
|
|
|
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); |
|
} |
|
|
|
HTMLInputElement::~HTMLInputElement() |
|
{ |
|
if (mNumberControlSpinnerIsSpinning) { |
|
StopNumberControlSpinnerSpin(eDisallowDispatchingEvents); |
|
} |
|
DestroyImageLoadingContent(); |
|
FreeData(); |
|
} |
|
|
|
void |
|
HTMLInputElement::FreeData() |
|
{ |
|
if (!IsSingleLineTextControl(false)) { |
|
free(mInputData.mValue); |
|
mInputData.mValue = nullptr; |
|
} else { |
|
UnbindFromFrame(nullptr); |
|
delete mInputData.mState; |
|
mInputData.mState = nullptr; |
|
} |
|
} |
|
|
|
nsTextEditorState* |
|
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, |
|
nsGenericHTMLFormElementWithState) |
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity) |
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers) |
|
if (tmp->IsSingleLineTextControl(false)) { |
|
tmp->mInputData.mState->Traverse(cb); |
|
} |
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFiles) |
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList) |
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement, |
|
nsGenericHTMLFormElementWithState) |
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity) |
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers) |
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFiles) |
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList) |
|
if (tmp->IsSingleLineTextControl(false)) { |
|
tmp->mInputData.mState->Unlink(); |
|
} |
|
//XXX should unlink more? |
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
|
|
NS_IMPL_ADDREF_INHERITED(HTMLInputElement, Element) |
|
NS_IMPL_RELEASE_INHERITED(HTMLInputElement, Element) |
|
|
|
// QueryInterface implementation for HTMLInputElement |
|
NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLInputElement) |
|
NS_INTERFACE_TABLE_INHERITED(HTMLInputElement, |
|
nsIDOMHTMLInputElement, |
|
nsITextControlElement, |
|
nsIPhonetic, |
|
imgINotificationObserver, |
|
nsIImageLoadingContent, |
|
imgIOnloadBlocker, |
|
nsIDOMNSEditableElement, |
|
nsIConstraintValidation) |
|
NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState) |
|
|
|
// nsIConstraintValidation |
|
NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLInputElement) |
|
|
|
// nsIDOMNode |
|
|
|
nsresult |
|
HTMLInputElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const |
|
{ |
|
*aResult = nullptr; |
|
|
|
already_AddRefed<mozilla::dom::NodeInfo> ni = nsRefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget(); |
|
nsRefPtr<HTMLInputElement> it = new HTMLInputElement(ni, NOT_FROM_PARSER); |
|
|
|
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; |
|
GetValueInternal(value); |
|
// SetValueInternal handles setting the VALUE_CHANGED bit for us |
|
rv = it->SetValueInternal(value, false, true); |
|
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->mStaticDocFileList); |
|
} else { |
|
it->mFiles.Clear(); |
|
it->mFiles.AppendElements(mFiles); |
|
} |
|
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); |
|
} |
|
break; |
|
case VALUE_MODE_DEFAULT: |
|
if (mType == NS_FORM_INPUT_IMAGE && it->OwnerDoc()->IsStaticDocument()) { |
|
CreateStaticImageClone(it); |
|
} |
|
break; |
|
} |
|
|
|
it.forget(aResult); |
|
return NS_OK; |
|
} |
|
|
|
nsresult |
|
HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName, |
|
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 the parser is 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 || !mParserCreating)) { |
|
WillRemoveFromRadioGroup(); |
|
} else if (aNotify && aName == nsGkAtoms::src && |
|
mType == NS_FORM_INPUT_IMAGE) { |
|
if (aValue) { |
|
LoadImage(aValue->String(), true, aNotify, eImageLoadType_Normal); |
|
} else { |
|
// Null value means the attr got unset; drop the image |
|
CancelImageRequests(aNotify); |
|
} |
|
} else if (aNotify && aName == nsGkAtoms::disabled) { |
|
mDisabledChanged = true; |
|
} else if (aName == nsGkAtoms::dir && |
|
AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir, |
|
nsGkAtoms::_auto, eIgnoreCase)) { |
|
SetDirectionIfAuto(false, aNotify); |
|
} 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); |
|
} |
|
} |
|
} |
|
|
|
return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName, |
|
aValue, aNotify); |
|
} |
|
|
|
nsresult |
|
HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, |
|
const nsAttrValue* aValue, 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 the parser is 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 || !mParserCreating)) { |
|
AddedToRadioGroup(); |
|
UpdateValueMissingValidityStateForRadio(false); |
|
} |
|
|
|
// 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 && |
|
!mValueChanged && GetValueMode() == VALUE_MODE_VALUE) { |
|
SetDefaultValueAsValue(); |
|
} |
|
|
|
// |
|
// 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 the parser is creating this element (wait |
|
// until everything is set) |
|
if (mParserCreating) { |
|
mShouldInitChecked = true; |
|
} else { |
|
DoSetChecked(DefaultChecked(), true, true); |
|
SetCheckedChanged(false); |
|
} |
|
} |
|
|
|
if (aName == nsGkAtoms::type) { |
|
if (!aValue) { |
|
// We're now a text input. Note that we have to handle this manually, |
|
// since removing an attribute (which is what happened, since aValue is |
|
// null) doesn't call ParseAttribute. |
|
HandleTypeChange(kInputDefaultType->value); |
|
} |
|
|
|
UpdateBarredFromConstraintValidation(); |
|
|
|
if (mType != NS_FORM_INPUT_IMAGE) { |
|
// We're no longer an image input. Cancel our image requests, if we have |
|
// any. Note that doing this when we already weren't an image is ok -- |
|
// just does nothing. |
|
CancelImageRequests(aNotify); |
|
} else if (aNotify) { |
|
// We just got switched to be an image input; we should see |
|
// whether we have an image to load; |
|
nsAutoString src; |
|
if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) { |
|
LoadImage(src, false, aNotify, eImageLoadType_Normal); |
|
} |
|
} |
|
} |
|
|
|
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); |
|
} |
|
|
|
UpdateValueMissingValidityState(); |
|
|
|
// This *has* to be called *after* validity has changed. |
|
if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) { |
|
UpdateBarredFromConstraintValidation(); |
|
} |
|
} else if (MaxLengthApplies() && aName == nsGkAtoms::maxlength) { |
|
UpdateTooLongValidityState(); |
|
} 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; |
|
UpdatePatternMismatchValidityState(); |
|
} else if (aName == nsGkAtoms::multiple) { |
|
UpdateTypeMismatchValidityState(); |
|
} else if (aName == nsGkAtoms::max) { |
|
UpdateHasRange(); |
|
UpdateRangeOverflowValidityState(); |
|
if (mType == NS_FORM_INPUT_RANGE) { |
|
// The value may need to change when @max changes since the value may |
|
// have been invalid and can now change to a valid value, or vice |
|
// versa. For example, consider: |
|
// <input type=range value=-1 max=1 step=3>. The valid range is 0 to 1 |
|
// while the nearest valid steps are -1 and 2 (the max value having |
|
// prevented there being a valid step in range). Changing @max to/from |
|
// 1 and a number greater than on equal to 3 should change whether we |
|
// have a step mismatch or not. |
|
// The value may also need to change between a value that results in |
|
// a step mismatch and a value that results in overflow. For example, |
|
// if @max in the example above were to change from 1 to -1. |
|
nsAutoString value; |
|
GetValue(value); |
|
nsresult rv = SetValueInternal(value, false, false); |
|
NS_ENSURE_SUCCESS(rv, rv); |
|
MOZ_ASSERT(!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW), |
|
"HTML5 spec does not allow this"); |
|
} |
|
} else if (aName == nsGkAtoms::min) { |
|
UpdateHasRange(); |
|
UpdateRangeUnderflowValidityState(); |
|
UpdateStepMismatchValidityState(); |
|
if (mType == NS_FORM_INPUT_RANGE) { |
|
// See @max comment |
|
nsAutoString value; |
|
GetValue(value); |
|
nsresult rv = SetValueInternal(value, false, false); |
|
NS_ENSURE_SUCCESS(rv, rv); |
|
MOZ_ASSERT(!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW), |
|
"HTML5 spec does not allow this"); |
|
} |
|
} else if (aName == nsGkAtoms::step) { |
|
UpdateStepMismatchValidityState(); |
|
if (mType == NS_FORM_INPUT_RANGE) { |
|
// See @max comment |
|
nsAutoString value; |
|
GetValue(value); |
|
nsresult rv = SetValueInternal(value, false, false); |
|
NS_ENSURE_SUCCESS(rv, rv); |
|
MOZ_ASSERT(!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW), |
|
"HTML5 spec does not allow this"); |
|
} |
|
} else if (aName == nsGkAtoms::dir && |
|
aValue && aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) { |
|
SetDirectionIfAuto(true, aNotify); |
|
} else if (aName == nsGkAtoms::lang) { |
|
if (mType == NS_FORM_INPUT_NUMBER) { |
|
// Update the value that is displayed to the user to the new locale: |
|
nsAutoString value; |
|
GetValueInternal(value); |
|
nsNumberControlFrame* numberControlFrame = |
|
do_QueryFrame(GetPrimaryFrame()); |
|
if (numberControlFrame) { |
|
numberControlFrame->SetValueOfAnonTextControl(value); |
|
} |
|
} |
|
} else if (aName == nsGkAtoms::autocomplete) { |
|
// Clear the cached @autocomplete attribute state. |
|
mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown; |
|
} |
|
|
|
UpdateState(aNotify); |
|
} |
|
|
|
return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName, |
|
aValue, aNotify); |
|
} |
|
|
|
// nsIDOMHTMLInputElement |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::GetForm(nsIDOMHTMLFormElement** aForm) |
|
{ |
|
return nsGenericHTMLFormElementWithState::GetForm(aForm); |
|
} |
|
|
|
NS_IMPL_STRING_ATTR(HTMLInputElement, DefaultValue, value) |
|
NS_IMPL_BOOL_ATTR(HTMLInputElement, DefaultChecked, checked) |
|
NS_IMPL_STRING_ATTR(HTMLInputElement, Accept, accept) |
|
NS_IMPL_STRING_ATTR(HTMLInputElement, Align, align) |
|
NS_IMPL_STRING_ATTR(HTMLInputElement, Alt, alt) |
|
NS_IMPL_BOOL_ATTR(HTMLInputElement, Autofocus, autofocus) |
|
//NS_IMPL_BOOL_ATTR(HTMLInputElement, Checked, checked) |
|
NS_IMPL_BOOL_ATTR(HTMLInputElement, Disabled, disabled) |
|
NS_IMPL_STRING_ATTR(HTMLInputElement, Max, max) |
|
NS_IMPL_STRING_ATTR(HTMLInputElement, Min, min) |
|
NS_IMPL_ACTION_ATTR(HTMLInputElement, FormAction, formaction) |
|
NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLInputElement, FormEnctype, formenctype, |
|
"", kFormDefaultEnctype->tag) |
|
NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLInputElement, FormMethod, formmethod, |
|
"", kFormDefaultMethod->tag) |
|
NS_IMPL_BOOL_ATTR(HTMLInputElement, FormNoValidate, formnovalidate) |
|
NS_IMPL_STRING_ATTR(HTMLInputElement, FormTarget, formtarget) |
|
NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, InputMode, inputmode, |
|
kInputDefaultInputmode->tag) |
|
NS_IMPL_BOOL_ATTR(HTMLInputElement, Multiple, multiple) |
|
NS_IMPL_NON_NEGATIVE_INT_ATTR(HTMLInputElement, MaxLength, maxlength) |
|
NS_IMPL_STRING_ATTR(HTMLInputElement, Name, name) |
|
NS_IMPL_BOOL_ATTR(HTMLInputElement, ReadOnly, readonly) |
|
NS_IMPL_BOOL_ATTR(HTMLInputElement, Required, required) |
|
NS_IMPL_URI_ATTR(HTMLInputElement, Src, src) |
|
NS_IMPL_STRING_ATTR(HTMLInputElement, Step, step) |
|
NS_IMPL_STRING_ATTR(HTMLInputElement, UseMap, usemap) |
|
//NS_IMPL_STRING_ATTR(HTMLInputElement, Value, value) |
|
NS_IMPL_UINT_ATTR_NON_ZERO_DEFAULT_VALUE(HTMLInputElement, Size, size, DEFAULT_COLS) |
|
NS_IMPL_STRING_ATTR(HTMLInputElement, Pattern, pattern) |
|
NS_IMPL_STRING_ATTR(HTMLInputElement, Placeholder, placeholder) |
|
NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, Type, type, |
|
kInputDefaultType->tag) |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::GetAutocomplete(nsAString& aValue) |
|
{ |
|
if (!DoesAutocompleteApply()) { |
|
return NS_OK; |
|
} |
|
|
|
aValue.Truncate(0); |
|
const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete); |
|
|
|
mAutocompleteAttrState = |
|
nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue, |
|
mAutocompleteAttrState); |
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::SetAutocomplete(const nsAString& aValue) |
|
{ |
|
return SetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, nullptr, aValue, true); |
|
} |
|
|
|
void |
|
HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo) |
|
{ |
|
if (!DoesAutocompleteApply()) { |
|
aInfo.SetNull(); |
|
return; |
|
} |
|
|
|
const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete); |
|
mAutocompleteAttrState = |
|
nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aInfo.SetValue(), |
|
mAutocompleteAttrState); |
|
} |
|
|
|
int32_t |
|
HTMLInputElement::TabIndexDefault() |
|
{ |
|
return 0; |
|
} |
|
|
|
uint32_t |
|
HTMLInputElement::Height() |
|
{ |
|
if (mType != NS_FORM_INPUT_IMAGE) { |
|
return 0; |
|
} |
|
return GetWidthHeightForImage(mCurrentRequest).height; |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::GetHeight(uint32_t* aHeight) |
|
{ |
|
*aHeight = Height(); |
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::SetHeight(uint32_t aHeight) |
|
{ |
|
ErrorResult rv; |
|
SetHeight(aHeight, rv); |
|
return rv.StealNSResult(); |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::GetIndeterminate(bool* aValue) |
|
{ |
|
*aValue = Indeterminate(); |
|
return NS_OK; |
|
} |
|
|
|
void |
|
HTMLInputElement::SetIndeterminateInternal(bool aValue, |
|
bool aShouldInvalidate) |
|
{ |
|
mIndeterminate = aValue; |
|
|
|
if (aShouldInvalidate) { |
|
// Repaint the frame |
|
nsIFrame* frame = GetPrimaryFrame(); |
|
if (frame) |
|
frame->InvalidateFrameSubtree(); |
|
} |
|
|
|
UpdateState(true); |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::SetIndeterminate(bool aValue) |
|
{ |
|
SetIndeterminateInternal(aValue, true); |
|
return NS_OK; |
|
} |
|
|
|
uint32_t |
|
HTMLInputElement::Width() |
|
{ |
|
if (mType != NS_FORM_INPUT_IMAGE) { |
|
return 0; |
|
} |
|
return GetWidthHeightForImage(mCurrentRequest).width; |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::GetWidth(uint32_t* aWidth) |
|
{ |
|
*aWidth = Width(); |
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::SetWidth(uint32_t aWidth) |
|
{ |
|
ErrorResult rv; |
|
SetWidth(aWidth, rv); |
|
return rv.StealNSResult(); |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::GetValue(nsAString& aValue) |
|
{ |
|
GetValueInternal(aValue); |
|
|
|
// Don't return non-sanitized value for types that are experimental on mobile. |
|
if (IsExperimentalMobileType(mType)) { |
|
SanitizeValue(aValue); |
|
} |
|
|
|
return NS_OK; |
|
} |
|
|
|
nsresult |
|
HTMLInputElement::GetValueInternal(nsAString& aValue) const |
|
{ |
|
switch (GetValueMode()) { |
|
case VALUE_MODE_VALUE: |
|
if (IsSingleLineTextControl(false)) { |
|
mInputData.mState->GetValue(aValue, true); |
|
} else { |
|
aValue.Assign(mInputData.mValue); |
|
} |
|
return NS_OK; |
|
|
|
case VALUE_MODE_FILENAME: |
|
if (nsContentUtils::IsCallerChrome()) { |
|
#ifndef MOZ_CHILD_PERMISSIONS |
|
aValue.Assign(mFirstFilePath); |
|
#else |
|
// XXX We'd love to assert that this can't happen, but some mochitests |
|
// use SpecialPowers to circumvent our more sane security model. |
|
if (!mFiles.IsEmpty()) { |
|
ErrorResult rv; |
|
mFiles[0]->GetMozFullPath(aValue, rv); |
|
if (NS_WARN_IF(rv.Failed())) { |
|
return rv.StealNSResult(); |
|
} |
|
return NS_OK; |
|
} |
|
else { |
|
aValue.Truncate(); |
|
} |
|
#endif |
|
} else { |
|
// Just return the leaf name |
|
if (mFiles.IsEmpty()) { |
|
aValue.Truncate(); |
|
} else { |
|
mFiles[0]->GetName(aValue); |
|
} |
|
} |
|
|
|
return NS_OK; |
|
|
|
case VALUE_MODE_DEFAULT: |
|
// Treat defaultValue as value. |
|
GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue); |
|
return NS_OK; |
|
|
|
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 NS_OK; |
|
} |
|
|
|
// This return statement is required for some compilers. |
|
return NS_OK; |
|
} |
|
|
|
bool |
|
HTMLInputElement::IsValueEmpty() const |
|
{ |
|
nsAutoString value; |
|
GetValueInternal(value); |
|
|
|
return value.IsEmpty(); |
|
} |
|
|
|
void |
|
HTMLInputElement::ClearFiles(bool aSetValueChanged) |
|
{ |
|
nsTArray<nsRefPtr<File>> files; |
|
SetFiles(files, aSetValueChanged); |
|
} |
|
|
|
/* 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); |
|
} |
|
|
|
bool |
|
HTMLInputElement::ConvertStringToNumber(nsAString& aValue, |
|
Decimal& aResultValue) const |
|
{ |
|
MOZ_ASSERT(DoesValueAsNumberApply(), |
|
"ConvertStringToNumber only applies if .valueAsNumber applies"); |
|
|
|
switch (mType) { |
|
case NS_FORM_INPUT_NUMBER: |
|
case NS_FORM_INPUT_RANGE: |
|
{ |
|
aResultValue = StringToDecimal(aValue); |
|
if (!aResultValue.isFinite()) { |
|
return false; |
|
} |
|
return true; |
|
} |
|
case NS_FORM_INPUT_DATE: |
|
{ |
|
uint32_t year, month, day; |
|
if (!GetValueAsDate(aValue, &year, &month, &day)) { |
|
return false; |
|
} |
|
|
|
JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day)); |
|
if (!time.isValid()) { |
|
return false; |
|
} |
|
|
|
aResultValue = Decimal::fromDouble(time.toDouble()); |
|
return true; |
|
} |
|
case NS_FORM_INPUT_TIME: |
|
uint32_t milliseconds; |
|
if (!ParseTime(aValue, &milliseconds)) { |
|
return false; |
|
} |
|
|
|
aResultValue = Decimal(int32_t(milliseconds)); |
|
return true; |
|
default: |
|
MOZ_ASSERT(false, "Unrecognized input type"); |
|
return false; |
|
} |
|
} |
|
|
|
Decimal |
|
HTMLInputElement::GetValueAsDecimal() const |
|
{ |
|
Decimal decimalValue; |
|
nsAutoString stringValue; |
|
|
|
GetValueInternal(stringValue); |
|
|
|
return !ConvertStringToNumber(stringValue, decimalValue) ? Decimal::nan() |
|
: decimalValue; |
|
} |
|
|
|
void |
|
HTMLInputElement::SetValue(const nsAString& aValue, 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 (!nsContentUtils::IsCallerChrome()) { |
|
// setting the value of a "FILE" input widget requires |
|
// chrome privilege |
|
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); |
|
return; |
|
} |
|
Sequence<nsString> list; |
|
if (!list.AppendElement(aValue, fallible)) { |
|
aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
|
return; |
|
} |
|
|
|
MozSetFileNameArray(list, aRv); |
|
return; |
|
} |
|
else { |
|
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); |
|
|
|
nsresult rv = SetValueInternal(aValue, false, true); |
|
if (NS_FAILED(rv)) { |
|
aRv.Throw(rv); |
|
return; |
|
} |
|
|
|
if (mFocusedValue.Equals(currentValue)) { |
|
GetValue(mFocusedValue); |
|
} |
|
} else { |
|
nsresult rv = SetValueInternal(aValue, false, true); |
|
if (NS_FAILED(rv)) { |
|
aRv.Throw(rv); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::SetValue(const nsAString& aValue) |
|
{ |
|
ErrorResult rv; |
|
SetValue(aValue, rv); |
|
return rv.StealNSResult(); |
|
} |
|
|
|
nsGenericHTMLElement* |
|
HTMLInputElement::GetList() const |
|
{ |
|
nsAutoString dataListId; |
|
GetAttr(kNameSpaceID_None, nsGkAtoms::list, dataListId); |
|
if (dataListId.IsEmpty()) { |
|
return nullptr; |
|
} |
|
|
|
//XXXsmaug How should this all work in case input element is in Shadow DOM. |
|
nsIDocument* doc = GetUncomposedDoc(); |
|
if (!doc) { |
|
return nullptr; |
|
} |
|
|
|
Element* element = doc->GetElementById(dataListId); |
|
if (!element || !element->IsHTMLElement(nsGkAtoms::datalist)) { |
|
return nullptr; |
|
} |
|
|
|
return static_cast<nsGenericHTMLElement*>(element); |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::GetList(nsIDOMHTMLElement** aValue) |
|
{ |
|
*aValue = nullptr; |
|
|
|
nsRefPtr<nsGenericHTMLElement> element = GetList(); |
|
if (!element) { |
|
return NS_OK; |
|
} |
|
|
|
element.forget(aValue); |
|
return NS_OK; |
|
} |
|
|
|
void |
|
HTMLInputElement::SetValue(Decimal aValue) |
|
{ |
|
MOZ_ASSERT(!aValue.isInfinity(), "aValue must not be Infinity!"); |
|
|
|
if (aValue.isNaN()) { |
|
SetValue(EmptyString()); |
|
return; |
|
} |
|
|
|
nsAutoString value; |
|
ConvertNumberToString(aValue, value); |
|
SetValue(value); |
|
} |
|
|
|
bool |
|
HTMLInputElement::ConvertNumberToString(Decimal aValue, |
|
nsAString& aResultString) const |
|
{ |
|
MOZ_ASSERT(DoesValueAsNumberApply(), |
|
"ConvertNumberToString is only implemented for types implementing .valueAsNumber"); |
|
MOZ_ASSERT(aValue.isFinite(), |
|
"aValue must be a valid non-Infinite number."); |
|
|
|
aResultString.Truncate(); |
|
|
|
switch (mType) { |
|
case NS_FORM_INPUT_NUMBER: |
|
case NS_FORM_INPUT_RANGE: |
|
{ |
|
char buf[32]; |
|
bool ok = aValue.toString(buf, ArrayLength(buf)); |
|
aResultString.AssignASCII(buf); |
|
MOZ_ASSERT(ok, "buf not big enough"); |
|
return ok; |
|
} |
|
case NS_FORM_INPUT_DATE: |
|
{ |
|
// The specs (and our JS APIs) require |aValue| to be truncated. |
|
aValue = aValue.floor(); |
|
|
|
double year = JS::YearFromTime(aValue.toDouble()); |
|
double month = JS::MonthFromTime(aValue.toDouble()); |
|
double day = JS::DayFromTime(aValue.toDouble()); |
|
|
|
if (IsNaN(year) || IsNaN(month) || IsNaN(day)) { |
|
return false; |
|
} |
|
|
|
aResultString.AppendPrintf("%04.0f-%02.0f-%02.0f", year, |
|
month + 1, day); |
|
|
|
return true; |
|
} |
|
case NS_FORM_INPUT_TIME: |
|
{ |
|
// Per spec, we need to truncate |aValue| and we should only represent |
|
// times inside a day [00:00, 24:00[, which means that we should do a |
|
// modulo on |aValue| using the number of milliseconds in a day (86400000). |
|
uint32_t value = NS_floorModulo(aValue.floor(), Decimal(86400000)).toDouble(); |
|
|
|
uint16_t milliseconds = value % 1000; |
|
value /= 1000; |
|
|
|
uint8_t seconds = value % 60; |
|
value /= 60; |
|
|
|
uint8_t minutes = value % 60; |
|
value /= 60; |
|
|
|
uint8_t hours = value; |
|
|
|
if (milliseconds != 0) { |
|
aResultString.AppendPrintf("%02d:%02d:%02d.%03d", |
|
hours, minutes, seconds, milliseconds); |
|
} else if (seconds != 0) { |
|
aResultString.AppendPrintf("%02d:%02d:%02d", |
|
hours, minutes, seconds); |
|
} else { |
|
aResultString.AppendPrintf("%02d:%02d", hours, minutes); |
|
} |
|
|
|
return true; |
|
} |
|
default: |
|
MOZ_ASSERT(false, "Unrecognized input type"); |
|
return false; |
|
} |
|
} |
|
|
|
|
|
Nullable<Date> |
|
HTMLInputElement::GetValueAsDate(ErrorResult& aRv) |
|
{ |
|
if (mType != NS_FORM_INPUT_DATE && mType != NS_FORM_INPUT_TIME) { |
|
return Nullable<Date>(); |
|
} |
|
|
|
switch (mType) { |
|
case NS_FORM_INPUT_DATE: |
|
{ |
|
uint32_t year, month, day; |
|
nsAutoString value; |
|
GetValueInternal(value); |
|
if (!GetValueAsDate(value, &year, &month, &day)) { |
|
return Nullable<Date>(); |
|
} |
|
|
|
JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day)); |
|
return Nullable<Date>(Date(time)); |
|
} |
|
case NS_FORM_INPUT_TIME: |
|
{ |
|
uint32_t millisecond; |
|
nsAutoString value; |
|
GetValueInternal(value); |
|
if (!ParseTime(value, &millisecond)) { |
|
return Nullable<Date>(); |
|
} |
|
|
|
JS::ClippedTime time = JS::TimeClip(millisecond); |
|
MOZ_ASSERT(time.toDouble() == millisecond, |
|
"HTML times are restricted to the day after the epoch and " |
|
"never clip"); |
|
return Nullable<Date>(Date(time)); |
|
} |
|
} |
|
|
|
MOZ_ASSERT(false, "Unrecognized input type"); |
|
aRv.Throw(NS_ERROR_UNEXPECTED); |
|
return Nullable<Date>(); |
|
} |
|
|
|
void |
|
HTMLInputElement::SetValueAsDate(Nullable<Date> aDate, ErrorResult& aRv) |
|
{ |
|
if (mType != NS_FORM_INPUT_DATE && mType != NS_FORM_INPUT_TIME) { |
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
|
return; |
|
} |
|
|
|
if (aDate.IsNull() || aDate.Value().IsUndefined()) { |
|
aRv = SetValue(EmptyString()); |
|
return; |
|
} |
|
|
|
SetValue(Decimal::fromDouble(aDate.Value().TimeStamp().toDouble())); |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::GetValueAsNumber(double* aValueAsNumber) |
|
{ |
|
*aValueAsNumber = ValueAsNumber(); |
|
return NS_OK; |
|
} |
|
|
|
void |
|
HTMLInputElement::SetValueAsNumber(double aValueAsNumber, ErrorResult& aRv) |
|
{ |
|
// TODO: return TypeError when HTMLInputElement is converted to WebIDL, see |
|
// bug 825197. |
|
if (IsInfinite(aValueAsNumber)) { |
|
aRv.Throw(NS_ERROR_INVALID_ARG); |
|
return; |
|
} |
|
|
|
if (!DoesValueAsNumberApply()) { |
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); |
|
return; |
|
} |
|
|
|
SetValue(Decimal::fromDouble(aValueAsNumber)); |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::SetValueAsNumber(double aValueAsNumber) |
|
{ |
|
ErrorResult rv; |
|
SetValueAsNumber(aValueAsNumber, rv); |
|
return rv.StealNSResult(); |
|
} |
|
|
|
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 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 ConvertStringToNumber(maxStr, max) ? max : defaultMaximum; |
|
} |
|
|
|
Decimal |
|
HTMLInputElement::GetStepBase() const |
|
{ |
|
MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER || |
|
mType == NS_FORM_INPUT_DATE || |
|
mType == NS_FORM_INPUT_TIME || |
|
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) && |
|
ConvertStringToNumber(minStr, stepBase)) { |
|
return stepBase; |
|
} |
|
|
|
// If @min is not a double, we should use @value. |
|
nsAutoString valueStr; |
|
if (GetAttr(kNameSpaceID_None, nsGkAtoms::value, valueStr) && |
|
ConvertStringToNumber(valueStr, stepBase)) { |
|
return stepBase; |
|
} |
|
|
|
return kDefaultStepBase; |
|
} |
|
|
|
nsresult |
|
HTMLInputElement::GetValueIfStepped(int32_t aStep, |
|
StepCallerType aCallerType, |
|
Decimal* aNextStep) |
|
{ |
|
if (!DoStepDownStepUpApply()) { |
|
return NS_ERROR_DOM_INVALID_STATE_ERR; |
|
} |
|
|
|
Decimal stepBase = GetStepBase(); |
|
Decimal step = GetStep(); |
|
if (step == kStepAny) { |
|
if (aCallerType != CALLED_FOR_USER_EVENT) { |
|
return NS_ERROR_DOM_INVALID_STATE_ERR; |
|
} |
|
// Allow the spin buttons and up/down arrow keys to do something sensible: |
|
step = GetDefaultStep(); |
|
} |
|
|
|
Decimal minimum = GetMinimum(); |
|
Decimal maximum = GetMaximum(); |
|
|
|
if (!maximum.isNaN()) { |
|
// "max - (max - stepBase) % step" is the nearest valid value to max. |
|
maximum = maximum - NS_floorModulo(maximum - stepBase, step); |
|
if (!minimum.isNaN()) { |
|
if (minimum > maximum) { |
|
// Either the minimum was greater than the maximum prior to our |
|
// adjustment to align maximum on a step, or else (if we adjusted |
|
// maximum) there is no valid step between minimum and the unadjusted |
|
// maximum. |
|
return NS_OK; |
|
} |
|
} |
|
} |
|
|
|
Decimal value = GetValueAsDecimal(); |
|
bool valueWasNaN = false; |
|
if (value.isNaN()) { |
|
value = Decimal(0); |
|
valueWasNaN = true; |
|
} |
|
Decimal valueBeforeStepping = value; |
|
|
|
Decimal deltaFromStep = NS_floorModulo(value - stepBase, step); |
|
|
|
if (deltaFromStep != Decimal(0)) { |
|
if (aStep > 0) { |
|
value += step - deltaFromStep; // partial step |
|
value += step * Decimal(aStep - 1); // then remaining steps |
|
} else if (aStep < 0) { |
|
value -= deltaFromStep; // partial step |
|
value += step * Decimal(aStep + 1); // then remaining steps |
|
} |
|
} else { |
|
value += step * Decimal(aStep); |
|
} |
|
|
|
// For date inputs, the value can hold a string that is not a day. We do not |
|
// want to round it, as it might result in a step mismatch. Instead we want to |
|
// clamp to the next valid value. |
|
if (mType == NS_FORM_INPUT_DATE && |
|
NS_floorModulo(Decimal(value - GetStepBase()), GetStepScaleFactor()) != Decimal(0)) { |
|
MOZ_ASSERT(GetStep() > Decimal(0)); |
|
Decimal validStep = EuclidLCM<Decimal>(GetStep().floor(), |
|
GetStepScaleFactor().floor()); |
|
if (aStep > 0) { |
|
value -= NS_floorModulo(value - GetStepBase(), validStep); |
|
value += validStep; |
|
} else if (aStep < 0) { |
|
value -= NS_floorModulo(value - GetStepBase(), validStep); |
|
} |
|
} |
|
|
|
if (value < minimum) { |
|
value = minimum; |
|
deltaFromStep = NS_floorModulo(value - stepBase, step); |
|
if (deltaFromStep != Decimal(0)) { |
|
value += step - deltaFromStep; |
|
} |
|
} |
|
if (value > maximum) { |
|
value = maximum; |
|
deltaFromStep = NS_floorModulo(value - stepBase, step); |
|
if (deltaFromStep != Decimal(0)) { |
|
value -= deltaFromStep; |
|
} |
|
} |
|
|
|
if (!valueWasNaN && // value="", resulting in us using "0" |
|
((aStep > 0 && value < valueBeforeStepping) || |
|
(aStep < 0 && value > valueBeforeStepping))) { |
|
// We don't want step-up to effectively step down, or step-down to |
|
// effectively step up, so return; |
|
return NS_OK; |
|
} |
|
|
|
*aNextStep = value; |
|
return NS_OK; |
|
} |
|
|
|
nsresult |
|
HTMLInputElement::ApplyStep(int32_t aStep) |
|
{ |
|
Decimal nextStep = Decimal::nan(); // unchanged if value will not change |
|
|
|
nsresult rv = GetValueIfStepped(aStep, CALLED_FOR_SCRIPT, &nextStep); |
|
|
|
if (NS_SUCCEEDED(rv) && nextStep.isFinite()) { |
|
SetValue(nextStep); |
|
} |
|
|
|
return rv; |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::StepDown(int32_t n, uint8_t optional_argc) |
|
{ |
|
return ApplyStep(optional_argc ? -n : -1); |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::StepUp(int32_t n, uint8_t optional_argc) |
|
{ |
|
return ApplyStep(optional_argc ? n : 1); |
|
} |
|
|
|
void |
|
HTMLInputElement::FlushFrames() |
|
{ |
|
if (GetComposedDoc()) { |
|
GetComposedDoc()->FlushPendingNotifications(Flush_Frames); |
|
} |
|
} |
|
|
|
void |
|
HTMLInputElement::MozGetFileNameArray(nsTArray<nsString>& aArray, |
|
ErrorResult& aRv) |
|
{ |
|
for (uint32_t i = 0; i < mFiles.Length(); i++) { |
|
nsString str; |
|
mFiles[i]->GetMozFullPathInternal(str, aRv); |
|
if (NS_WARN_IF(aRv.Failed())) { |
|
return; |
|
} |
|
|
|
aArray.AppendElement(str); |
|
} |
|
} |
|
|
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::MozGetFileNameArray(uint32_t* aLength, char16_t*** aFileNames) |
|
{ |
|
if (!nsContentUtils::IsCallerChrome()) { |
|
// Since this function returns full paths it's important that normal pages |
|
// can't call it. |
|
return NS_ERROR_DOM_SECURITY_ERR; |
|
} |
|
|
|
ErrorResult rv; |
|
nsTArray<nsString> array; |
|
MozGetFileNameArray(array, rv); |
|
if (NS_WARN_IF(rv.Failed())) { |
|
return rv.StealNSResult(); |
|
} |
|
|
|
*aLength = array.Length(); |
|
char16_t** ret = |
|
static_cast<char16_t**>(NS_Alloc(*aLength * sizeof(char16_t*))); |
|
if (!ret) { |
|
return NS_ERROR_OUT_OF_MEMORY; |
|
} |
|
|
|
for (uint32_t i = 0; i < *aLength; ++i) { |
|
ret[i] = NS_strdup(array[i].get()); |
|
} |
|
|
|
*aFileNames = ret; |
|
|
|
return NS_OK; |
|
} |
|
|
|
void |
|
HTMLInputElement::MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles) |
|
{ |
|
nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject(); |
|
MOZ_ASSERT(global); |
|
if (!global) { |
|
return; |
|
} |
|
nsTArray<nsRefPtr<File>> files; |
|
for (uint32_t i = 0; i < aFiles.Length(); ++i) { |
|
nsRefPtr<File> file = File::Create(global, aFiles[i].get()->Impl()); |
|
MOZ_ASSERT(file); |
|
|
|
files.AppendElement(file); |
|
} |
|
SetFiles(files, true); |
|
} |
|
|
|
void |
|
HTMLInputElement::MozSetFileNameArray(const Sequence< nsString >& aFileNames, ErrorResult& aRv) |
|
{ |
|
if (XRE_IsContentProcess()) { |
|
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); |
|
return; |
|
} |
|
|
|
nsTArray<nsRefPtr<File>> files; |
|
for (uint32_t i = 0; i < aFileNames.Length(); ++i) { |
|
nsCOMPtr<nsIFile> file; |
|
|
|
if (StringBeginsWith(aFileNames[i], NS_LITERAL_STRING("file:"), |
|
nsASCIICaseInsensitiveStringComparator())) { |
|
// Converts the URL string into the corresponding nsIFile if possible |
|
// A local file will be created if the URL string begins with file:// |
|
NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames[i]), |
|
getter_AddRefs(file)); |
|
} |
|
|
|
if (!file) { |
|
// this is no "file://", try as local file |
|
NS_NewLocalFile(aFileNames[i], false, getter_AddRefs(file)); |
|
} |
|
|
|
if (file) { |
|
nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject(); |
|
nsRefPtr<File> domFile = File::CreateFromFile(global, file); |
|
files.AppendElement(domFile); |
|
} else { |
|
continue; // Not much we can do if the file doesn't exist |
|
} |
|
|
|
} |
|
|
|
SetFiles(files, true); |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames, uint32_t aLength) |
|
{ |
|
if (!nsContentUtils::IsCallerChrome()) { |
|
// setting the value of a "FILE" input widget requires chrome privilege |
|
return NS_ERROR_DOM_SECURITY_ERR; |
|
} |
|
|
|
Sequence<nsString> list; |
|
for (uint32_t i = 0; i < aLength; ++i) { |
|
if (!list.AppendElement(nsDependentString(aFileNames[i]), fallible)) { |
|
return NS_ERROR_OUT_OF_MEMORY; |
|
} |
|
} |
|
|
|
ErrorResult rv; |
|
MozSetFileNameArray(list, rv); |
|
return rv.StealNSResult(); |
|
} |
|
|
|
bool |
|
HTMLInputElement::MozIsTextField(bool aExcludePassword) |
|
{ |
|
// TODO: temporary until bug 773205 is fixed. |
|
if (IsExperimentalMobileType(mType)) { |
|
return false; |
|
} |
|
|
|
return IsSingleLineTextControl(aExcludePassword); |
|
} |
|
|
|
HTMLInputElement* |
|
HTMLInputElement::GetOwnerNumberControl() |
|
{ |
|
if (IsInNativeAnonymousSubtree() && |
|
mType == NS_FORM_INPUT_TEXT && |
|
GetParent() && GetParent()->GetParent()) { |
|
HTMLInputElement* grandparent = |
|
HTMLInputElement::FromContentOrNull(GetParent()->GetParent()); |
|
if (grandparent && grandparent->mType == NS_FORM_INPUT_NUMBER) { |
|
return grandparent; |
|
} |
|
} |
|
return nullptr; |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult) |
|
{ |
|
*aResult = MozIsTextField(aExcludePassword); |
|
return NS_OK; |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::SetUserInput(const nsAString& aValue) |
|
{ |
|
if (!nsContentUtils::IsCallerChrome()) { |
|
return NS_ERROR_DOM_SECURITY_ERR; |
|
} |
|
|
|
if (mType == NS_FORM_INPUT_FILE) |
|
{ |
|
Sequence<nsString> list; |
|
if (!list.AppendElement(aValue, fallible)) { |
|
return NS_ERROR_OUT_OF_MEMORY; |
|
} |
|
|
|
ErrorResult rv; |
|
MozSetFileNameArray(list, rv); |
|
return rv.StealNSResult(); |
|
} else { |
|
nsresult rv = SetValueInternal(aValue, true, true); |
|
NS_ENSURE_SUCCESS(rv, rv); |
|
} |
|
|
|
nsContentUtils::DispatchTrustedEvent(OwnerDoc(), |
|
static_cast<nsIDOMHTMLInputElement*>(this), |
|
NS_LITERAL_STRING("input"), true, |
|
true); |
|
|
|
// If this element is not currently focused, it won't receive a change event for this |
|
// update through the normal channels. So fire a change event immediately, instead. |
|
if (!ShouldBlur(this)) { |
|
FireChangeEventIfNeeded(); |
|
} |
|
|
|
return NS_OK; |
|
} |
|
|
|
nsIEditor* |
|
HTMLInputElement::GetEditor() |
|
{ |
|
nsTextEditorState* state = GetEditorState(); |
|
if (state) { |
|
return state->GetEditor(); |
|
} |
|
return nullptr; |
|
} |
|
|
|
NS_IMETHODIMP_(nsIEditor*) |
|
HTMLInputElement::GetTextEditor() |
|
{ |
|
return GetEditor(); |
|
} |
|
|
|
NS_IMETHODIMP_(nsISelectionController*) |
|
HTMLInputElement::GetSelectionController() |
|
{ |
|
nsTextEditorState* state = GetEditorState(); |
|
if (state) { |
|
return state->GetSelectionController(); |
|
} |
|
return nullptr; |
|
} |
|
|
|
nsFrameSelection* |
|
HTMLInputElement::GetConstFrameSelection() |
|
{ |
|
nsTextEditorState* state = GetEditorState(); |
|
if (state) { |
|
return state->GetConstFrameSelection(); |
|
} |
|
return nullptr; |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::BindToFrame(nsTextControlFrame* aFrame) |
|
{ |
|
nsTextEditorState* state = GetEditorState(); |
|
if (state) { |
|
return state->BindToFrame(aFrame); |
|
} |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
NS_IMETHODIMP_(void) |
|
HTMLInputElement::UnbindFromFrame(nsTextControlFrame* aFrame) |
|
{ |
|
nsTextEditorState* state = GetEditorState(); |
|
if (state && aFrame) { |
|
state->UnbindFromFrame(aFrame); |
|
} |
|
} |
|
|
|
NS_IMETHODIMP |
|
HTMLInputElement::CreateEditor() |
|
{ |
|
nsTextEditorState* state = GetEditorState(); |
|
if (state) { |
|
return state->PrepareEditor(); |
|
} |
|
return NS_ERROR_FAILURE; |
|
} |
|
|
|
NS_IMETHODIMP_(nsIContent*) |
|
HTMLInputElement::GetRootEditorNode() |
|
{ |
|
nsTextEditorState* state = GetEditorState(); |
|
if (state) { |
|
return state->GetRootNode(); |
|
} |
|
return nullptr; |
|
} |
|
|
|
NS_IMETHODIMP_(Element*) |
|
HTMLInputElement::CreatePlaceholderNode() |
|
{ |
|
nsTextEditorState* state = GetEditorState(); |
|
if (state) { |
|
NS_ENSURE_SUCCESS(state->CreatePlaceholderNode(), nullptr); |
|
return state->GetPlaceholderNode(); |
|
} |
|
return nullptr; |
|
} |
|
|
|
NS_IMETHODIMP_(Element*) |
|
HTMLInputElement::GetPlaceholderNode() |
|
{ |
|
nsTextEditorState* state = GetEditorState(); |
|
if (state) { |
|
return state->GetPlaceholderNode(); |
|
} |
|
return nullptr; |
|
} |
|
|
|
NS_IMETHODIMP_(void) |
|
HTMLInputElement::UpdatePlaceholderVisibility(bool aNotify) |
|
{ |
|
nsTextEditorState* state = GetEditorState(); |
|
if (state) { |
|
state->UpdatePlaceholderVisibility(aNotify); |
|
} |
|
} |
|
|
|
NS_IMETHODIMP_(bool) |
|
HTMLInputElement::GetPlaceholderVisibility() |
|
{ |
|
nsTextEditorState* state = GetEditorState(); |
|
if (!state) { |
|
return false; |
|
} |
|
|
|
return state->GetPlaceholderVisibility(); |
|
} |
|
|
|
void |
|
HTMLInputElement::GetDisplayFileName(nsAString& aValue) const |
|
{ |
|
if (OwnerDoc()->IsStaticDocument()) { |
|
aValue = mStaticDocFileList; |
|
return; |
|
} |
|
|
|
if (mFiles.Length() == 1) { |
|
mFiles[0]->GetName(aValue); |
|
return; |
|
} |
|
|
|
nsXPIDLString value; |
|
|
|
if (mFiles.IsEmpty()) { |
|
if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) { |
|
nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES, |
|
"NoFilesSelected", value); |
|
} else { |
|
nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES, |
|
"NoFileSelected", value); |
|
} |
|
} else { |
|
nsString count; |
|
count.AppendInt(int(mFiles.Length())); |
|
|
|
const char16_t* params[] = { count.get() }; |
|
nsContentUtils::FormatLocalizedString(nsContentUtils::eFORMS_PROPERTIES, |
|
"XFilesSelected", params, value); |
|
} |
|
|
|
aValue = value; |
|
} |
|
|
|
void |
|
HTMLInputElement::SetFiles(const nsTArray<nsRefPtr<File>>& aFiles, |
|
bool aSetValueChanged) |
|
{ |
|
mFiles.Clear(); |
|
mFiles.AppendElements(aFiles); |
|
|
|
AfterSetFiles(aSetValueChanged); |
|
} |
|
|
|
void |
|
HTMLInputElement::SetFiles(nsIDOMFileList* aFiles, |
|
bool aSetValueChanged) |
|
{ |
|
nsRefPtr<FileList> files = static_cast<FileList*>(aFiles); |
|
mFiles.Clear(); |
|
|
|
if (aFiles) { |
|
uint32_t listLength; |
|
aFiles->GetLength(&listLength); |
|
for (uint32_t i = 0; i < listLength; i++) { |
|
nsRefPtr<File> file = files->Item(i); |
|
mFiles.AppendElement(file); |
|
} |
|
} |
|
|
|
AfterSetFiles(aSetValueChanged); |
|
} |
|
|
|
void |
|
HTMLInputElement::AfterSetFiles(bool aSetValueChanged) |
|
{ |
|
// No need to flush here, if there's no frame at this point we |
|
// don't need to force creation of one just to tell it about this |
|
// new value. We just want the display to update as needed. |
|
nsIFormControlFrame* formControlFrame = GetFormControlFrame(false); |
|
if (formControlFrame) { |
|
nsAutoString readableValue; |
|
GetDisplayFileName(readableValue); |
|
formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue); |
|
} |
|
|
|
#ifndef MOZ_CHILD_PERMISSIONS |
|
// Grab the full path here for any chrome callers who access our .value via a |
|
// CPOW. This path won't be called from a CPOW meaning the potential sync IPC |
|
// call under GetMozFullPath won't be rejected for not being urgent. |
|
// XXX Protected by the ifndef because the blob code doesn't allow us to send |
|
// this message in b2g. |
|
if (mFiles.IsEmpty()) { |
|
mFirstFilePath.Truncate(); |
|
} else { |
|
ErrorResult rv; |
|
mFiles[0]->GetMozFullPath(mFirstFilePath, rv); |
|
if (NS_WARN_IF(rv.Failed())) { |
|
rv.SuppressException(); |
|
} |
|
} |
|
#endif |
|
|
|
UpdateFileList(); |
|
|
|
if (aSetValueChanged) { |
|
SetValueChanged(true); |
|
} |
|
|
|
UpdateAllValidityStates(true); |
|
} |
|
|
|
void |
|
HTMLInputElement::FireChangeEventIfNeeded() |
|
{ |
|
nsAutoString value; |
|
GetValue(value); |
|
|
|
if (!MayFireChangeOnBlur() || mFocusedValue.Equals(value)) { |
|
return; |
|
} |
|
|
|
// Dispatch the change event. |
|
mFocusedValue = value; |
|
nsContentUtils::DispatchTrustedEvent(OwnerDoc(), |
|
static_cast<nsIContent*>(this), |
|
NS_LITERAL_STRING("change"), true, |
|
false); |
|
} |
|
|
|
FileList* |
|
HTMLInputElement::GetFiles() |
|
{ |
|
if (mType != NS_FORM_INPUT_FILE) { |
|
return nullptr; |
|
} |
|
|
|
if (!mFileList) { |
|
mFileList = new FileList(static_cast<nsIContent*>(this)); |
|
UpdateFileList(); |
|
} |
|
|
|
return mFileList; |
|
} |
|
|
|
/* static */ void |
|
HTMLInputElement::HandleNumberControlSpin(void* aData) |
|
{ |
|
HTMLInputElement* input = static_cast<HTMLInputElement*>(aData); |
|
|
|
NS_ASSERTION(input->mNumberControlSpinnerIsSpinning, |
|
"Should have called nsRepeatService::Stop()"); |
|
|
|
nsNumberControlFrame* numberControlFrame = |
|
do_QueryFrame(input->GetPrimaryFrame()); |
|
if (input->mType != NS_FORM_INPUT_NUMBER || !numberControlFrame) { |
|
// Type has changed (and possibly our frame type hasn't been updated yet) |
|
// or else we've lost our frame. Either way, stop the timer and don't do |
|
// anything else. |
|
input->StopNumberControlSpinnerSpin(); |
|
} else { |
|
input->StepNumberControlForUserEvent(input->mNumberControlSpinnerSpinsUp ? 1 : -1); |
|
} |
|
} |
|
|
|
nsresult |
|
HTMLInputElement::UpdateFileList() |
|
{ |
|
if (mFileList) { |
|
mFileList->Clear(); |
|
|
|
const nsTArray<nsRefPtr<File>>& files = GetFilesInternal(); |
|
for (uint32_t i = 0; i < files.Length(); ++i) { |
|
if (!mFileList->Append(files[i])) { |
|
return NS_ERROR_FAILURE; |
|
} |
|
} |
|
} |
|
|
|
return NS_OK; |
|
} |
|
|
|
nsresult |
|
HTMLInputElement::SetValueInternal(const nsAString& aValue, |
|
bool aUserInput, |
|
bool aSetValueChanged) |
|
{ |
|
NS_PRECONDITION(GetValueMode() != VALUE_MODE_FILENAME, |
|
"Don't call SetValueInternal for file inputs"); |
|
|
|
switch (GetValueMode()) { |
|
case VALUE_MODE_VALUE: |
|
{ |
|
// At the moment, only single line text control have to sanitize their value |
|
// Because we have to create a new string for that, we should prevent doing |
|
// it if it |