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"
10 #include "nsIDocument.h"
11 #include "mozilla/dom/NodeInfo.h"
12 #include "mozilla/dom/Element.h"
13 #include "mozilla/dom/DataTransfer.h"
14 #include "mozilla/dom/HTMLButtonElement.h"
15 #include "mozilla/dom/HTMLInputElement.h"
16 #include "nsNodeInfoManager.h"
17 #include "nsContentCreatorFunctions.h"
18 #include "nsContentUtils.h"
19 #include "mozilla/EventStates.h"
20 #include "mozilla/dom/DOMStringList.h"
21 #include "nsIDOMDragEvent.h"
22 #include "nsIDOMFileList.h"
23 #include "nsContentList.h"
24 #include "nsIDOMMutationEvent.h"
25 #include "nsTextNode.h"
27 using namespace mozilla
;
28 using namespace mozilla::dom
;
31 NS_NewFileControlFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
33 return new (aPresShell
) nsFileControlFrame(aContext
);
36 NS_IMPL_FRAMEARENA_HELPERS(nsFileControlFrame
)
38 nsFileControlFrame::nsFileControlFrame(nsStyleContext
* aContext
)
39 : nsBlockFrame(aContext
)
41 AddStateBits(NS_BLOCK_FLOAT_MGR
);
46 nsFileControlFrame::Init(nsIContent
* aContent
,
47 nsContainerFrame
* aParent
,
48 nsIFrame
* aPrevInFlow
)
50 nsBlockFrame::Init(aContent
, aParent
, aPrevInFlow
);
52 mMouseListener
= new DnDListener(this);
56 nsFileControlFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
58 ENSURE_TRUE(mContent
);
62 mContent
->RemoveSystemEventListener(NS_LITERAL_STRING("drop"),
63 mMouseListener
, false);
64 mContent
->RemoveSystemEventListener(NS_LITERAL_STRING("dragover"),
65 mMouseListener
, false);
68 nsContentUtils::DestroyAnonymousContent(&mTextContent
);
69 nsContentUtils::DestroyAnonymousContent(&mBrowse
);
71 mMouseListener
->ForgetFrame();
72 nsBlockFrame::DestroyFrom(aDestructRoot
);
76 nsFileControlFrame::CreateAnonymousContent(nsTArray
<ContentInfo
>& aElements
)
78 nsCOMPtr
<nsIDocument
> doc
= mContent
->GetComposedDoc();
80 // Create and setup the file picking button.
81 mBrowse
= doc
->CreateHTMLElement(nsGkAtoms::button
);
82 // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
84 mBrowse
->SetIsNativeAnonymousRoot();
85 mBrowse
->SetAttr(kNameSpaceID_None
, nsGkAtoms::type
,
86 NS_LITERAL_STRING("button"), false);
88 // Set the file picking button text depending on the current locale.
89 nsXPIDLString buttonTxt
;
90 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
93 // Set the browse button text. It's a bit of a pain to do because we want to
94 // make sure we are not notifying.
95 nsRefPtr
<nsTextNode
> textContent
=
96 new nsTextNode(mBrowse
->NodeInfo()->NodeInfoManager());
98 textContent
->SetText(buttonTxt
, false);
100 nsresult rv
= mBrowse
->AppendChildTo(textContent
, false);
101 NS_ENSURE_SUCCESS(rv
, rv
);
103 // Make sure access key and tab order for the element actually redirect to the
104 // file picking button.
105 nsRefPtr
<HTMLInputElement
> fileContent
= HTMLInputElement::FromContentOrNull(mContent
);
106 nsRefPtr
<HTMLButtonElement
> browseControl
= HTMLButtonElement::FromContentOrNull(mBrowse
);
108 nsAutoString accessKey
;
109 fileContent
->GetAccessKey(accessKey
);
110 browseControl
->SetAccessKey(accessKey
);
113 fileContent
->GetTabIndex(&tabIndex
);
114 browseControl
->SetTabIndex(tabIndex
);
116 if (!aElements
.AppendElement(mBrowse
)) {
117 return NS_ERROR_OUT_OF_MEMORY
;
120 // Create and setup the text showing the selected files.
121 nsRefPtr
<NodeInfo
> nodeInfo
;
122 nodeInfo
= doc
->NodeInfoManager()->GetNodeInfo(nsGkAtoms::label
, nullptr,
124 nsIDOMNode::ELEMENT_NODE
);
125 NS_TrustedNewXULElement(getter_AddRefs(mTextContent
), nodeInfo
.forget());
126 // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
128 mTextContent
->SetIsNativeAnonymousRoot();
129 mTextContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::crop
,
130 NS_LITERAL_STRING("center"), false);
132 // Update the displayed text to reflect the current element's value.
134 HTMLInputElement::FromContent(mContent
)->GetDisplayFileName(value
);
135 UpdateDisplayedValue(value
, false);
137 if (!aElements
.AppendElement(mTextContent
)) {
138 return NS_ERROR_OUT_OF_MEMORY
;
141 // We should be able to interact with the element by doing drag and drop.
142 mContent
->AddSystemEventListener(NS_LITERAL_STRING("drop"),
143 mMouseListener
, false);
144 mContent
->AddSystemEventListener(NS_LITERAL_STRING("dragover"),
145 mMouseListener
, false);
153 nsFileControlFrame::AppendAnonymousContentTo(nsTArray
<nsIContent
*>& aElements
,
157 aElements
.AppendElement(mBrowse
);
161 aElements
.AppendElement(mTextContent
);
165 NS_QUERYFRAME_HEAD(nsFileControlFrame
)
166 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator
)
167 NS_QUERYFRAME_ENTRY(nsIFormControlFrame
)
168 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame
)
171 nsFileControlFrame::SetFocus(bool aOn
, bool aRepaint
)
176 * This is called when we receive a drop or a dragover.
179 nsFileControlFrame::DnDListener::HandleEvent(nsIDOMEvent
* aEvent
)
181 NS_ASSERTION(mFrame
, "We should have been unregistered");
183 bool defaultPrevented
= false;
184 aEvent
->GetDefaultPrevented(&defaultPrevented
);
185 if (defaultPrevented
) {
189 nsCOMPtr
<nsIDOMDragEvent
> dragEvent
= do_QueryInterface(aEvent
);
194 nsCOMPtr
<nsIDOMDataTransfer
> dataTransfer
;
195 dragEvent
->GetDataTransfer(getter_AddRefs(dataTransfer
));
196 if (!IsValidDropData(dataTransfer
)) {
201 nsIContent
* content
= mFrame
->GetContent();
202 bool supportsMultiple
= content
&& content
->HasAttr(kNameSpaceID_None
, nsGkAtoms::multiple
);
203 if (!CanDropTheseFiles(dataTransfer
, supportsMultiple
)) {
204 dataTransfer
->SetDropEffect(NS_LITERAL_STRING("none"));
205 aEvent
->StopPropagation();
209 nsAutoString eventType
;
210 aEvent
->GetType(eventType
);
211 if (eventType
.EqualsLiteral("dragover")) {
212 // Prevent default if we can accept this drag data
213 aEvent
->PreventDefault();
217 if (eventType
.EqualsLiteral("drop")) {
218 aEvent
->StopPropagation();
219 aEvent
->PreventDefault();
221 NS_ASSERTION(content
, "The frame has no content???");
223 HTMLInputElement
* inputElement
= HTMLInputElement::FromContent(content
);
224 NS_ASSERTION(inputElement
, "No input element for this file upload control frame!");
226 nsCOMPtr
<nsIDOMFileList
> fileList
;
227 dataTransfer
->GetFiles(getter_AddRefs(fileList
));
229 inputElement
->SetFiles(fileList
, true);
230 nsContentUtils::DispatchTrustedEvent(content
->OwnerDoc(), content
,
231 NS_LITERAL_STRING("change"), true,
239 nsFileControlFrame::DnDListener::IsValidDropData(nsIDOMDataTransfer
* aDOMDataTransfer
)
241 nsCOMPtr
<DataTransfer
> dataTransfer
= do_QueryInterface(aDOMDataTransfer
);
242 NS_ENSURE_TRUE(dataTransfer
, false);
244 // We only support dropping files onto a file upload control
245 nsRefPtr
<DOMStringList
> types
= dataTransfer
->Types();
246 return types
->Contains(NS_LITERAL_STRING("Files"));
250 nsFileControlFrame::DnDListener::CanDropTheseFiles(nsIDOMDataTransfer
* aDOMDataTransfer
,
251 bool aSupportsMultiple
)
253 nsCOMPtr
<DataTransfer
> dataTransfer
= do_QueryInterface(aDOMDataTransfer
);
254 NS_ENSURE_TRUE(dataTransfer
, false);
256 nsCOMPtr
<nsIDOMFileList
> fileList
;
257 dataTransfer
->GetFiles(getter_AddRefs(fileList
));
259 uint32_t listLength
= 0;
261 fileList
->GetLength(&listLength
);
263 return listLength
<= 1 || aSupportsMultiple
;
267 nsFileControlFrame::GetMinISize(nsRenderingContext
*aRenderingContext
)
270 DISPLAY_MIN_WIDTH(this, result
);
272 // Our min width is our pref width
273 result
= GetPrefISize(aRenderingContext
);
278 nsFileControlFrame::SyncDisabledState()
280 EventStates eventStates
= mContent
->AsElement()->State();
281 if (eventStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
282 mBrowse
->SetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, EmptyString(),
285 mBrowse
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::disabled
, true);
290 nsFileControlFrame::AttributeChanged(int32_t aNameSpaceID
,
294 if (aNameSpaceID
== kNameSpaceID_None
&& aAttribute
== nsGkAtoms::tabindex
) {
295 if (aModType
== nsIDOMMutationEvent::REMOVAL
) {
296 mBrowse
->UnsetAttr(aNameSpaceID
, aAttribute
, true);
299 mContent
->GetAttr(aNameSpaceID
, aAttribute
, value
);
300 mBrowse
->SetAttr(aNameSpaceID
, aAttribute
, value
, true);
304 return nsBlockFrame::AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
308 nsFileControlFrame::ContentStatesChanged(EventStates aStates
)
310 if (aStates
.HasState(NS_EVENT_STATE_DISABLED
)) {
311 nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
315 #ifdef DEBUG_FRAME_DUMP
317 nsFileControlFrame::GetFrameName(nsAString
& aResult
) const
319 return MakeFrameName(NS_LITERAL_STRING("FileControl"), aResult
);
324 nsFileControlFrame::UpdateDisplayedValue(const nsAString
& aValue
, bool aNotify
)
326 mTextContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::value
, aValue
, aNotify
);
330 nsFileControlFrame::SetFormProperty(nsIAtom
* aName
,
331 const nsAString
& aValue
)
333 if (nsGkAtoms::value
== aName
) {
334 UpdateDisplayedValue(aValue
, true);
340 nsFileControlFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
341 const nsRect
& aDirtyRect
,
342 const nsDisplayListSet
& aLists
)
344 BuildDisplayListForInline(aBuilder
, aDirtyRect
, aLists
);
349 nsFileControlFrame::AccessibleType()
351 return a11y::eHTMLFileInputType
;
355 ////////////////////////////////////////////////////////////
356 // Mouse listener implementation
358 NS_IMPL_ISUPPORTS(nsFileControlFrame::MouseListener
,