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

/* -*- 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