1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsFileControlFrame.h"
8 #include "nsIContent.h"
11 #include "nsPresContext.h"
12 #include "nsGkAtoms.h"
13 #include "nsWidgetsCID.h"
14 #include "nsIComponentManager.h"
15 #include "nsHTMLParts.h"
16 #include "nsIDOMHTMLInputElement.h"
17 #include "nsIFormControl.h"
18 #include "nsINameSpaceManager.h"
20 #include "nsIDOMElement.h"
21 #include "nsIDocument.h"
22 #include "nsIPresShell.h"
24 #include "nsISupportsPrimitives.h"
25 #include "nsPIDOMWindow.h"
26 #include "nsIFilePicker.h"
27 #include "nsIDOMMouseEvent.h"
28 #include "nsINodeInfo.h"
29 #include "nsIDOMEventTarget.h"
31 #include "nsHTMLInputElement.h"
32 #include "nsNodeInfoManager.h"
33 #include "nsContentCreatorFunctions.h"
34 #include "nsContentUtils.h"
35 #include "nsDisplayList.h"
36 #include "nsEventListenerManager.h"
38 #include "nsAccessibilityService.h"
41 #include "nsInterfaceHashtable.h"
42 #include "nsURIHashKey.h"
44 #include "nsWeakReference.h"
45 #include "nsIVariant.h"
46 #include "mozilla/Services.h"
47 #include "nsDirectoryServiceDefs.h"
48 #include "nsICapturePicker.h"
49 #include "nsIFileURL.h"
50 #include "nsDOMFile.h"
51 #include "nsEventStates.h"
52 #include "nsTextControlFrame.h"
54 #include "nsIDOMDOMStringList.h"
55 #include "nsIDOMDragEvent.h"
57 namespace dom
= mozilla::dom
;
60 #define SYNC_BUTTON 0x2
63 NS_NewFileControlFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
65 return new (aPresShell
) nsFileControlFrame(aContext
);
68 NS_IMPL_FRAMEARENA_HELPERS(nsFileControlFrame
)
70 nsFileControlFrame::nsFileControlFrame(nsStyleContext
* aContext
):
71 nsBlockFrame(aContext
)
73 AddStateBits(NS_BLOCK_FLOAT_MGR
);
78 nsFileControlFrame::Init(nsIContent
* aContent
,
80 nsIFrame
* aPrevInFlow
)
82 nsresult rv
= nsBlockFrame::Init(aContent
, aParent
, aPrevInFlow
);
83 NS_ENSURE_SUCCESS(rv
, rv
);
85 mMouseListener
= new BrowseMouseListener(this);
86 NS_ENSURE_TRUE(mMouseListener
, NS_ERROR_OUT_OF_MEMORY
);
87 mCaptureMouseListener
= new CaptureMouseListener(this);
88 NS_ENSURE_TRUE(mCaptureMouseListener
, NS_ERROR_OUT_OF_MEMORY
);
94 nsFileControlFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
96 ENSURE_TRUE(mContent
);
98 // Remove the drag events
100 mContent
->RemoveSystemEventListener(NS_LITERAL_STRING("drop"),
101 mMouseListener
, false);
102 mContent
->RemoveSystemEventListener(NS_LITERAL_STRING("dragover"),
103 mMouseListener
, false);
106 // remove mMouseListener as a mouse event listener (bug 40533, bug 355931)
107 nsContentUtils::DestroyAnonymousContent(&mCapture
);
110 mBrowse
->RemoveSystemEventListener(NS_LITERAL_STRING("click"),
111 mMouseListener
, false);
113 nsContentUtils::DestroyAnonymousContent(&mBrowse
);
116 mTextContent
->RemoveSystemEventListener(NS_LITERAL_STRING("click"),
117 mMouseListener
, false);
119 nsContentUtils::DestroyAnonymousContent(&mTextContent
);
121 mCaptureMouseListener
->ForgetFrame();
122 mMouseListener
->ForgetFrame();
123 nsBlockFrame::DestroyFrom(aDestructRoot
);
127 nsFileControlFrame::CreateAnonymousContent(nsTArray
<ContentInfo
>& aElements
)
129 // Get the NodeInfoManager and tag necessary to create input elements
130 nsCOMPtr
<nsIDocument
> doc
= mContent
->GetDocument();
132 nsCOMPtr
<nsINodeInfo
> nodeInfo
;
133 nodeInfo
= doc
->NodeInfoManager()->GetNodeInfo(nsGkAtoms::input
, nullptr,
135 nsIDOMNode::ELEMENT_NODE
);
137 // Create the text content
138 NS_NewHTMLElement(getter_AddRefs(mTextContent
), nodeInfo
.forget(),
139 dom::NOT_FROM_PARSER
);
141 return NS_ERROR_OUT_OF_MEMORY
;
143 // Mark the element to be native anonymous before setting any attributes.
144 mTextContent
->SetNativeAnonymous();
146 mTextContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::type
,
147 NS_LITERAL_STRING("text"), false);
149 nsHTMLInputElement
* inputElement
=
150 nsHTMLInputElement::FromContent(mContent
);
151 NS_ASSERTION(inputElement
, "Why is our content not a <input>?");
153 // Initialize value when we create the content in case the value was set
154 // before we got here
156 inputElement
->GetDisplayFileName(value
);
158 nsCOMPtr
<nsIDOMHTMLInputElement
> textControl
= do_QueryInterface(mTextContent
);
159 NS_ASSERTION(textControl
, "Why is the <input> we created not a <input>?");
160 textControl
->SetValue(value
);
162 textControl
->SetTabIndex(-1);
163 textControl
->SetReadOnly(true);
165 if (!aElements
.AppendElement(mTextContent
))
166 return NS_ERROR_OUT_OF_MEMORY
;
168 // Register the whole frame as an event listener of drag events
169 mContent
->AddSystemEventListener(NS_LITERAL_STRING("drop"),
170 mMouseListener
, false);
171 mContent
->AddSystemEventListener(NS_LITERAL_STRING("dragover"),
172 mMouseListener
, false);
174 // Register as an event listener of the textbox
175 // to open file dialog on mouse click
176 mTextContent
->AddSystemEventListener(NS_LITERAL_STRING("click"),
177 mMouseListener
, false);
179 // Create the browse button
180 nodeInfo
= doc
->NodeInfoManager()->GetNodeInfo(nsGkAtoms::input
, nullptr,
182 nsIDOMNode::ELEMENT_NODE
);
183 NS_NewHTMLElement(getter_AddRefs(mBrowse
), nodeInfo
.forget(),
184 dom::NOT_FROM_PARSER
);
186 return NS_ERROR_OUT_OF_MEMORY
;
188 // Mark the element to be native anonymous before setting any attributes.
189 mBrowse
->SetNativeAnonymous();
191 mBrowse
->SetAttr(kNameSpaceID_None
, nsGkAtoms::type
,
192 NS_LITERAL_STRING("button"), false);
194 // Create the capture button
195 nsCOMPtr
<nsICapturePicker
> capturePicker
;
196 capturePicker
= do_GetService("@mozilla.org/capturepicker;1");
198 CaptureCallbackData data
;
199 data
.picker
= capturePicker
;
200 data
.mode
= GetCaptureMode(data
);
202 if (data
.mode
!= 0) {
203 mCaptureMouseListener
->mMode
= data
.mode
;
204 nodeInfo
= doc
->NodeInfoManager()->GetNodeInfo(nsGkAtoms::input
, nullptr,
206 nsIDOMNode::ELEMENT_NODE
);
207 NS_NewHTMLElement(getter_AddRefs(mCapture
), nodeInfo
.forget(),
208 dom::NOT_FROM_PARSER
);
210 return NS_ERROR_OUT_OF_MEMORY
;
212 // Mark the element to be native anonymous before setting any attributes.
213 mCapture
->SetNativeAnonymous();
215 mCapture
->SetAttr(kNameSpaceID_None
, nsGkAtoms::type
,
216 NS_LITERAL_STRING("button"), false);
218 mCapture
->SetAttr(kNameSpaceID_None
, nsGkAtoms::value
,
219 NS_LITERAL_STRING("capture"), false);
221 mCapture
->AddSystemEventListener(NS_LITERAL_STRING("click"),
222 mCaptureMouseListener
, false);
225 nsCOMPtr
<nsIDOMHTMLInputElement
> fileContent
= do_QueryInterface(mContent
);
226 nsCOMPtr
<nsIDOMHTMLInputElement
> browseControl
= do_QueryInterface(mBrowse
);
227 if (fileContent
&& browseControl
) {
229 nsAutoString accessKey
;
231 fileContent
->GetAccessKey(accessKey
);
232 browseControl
->SetAccessKey(accessKey
);
233 fileContent
->GetTabIndex(&tabIndex
);
234 browseControl
->SetTabIndex(tabIndex
);
237 if (!aElements
.AppendElement(mBrowse
))
238 return NS_ERROR_OUT_OF_MEMORY
;
240 if (mCapture
&& !aElements
.AppendElement(mCapture
))
241 return NS_ERROR_OUT_OF_MEMORY
;
243 // Register as an event listener of the button
244 // to open file dialog on mouse click
245 mBrowse
->AddSystemEventListener(NS_LITERAL_STRING("click"),
246 mMouseListener
, false);
248 SyncAttr(kNameSpaceID_None
, nsGkAtoms::size
, SYNC_TEXT
);
255 nsFileControlFrame::AppendAnonymousContentTo(nsBaseContentList
& aElements
,
258 aElements
.MaybeAppendElement(mTextContent
);
259 aElements
.MaybeAppendElement(mBrowse
);
260 aElements
.MaybeAppendElement(mCapture
);
263 NS_QUERYFRAME_HEAD(nsFileControlFrame
)
264 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator
)
265 NS_QUERYFRAME_ENTRY(nsIFormControlFrame
)
266 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame
)
269 nsFileControlFrame::SetFocus(bool aOn
, bool aRepaint
)
273 bool ShouldProcessMouseClick(nsIDOMEvent
* aMouseEvent
)
275 // only allow the left button
276 nsCOMPtr
<nsIDOMMouseEvent
> mouseEvent
= do_QueryInterface(aMouseEvent
);
277 NS_ENSURE_TRUE(mouseEvent
, false);
278 bool defaultPrevented
= false;
279 aMouseEvent
->GetPreventDefault(&defaultPrevented
);
280 if (defaultPrevented
) {
284 uint16_t whichButton
;
285 if (NS_FAILED(mouseEvent
->GetButton(&whichButton
)) || whichButton
!= 0) {
290 if (NS_FAILED(mouseEvent
->GetDetail(&clickCount
)) || clickCount
> 1) {
298 * This is called when our capture button is clicked
301 nsFileControlFrame::CaptureMouseListener::HandleEvent(nsIDOMEvent
* aMouseEvent
)
305 NS_ASSERTION(mFrame
, "We should have been unregistered");
306 if (!ShouldProcessMouseClick(aMouseEvent
))
309 // Get parent nsPIDOMWindow object.
310 nsIContent
* content
= mFrame
->GetContent();
312 return NS_ERROR_FAILURE
;
314 nsHTMLInputElement
* inputElement
= nsHTMLInputElement::FromContent(content
);
316 return NS_ERROR_FAILURE
;
318 nsCOMPtr
<nsIDocument
> doc
= content
->GetDocument();
320 return NS_ERROR_FAILURE
;
324 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
325 "MediaUpload", title
);
327 nsPIDOMWindow
* win
= doc
->GetWindow();
329 return NS_ERROR_FAILURE
;
332 nsCOMPtr
<nsICapturePicker
> capturePicker
;
333 capturePicker
= do_CreateInstance("@mozilla.org/capturepicker;1");
335 return NS_ERROR_FAILURE
;
337 rv
= capturePicker
->Init(win
, title
, mMode
);
338 NS_ENSURE_SUCCESS(rv
, rv
);
342 rv
= capturePicker
->Show(&result
);
343 NS_ENSURE_SUCCESS(rv
, rv
);
344 if (result
== nsICapturePicker::RETURN_CANCEL
)
348 // The frame got destroyed while the filepicker was up. Don't do
350 // (This listener itself can't be destroyed because the event listener
351 // manager holds a strong reference to us while it fires the event.)
355 nsCOMPtr
<nsIDOMFile
> domFile
;
356 rv
= capturePicker
->GetFile(getter_AddRefs(domFile
));
357 NS_ENSURE_SUCCESS(rv
, rv
);
359 nsCOMArray
<nsIDOMFile
> newFiles
;
361 newFiles
.AppendObject(domFile
);
363 return NS_ERROR_FAILURE
;
366 // XXXkhuey we really should have a better UI story than the tired old
367 // uneditable text box with the file name inside.
368 // Set new selected files
369 if (newFiles
.Count()) {
370 // Tell our input element that this update of the value is a user
371 // initiated change. Otherwise it'll think that the value is being set by
372 // a script and not fire onchange when it should.
374 inputElement
->SetFiles(newFiles
, true);
376 // Should fire a change event here since the SetFiles() call above ensures
377 // a different value from the mFocusedValue of the inputElement.
378 inputElement
->FireChangeEventIfNeeded();
385 * This is called when we receive any registered events on the control.
386 * We've only registered for drop, dragover and click events.
389 nsFileControlFrame::BrowseMouseListener::HandleEvent(nsIDOMEvent
* aEvent
)
391 NS_ASSERTION(mFrame
, "We should have been unregistered");
393 nsAutoString eventType
;
394 aEvent
->GetType(eventType
);
395 if (eventType
.EqualsLiteral("click")) {
396 if (!ShouldProcessMouseClick(aEvent
))
399 nsHTMLInputElement
* input
=
400 nsHTMLInputElement::FromContent(mFrame
->GetContent());
401 return input
? input
->FireAsyncClickHandler() : NS_OK
;
404 bool defaultPrevented
= false;
405 aEvent
->GetPreventDefault(&defaultPrevented
);
406 if (defaultPrevented
) {
410 nsCOMPtr
<nsIDOMDragEvent
> dragEvent
= do_QueryInterface(aEvent
);
411 if (!dragEvent
|| !IsValidDropData(dragEvent
)) {
415 if (eventType
.EqualsLiteral("dragover")) {
416 // Prevent default if we can accept this drag data
417 aEvent
->PreventDefault();
421 if (eventType
.EqualsLiteral("drop")) {
422 aEvent
->StopPropagation();
423 aEvent
->PreventDefault();
425 nsIContent
* content
= mFrame
->GetContent();
426 NS_ASSERTION(content
, "The frame has no content???");
428 nsHTMLInputElement
* inputElement
= nsHTMLInputElement::FromContent(content
);
429 NS_ASSERTION(inputElement
, "No input element for this file upload control frame!");
431 nsCOMPtr
<nsIDOMDataTransfer
> dataTransfer
;
432 dragEvent
->GetDataTransfer(getter_AddRefs(dataTransfer
));
434 nsCOMPtr
<nsIDOMFileList
> fileList
;
435 dataTransfer
->GetFiles(getter_AddRefs(fileList
));
438 inputElement
->SetFiles(fileList
, true);
439 inputElement
->FireChangeEventIfNeeded();
446 nsFileControlFrame::BrowseMouseListener::IsValidDropData(nsIDOMDragEvent
* aEvent
)
448 nsCOMPtr
<nsIDOMDataTransfer
> dataTransfer
;
449 aEvent
->GetDataTransfer(getter_AddRefs(dataTransfer
));
450 NS_ENSURE_TRUE(dataTransfer
, false);
452 nsCOMPtr
<nsIDOMDOMStringList
> types
;
453 dataTransfer
->GetTypes(getter_AddRefs(types
));
454 NS_ENSURE_TRUE(types
, false);
456 // We only support dropping files onto a file upload control
458 types
->Contains(NS_LITERAL_STRING("Files"), &typeSupported
);
459 return typeSupported
;
463 nsFileControlFrame::GetMinWidth(nsRenderingContext
*aRenderingContext
)
466 DISPLAY_MIN_WIDTH(this, result
);
468 // Our min width is our pref width
469 result
= GetPrefWidth(aRenderingContext
);
474 nsFileControlFrame::GetTextControlFrame()
476 nsITextControlFrame
* tc
= do_QueryFrame(mTextContent
->GetPrimaryFrame());
477 return static_cast<nsTextControlFrame
*>(tc
);
481 nsFileControlFrame::GetSkipSides() const
487 nsFileControlFrame::SyncAttr(int32_t aNameSpaceID
, nsIAtom
* aAttribute
,
488 int32_t aWhichControls
)
491 if (mContent
->GetAttr(aNameSpaceID
, aAttribute
, value
)) {
492 if (aWhichControls
& SYNC_TEXT
&& mTextContent
) {
493 mTextContent
->SetAttr(aNameSpaceID
, aAttribute
, value
, true);
495 if (aWhichControls
& SYNC_BUTTON
&& mBrowse
) {
496 mBrowse
->SetAttr(aNameSpaceID
, aAttribute
, value
, true);
499 if (aWhichControls
& SYNC_TEXT
&& mTextContent
) {
500 mTextContent
->UnsetAttr(aNameSpaceID
, aAttribute
, true);
502 if (aWhichControls
& SYNC_BUTTON
&& mBrowse
) {
503 mBrowse
->UnsetAttr(aNameSpaceID
, aAttribute
, true);
509 nsFileControlFrame::SyncDisabledState()
511 nsEventStates eventStates
= mContent
->AsElement()->State();
512 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
513 mTextContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, EmptyString(),
515 mBrowse
->SetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, EmptyString(),
518 mTextContent
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, true);
519 mBrowse
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, true);
524 nsFileControlFrame::AttributeChanged(int32_t aNameSpaceID
,
528 if (aNameSpaceID
== kNameSpaceID_None
) {
529 if (aAttribute
== nsGkAtoms::size
) {
530 SyncAttr(aNameSpaceID
, aAttribute
, SYNC_TEXT
);
531 } else if (aAttribute
== nsGkAtoms::tabindex
) {
532 SyncAttr(aNameSpaceID
, aAttribute
, SYNC_BUTTON
);
536 return nsBlockFrame::AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
540 nsFileControlFrame::ContentStatesChanged(nsEventStates aStates
)
542 if (aStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
543 nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
548 nsFileControlFrame::IsLeaf() const
555 nsFileControlFrame::GetFrameName(nsAString
& aResult
) const
557 return MakeFrameName(NS_LITERAL_STRING("FileControl"), aResult
);
562 nsFileControlFrame::SetFormProperty(nsIAtom
* aName
,
563 const nsAString
& aValue
)
565 if (nsGkAtoms::value
== aName
) {
566 nsCOMPtr
<nsIDOMHTMLInputElement
> textControl
=
567 do_QueryInterface(mTextContent
);
568 NS_ASSERTION(textControl
,
569 "The text control should exist and be an input element");
570 textControl
->SetValue(aValue
);
576 nsFileControlFrame::GetFormProperty(nsIAtom
* aName
, nsAString
& aValue
) const
578 aValue
.Truncate(); // initialize out param
580 if (nsGkAtoms::value
== aName
) {
581 nsHTMLInputElement
* inputElement
=
582 nsHTMLInputElement::FromContent(mContent
);
585 inputElement
->GetDisplayFileName(aValue
);
592 nsFileControlFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
593 const nsRect
& aDirtyRect
,
594 const nsDisplayListSet
& aLists
)
597 if (GetStyleBorder()->mBoxShadow
) {
598 nsresult rv
= aLists
.BorderBackground()->AppendNewToTop(new (aBuilder
)
599 nsDisplayBoxShadowOuter(aBuilder
, this));
600 NS_ENSURE_SUCCESS(rv
, rv
);
603 // Our background is inherited to the text input, and we don't really want to
604 // paint it or out padding and borders (which we never have anyway, per
605 // styles in forms.css) -- doing it just makes us look ugly in some cases and
606 // has no effect in others.
607 nsDisplayListCollection tempList
;
608 nsresult rv
= nsBlockFrame::BuildDisplayList(aBuilder
, aDirtyRect
, tempList
);
612 tempList
.BorderBackground()->DeleteAll();
615 nsRect
clipRect(aBuilder
->ToReferenceFrame(this), GetSize());
616 clipRect
.width
= GetVisualOverflowRect().XMost();
617 nscoord radii
[8] = {0, 0, 0, 0, 0, 0, 0, 0};
618 rv
= OverflowClip(aBuilder
, tempList
, aLists
, clipRect
, radii
);
619 NS_ENSURE_SUCCESS(rv
, rv
);
621 // Disabled file controls don't pass mouse events to their children, so we
622 // put an invisible item in the display list above the children
623 // just to catch events
624 nsEventStates eventStates
= mContent
->AsElement()->State();
625 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
) && IsVisibleForPainting(aBuilder
)) {
626 rv
= aLists
.Content()->AppendNewToTop(
627 new (aBuilder
) nsDisplayEventReceiver(aBuilder
, this));
632 return DisplaySelectionOverlay(aBuilder
, aLists
.Content());
636 already_AddRefed
<Accessible
>
637 nsFileControlFrame::CreateAccessible()
639 // Accessible object exists just to hold onto its children, for later shutdown
640 nsAccessibilityService
* accService
= nsIPresShell::AccService();
644 return accService
->CreateHTMLFileInputAccessible(mContent
,
645 PresContext()->PresShell());
650 nsFileControlFrame::GetCaptureMode(const CaptureCallbackData
& aData
)
652 int32_t filters
= nsHTMLInputElement::FromContent(mContent
)->GetFilterFromAccept();
656 if (filters
== nsIFilePicker::filterImages
) {
657 rv
= aData
.picker
->ModeMayBeAvailable(nsICapturePicker::MODE_STILL
,
659 NS_ENSURE_SUCCESS(rv
, 0);
660 if (captureEnabled
) {
661 return nsICapturePicker::MODE_STILL
;
666 if (filters
== nsIFilePicker::filterAudio
) {
667 rv
= aData
.picker
->ModeMayBeAvailable(nsICapturePicker::MODE_AUDIO_CLIP
,
669 NS_ENSURE_SUCCESS(rv
, 0);
670 if (captureEnabled
) {
671 return nsICapturePicker::MODE_AUDIO_CLIP
;
676 if (filters
== nsIFilePicker::filterVideo
) {
677 rv
= aData
.picker
->ModeMayBeAvailable(nsICapturePicker::MODE_VIDEO_CLIP
,
679 NS_ENSURE_SUCCESS(rv
, 0);
680 if (captureEnabled
) {
681 return nsICapturePicker::MODE_VIDEO_CLIP
;
683 rv
= aData
.picker
->ModeMayBeAvailable(nsICapturePicker::MODE_VIDEO_NO_SOUND_CLIP
,
685 NS_ENSURE_SUCCESS(rv
, 0);
686 if (captureEnabled
) {
687 return nsICapturePicker::MODE_VIDEO_NO_SOUND_CLIP
;
694 ////////////////////////////////////////////////////////////
695 // Mouse listener implementation
697 NS_IMPL_ISUPPORTS1(nsFileControlFrame::MouseListener
,