--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -19,16 +19,17 @@
#include "nsIControllers.h"
#include "nsIStringBundle.h"
#include "nsFocusManager.h"
#include "nsPIDOMWindow.h"
#include "nsContentCID.h"
#include "nsIComponentManager.h"
#include "nsIDOMHTMLFormElement.h"
+#include "nsIDOMProgressEvent.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"
@@ -209,16 +210,19 @@ const Decimal HTMLInputElement::kStepAny
#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 MOZ_FINAL : public nsISupports
{
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID)
NS_DECL_ISUPPORTS
bool IsCheckedSet() {
return mCheckedSet;
@@ -478,28 +482,30 @@ public:
new DirPickerRecursiveFileEnumerator(mTopDir);
bool hasMore = true;
nsCOMPtr<nsISupports> tmp;
while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
iter->GetNext(getter_AddRefs(tmp));
nsCOMPtr<nsIDOMFile> domFile = do_QueryInterface(tmp);
MOZ_ASSERT(domFile);
mFileList.AppendElement(domFile);
+ mInput->SetFileListProgress(mFileList.Length());
}
return NS_DispatchToMainThread(this);
}
// Now back on the main thread, set the list on our HTMLInputElement:
if (mFileList.IsEmpty()) {
return NS_OK;
}
// 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(mFileList, true);
+ mInput->MaybeDispatchProgressEvent(true); // last progress event
nsresult rv =
nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
NS_LITERAL_STRING("change"), true,
false);
// Clear mInput to make sure that it can't lose its last strong ref off the
// main thread (which may happen if our dtor runs off the main thread)!
mInput = nullptr;
@@ -562,16 +568,19 @@ HTMLInputElement::nsFilePickerShownCallb
HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
mInput->OwnerDoc(), pickedDir);
nsCOMPtr<nsIEventTarget> target
= do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
NS_ASSERTION(target, "Must have stream transport service");
+ mInput->ResetProgressCounters();
+ mInput->StartProgressEventTimer();
+
// DirPickerBuildFileListTask takes care of calling SetFiles() and
// dispatching the "change" event.
nsRefPtr<DirPickerBuildFileListTask> event =
new DirPickerBuildFileListTask(mInput.get(), pickedDir.get());
return target->Dispatch(event, NS_DISPATCH_NORMAL);
}
// Collect new selected filenames
@@ -987,32 +996,35 @@ static nsresult FireEventForAccessibilit
//
// construction, destruction
//
HTMLInputElement::HTMLInputElement(already_AddRefed<nsINodeInfo> aNodeInfo,
FromParser aFromParser)
: nsGenericHTMLFormElementWithState(aNodeInfo)
+ , mFileListProgress(0)
+ , mLastFileListProgress(0)
, mType(kInputDefaultType->value)
, 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)
+ , mProgressTimerIsActive(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
@@ -1091,24 +1103,25 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
//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_INHERITED8(HTMLInputElement,
+ NS_INTERFACE_TABLE_INHERITED9(HTMLInputElement,
nsIDOMHTMLInputElement,
nsITextControlElement,
nsIPhonetic,
imgINotificationObserver,
nsIImageLoadingContent,
imgIOnloadBlocker,
nsIDOMNSEditableElement,
+ nsITimerCallback,
nsIConstraintValidation)
NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState)
// nsIConstraintValidation
NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLInputElement)
// nsIDOMNode
@@ -2426,16 +2439,103 @@ void
HTMLInputElement::OpenDirectoryPicker(ErrorResult& aRv)
{
if (mType != NS_FORM_INPUT_FILE) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
}
InitFilePicker(FILE_PICKER_DIRECTORY);
}
+void
+HTMLInputElement::StartProgressEventTimer()
+{
+ if (!mProgressTimer) {
+ mProgressTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+ }
+ if (mProgressTimer) {
+ mProgressTimerIsActive = true;
+ mProgressTimer->Cancel();
+ mProgressTimer->InitWithCallback(this, kProgressEventInterval,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+}
+
+// nsITimerCallback's only method
+NS_IMETHODIMP
+HTMLInputElement::Notify(nsITimer* aTimer)
+{
+ if (mProgressTimer == aTimer) {
+ mProgressTimerIsActive = false;
+ MaybeDispatchProgressEvent(false);
+ return NS_OK;
+ }
+
+ // Just in case some JS user wants to QI to nsITimerCallback and play with us...
+ NS_WARNING("Unexpected timer!");
+ return NS_ERROR_INVALID_POINTER;
+}
+
+void
+HTMLInputElement::MaybeDispatchProgressEvent(bool aFinalProgress)
+{
+ nsRefPtr<HTMLInputElement> kungFuDeathGrip;
+
+ if (aFinalProgress && mProgressTimerIsActive) {
+ // mProgressTimer may hold the last reference to us, so take another strong
+ // ref to make sure we don't die under Cancel() and leave this method
+ // running on deleted memory.
+ kungFuDeathGrip = this;
+
+ mProgressTimerIsActive = false;
+ mProgressTimer->Cancel();
+ }
+
+ if (mProgressTimerIsActive ||
+ mFileListProgress == mLastFileListProgress) {
+ return;
+ }
+
+ if (!aFinalProgress) {
+ StartProgressEventTimer();
+ }
+
+ mLastFileListProgress = mFileListProgress;
+
+ DispatchProgressEvent(NS_LITERAL_STRING(PROGRESS_STR),
+ false, mLastFileListProgress,
+ 0);
+}
+
+void
+HTMLInputElement::DispatchProgressEvent(const nsAString& aType,
+ bool aLengthComputable,
+ uint64_t aLoaded, uint64_t aTotal)
+{
+ NS_ASSERTION(!aType.IsEmpty(), "missing event type");
+
+ nsCOMPtr<nsIDOMEvent> event;
+ nsresult rv = NS_NewDOMProgressEvent(getter_AddRefs(event), this,
+ nullptr, nullptr);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsCOMPtr<nsIDOMProgressEvent> progress = do_QueryInterface(event);
+ if (!progress) {
+ return;
+ }
+
+ progress->InitProgressEvent(aType, false, false, aLengthComputable,
+ aLoaded, (aTotal == UINT64_MAX) ? 0 : aTotal);
+
+ event->SetTrusted(true);
+
+ DispatchDOMEvent(nullptr, event, nullptr, nullptr);
+}
+
nsresult
HTMLInputElement::UpdateFileList()
{
if (mFileList) {
mFileList->Clear();
const nsTArray<nsCOMPtr<nsIDOMFile> >& files = GetFilesInternal();
for (uint32_t i = 0; i < files.Length(); ++i) {
--- a/content/html/content/src/HTMLInputElement.h
+++ b/content/html/content/src/HTMLInputElement.h
@@ -6,16 +6,17 @@
#ifndef mozilla_dom_HTMLInputElement_h
#define mozilla_dom_HTMLInputElement_h
#include "mozilla/Attributes.h"
#include "nsGenericHTMLElement.h"
#include "nsImageLoadingContent.h"
#include "nsIDOMHTMLInputElement.h"
#include "nsITextControlElement.h"
+#include "nsITimer.h"
#include "nsIPhonetic.h"
#include "nsIDOMNSEditableElement.h"
#include "nsCOMPtr.h"
#include "nsIConstraintValidation.h"
#include "mozilla/dom/HTMLFormElement.h" // for HasEverTriedInvalidSubmit()
#include "nsIFilePicker.h"
#include "nsIContentPrefService2.h"
#include "mozilla/Decimal.h"
@@ -77,16 +78,17 @@ public:
};
class HTMLInputElement MOZ_FINAL : public nsGenericHTMLFormElementWithState,
public nsImageLoadingContent,
public nsIDOMHTMLInputElement,
public nsITextControlElement,
public nsIPhonetic,
public nsIDOMNSEditableElement,
+ public nsITimerCallback,
public nsIConstraintValidation
{
public:
using nsIConstraintValidation::GetValidationMessage;
using nsIConstraintValidation::CheckValidity;
using nsIConstraintValidation::WillValidate;
using nsIConstraintValidation::Validity;
using nsGenericHTMLFormElementWithState::GetForm;
@@ -220,16 +222,24 @@ public:
static UploadLastDir* gUploadLastDir;
// create and destroy the static UploadLastDir object for remembering
// which directory was last used on a site-by-site basis
static void InitUploadLastDir();
static void DestroyUploadLastDir();
void MaybeLoadImage();
+ // nsITimerCallback
+ NS_DECL_NSITIMERCALLBACK
+
+ // Avoid warning about the implementation of nsITimerCallback::Notify hiding
+ // our nsImageLoadingContent base class' implementation of
+ // imgINotificationObserver::Notify:
+ using nsImageLoadingContent::Notify;
+
// nsIConstraintValidation
bool IsTooLong();
bool IsValueMissing() const;
bool HasTypeMismatch() const;
bool HasPatternMismatch() const;
bool IsRangeOverflow() const;
bool IsRangeUnderflow() const;
bool HasStepMismatch() const;
@@ -387,16 +397,27 @@ public:
}
// XPCOM GetForm() is OK
nsDOMFileList* GetFiles();
void OpenDirectoryPicker(ErrorResult& aRv);
+ void ResetProgressCounters()
+ {
+ mFileListProgress = 0;
+ mLastFileListProgress = 0;
+ }
+ void StartProgressEventTimer();
+ void MaybeDispatchProgressEvent(bool aFinalProgress);
+ void DispatchProgressEvent(const nsAString& aType,
+ bool aLengthComputable,
+ uint64_t aLoaded, uint64_t aTotal);
+
// XPCOM GetFormAction() is OK
void SetFormAction(const nsAString& aValue, ErrorResult& aRv)
{
SetHTMLAttr(nsGkAtoms::formaction, aValue, aRv);
}
// XPCOM GetFormEnctype() is OK
void SetFormEnctype(const nsAString& aValue, ErrorResult& aRv)
@@ -643,16 +664,23 @@ public:
bool MozIsTextField(bool aExcludePassword);
nsIEditor* GetEditor();
// XPCOM SetUserInput() is OK
// XPCOM GetPhonetic() is OK
+ void SetFileListProgress(uint32_t mFileCount)
+ {
+ MOZ_ASSERT(!NS_IsMainThread(),
+ "Why are we calling this on the main thread?");
+ mFileListProgress = mFileCount;
+ }
+
protected:
virtual JSObject* WrapNode(JSContext* aCx,
JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
// Pull IsSingleLineTextControl into our scope, otherwise it'd be hidden
// by the nsITextControlElement version.
using nsGenericHTMLFormElementWithState::IsSingleLineTextControl;
@@ -1139,32 +1167,52 @@ protected:
/**
* If mIsDraggingRange is true, this is the value that the input had before
* the drag started. Used to reset the input to its old value if the drag is
* canceled.
*/
Decimal mRangeThumbDragStartValue;
+ /**
+ * Timer that is used when mType == NS_FORM_INPUT_FILE and the user selects a
+ * directory. It is used to fire progress events while the list of files
+ * under that directory tree is built.
+ */
+ nsCOMPtr<nsITimer> mProgressTimer;
+
// Step scale factor values, for input types that have one.
static const Decimal kStepScaleFactorDate;
static const Decimal kStepScaleFactorNumberRange;
static const Decimal kStepScaleFactorTime;
// Default step base value when a type do not have specific one.
static const Decimal kDefaultStepBase;
// Default step used when there is no specified step.
static const Decimal kDefaultStep;
static const Decimal kDefaultStepTime;
// Float value returned by GetStep() when the step attribute is set to 'any'.
static const Decimal kStepAny;
/**
+ * The number of files added to the FileList being built off-main-thread when
+ * mType == NS_FORM_INPUT_FILE and the user selects a directory. This is set
+ * off the main thread, read on main thread.
+ */
+ mozilla::Atomic<uint32_t> mFileListProgress;
+
+ /**
+ * The number of files added to the FileList at the time the last progress
+ * event was fired.
+ */
+ uint32_t mLastFileListProgress;
+
+ /**
* The type of this input (<input type=...>) as an integer.
* @see nsIFormControl.h (specifically NS_FORM_INPUT_*)
*/
uint8_t mType;
bool mDisabledChanged : 1;
bool mValueChanged : 1;
bool mCheckedChanged : 1;
bool mChecked : 1;
@@ -1174,16 +1222,17 @@ protected:
bool mInInternalActivate : 1;
bool mCheckedIsToggled : 1;
bool mIndeterminate : 1;
bool mInhibitRestoration : 1;
bool mCanShowValidUI : 1;
bool mCanShowInvalidUI : 1;
bool mHasRange : 1;
bool mIsDraggingRange : 1;
+ bool mProgressTimerIsActive : 1;
private:
/**
* Returns true if this input's type will fire a DOM "change" event when it
* loses focus if its value has changed since it gained focus.
*/
bool MayFireChangeOnBlur() const {