nsFileControlFrame.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  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/. */
  5. #include "nsFileControlFrame.h"
  6. #include "nsGkAtoms.h"
  7. #include "nsCOMPtr.h"
  8. #include "nsIDocument.h"
  9. #include "mozilla/dom/NodeInfo.h"
  10. #include "mozilla/dom/Element.h"
  11. #include "mozilla/dom/DataTransfer.h"
  12. #include "mozilla/dom/HTMLButtonElement.h"
  13. #include "mozilla/dom/HTMLInputElement.h"
  14. #include "mozilla/Preferences.h"
  15. #include "nsNodeInfoManager.h"
  16. #include "nsContentCreatorFunctions.h"
  17. #include "nsContentUtils.h"
  18. #include "mozilla/EventStates.h"
  19. #include "mozilla/dom/DOMStringList.h"
  20. #include "mozilla/dom/Directory.h"
  21. #include "mozilla/dom/FileList.h"
  22. #include "nsIDOMDragEvent.h"
  23. #include "nsIDOMFileList.h"
  24. #include "nsContentList.h"
  25. #include "nsIDOMMutationEvent.h"
  26. #include "nsTextNode.h"
  27. using namespace mozilla;
  28. using namespace mozilla::dom;
  29. nsIFrame*
  30. NS_NewFileControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
  31. {
  32. return new (aPresShell) nsFileControlFrame(aContext);
  33. }
  34. NS_IMPL_FRAMEARENA_HELPERS(nsFileControlFrame)
  35. nsFileControlFrame::nsFileControlFrame(nsStyleContext* aContext)
  36. : nsBlockFrame(aContext)
  37. {
  38. AddStateBits(NS_BLOCK_FLOAT_MGR);
  39. }
  40. void
  41. nsFileControlFrame::Init(nsIContent* aContent,
  42. nsContainerFrame* aParent,
  43. nsIFrame* aPrevInFlow)
  44. {
  45. nsBlockFrame::Init(aContent, aParent, aPrevInFlow);
  46. mMouseListener = new DnDListener(this);
  47. }
  48. void
  49. nsFileControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
  50. {
  51. ENSURE_TRUE(mContent);
  52. // Remove the events.
  53. if (mContent) {
  54. mContent->RemoveSystemEventListener(NS_LITERAL_STRING("drop"),
  55. mMouseListener, false);
  56. mContent->RemoveSystemEventListener(NS_LITERAL_STRING("dragover"),
  57. mMouseListener, false);
  58. }
  59. nsContentUtils::DestroyAnonymousContent(&mTextContent);
  60. nsContentUtils::DestroyAnonymousContent(&mBrowseFilesOrDirs);
  61. mMouseListener->ForgetFrame();
  62. nsBlockFrame::DestroyFrom(aDestructRoot);
  63. }
  64. static already_AddRefed<Element>
  65. MakeAnonButton(nsIDocument* aDoc, const char* labelKey,
  66. HTMLInputElement* aInputElement,
  67. const nsAString& aAccessKey)
  68. {
  69. RefPtr<Element> button = aDoc->CreateHTMLElement(nsGkAtoms::button);
  70. // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
  71. // attribute.
  72. button->SetIsNativeAnonymousRoot();
  73. button->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
  74. NS_LITERAL_STRING("button"), false);
  75. // Set the file picking button text depending on the current locale.
  76. nsXPIDLString buttonTxt;
  77. nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
  78. labelKey, buttonTxt);
  79. // Set the browse button text. It's a bit of a pain to do because we want to
  80. // make sure we are not notifying.
  81. RefPtr<nsTextNode> textContent =
  82. new nsTextNode(button->NodeInfo()->NodeInfoManager());
  83. textContent->SetText(buttonTxt, false);
  84. nsresult rv = button->AppendChildTo(textContent, false);
  85. if (NS_FAILED(rv)) {
  86. return nullptr;
  87. }
  88. // Make sure access key and tab order for the element actually redirect to the
  89. // file picking button.
  90. RefPtr<HTMLButtonElement> buttonElement =
  91. HTMLButtonElement::FromContentOrNull(button);
  92. if (!aAccessKey.IsEmpty()) {
  93. buttonElement->SetAccessKey(aAccessKey);
  94. }
  95. // Both elements are given the same tab index so that the user can tab
  96. // to the file control at the correct index, and then between the two
  97. // buttons.
  98. int32_t tabIndex;
  99. aInputElement->GetTabIndex(&tabIndex);
  100. buttonElement->SetTabIndex(tabIndex);
  101. return button.forget();
  102. }
  103. nsresult
  104. nsFileControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
  105. {
  106. nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
  107. RefPtr<HTMLInputElement> fileContent = HTMLInputElement::FromContentOrNull(mContent);
  108. // The access key is transferred to the "Choose files..." button only. In
  109. // effect that access key allows access to the control via that button, then
  110. // the user can tab between the two buttons.
  111. nsAutoString accessKey;
  112. fileContent->GetAccessKey(accessKey);
  113. mBrowseFilesOrDirs = MakeAnonButton(doc, "Browse", fileContent, accessKey);
  114. if (!mBrowseFilesOrDirs || !aElements.AppendElement(mBrowseFilesOrDirs)) {
  115. return NS_ERROR_OUT_OF_MEMORY;
  116. }
  117. // Create and setup the text showing the selected files.
  118. RefPtr<NodeInfo> nodeInfo;
  119. nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::label, nullptr,
  120. kNameSpaceID_XUL,
  121. nsIDOMNode::ELEMENT_NODE);
  122. NS_TrustedNewXULElement(getter_AddRefs(mTextContent), nodeInfo.forget());
  123. // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
  124. // attribute.
  125. mTextContent->SetIsNativeAnonymousRoot();
  126. mTextContent->SetAttr(kNameSpaceID_None, nsGkAtoms::crop,
  127. NS_LITERAL_STRING("center"), false);
  128. // Update the displayed text to reflect the current element's value.
  129. nsAutoString value;
  130. HTMLInputElement::FromContent(mContent)->GetDisplayFileName(value);
  131. UpdateDisplayedValue(value, false);
  132. if (!aElements.AppendElement(mTextContent)) {
  133. return NS_ERROR_OUT_OF_MEMORY;
  134. }
  135. // We should be able to interact with the element by doing drag and drop.
  136. mContent->AddSystemEventListener(NS_LITERAL_STRING("drop"),
  137. mMouseListener, false);
  138. mContent->AddSystemEventListener(NS_LITERAL_STRING("dragover"),
  139. mMouseListener, false);
  140. SyncDisabledState();
  141. return NS_OK;
  142. }
  143. void
  144. nsFileControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
  145. uint32_t aFilter)
  146. {
  147. if (mBrowseFilesOrDirs) {
  148. aElements.AppendElement(mBrowseFilesOrDirs);
  149. }
  150. if (mTextContent) {
  151. aElements.AppendElement(mTextContent);
  152. }
  153. }
  154. NS_QUERYFRAME_HEAD(nsFileControlFrame)
  155. NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
  156. NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
  157. NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
  158. void
  159. nsFileControlFrame::SetFocus(bool aOn, bool aRepaint)
  160. {
  161. }
  162. static void
  163. AppendBlobImplAsDirectory(nsTArray<OwningFileOrDirectory>& aArray,
  164. BlobImpl* aBlobImpl,
  165. nsIContent* aContent)
  166. {
  167. MOZ_ASSERT(aBlobImpl);
  168. MOZ_ASSERT(aBlobImpl->IsDirectory());
  169. nsAutoString fullpath;
  170. ErrorResult err;
  171. aBlobImpl->GetMozFullPath(fullpath, err);
  172. if (err.Failed()) {
  173. err.SuppressException();
  174. return;
  175. }
  176. nsCOMPtr<nsIFile> file;
  177. NS_ConvertUTF16toUTF8 path(fullpath);
  178. nsresult rv = NS_NewNativeLocalFile(path, true, getter_AddRefs(file));
  179. if (NS_WARN_IF(NS_FAILED(rv))) {
  180. return;
  181. }
  182. nsPIDOMWindowInner* inner = aContent->OwnerDoc()->GetInnerWindow();
  183. if (!inner || !inner->IsCurrentInnerWindow()) {
  184. return;
  185. }
  186. RefPtr<Directory> directory =
  187. Directory::Create(inner, file);
  188. MOZ_ASSERT(directory);
  189. OwningFileOrDirectory* element = aArray.AppendElement();
  190. element->SetAsDirectory() = directory;
  191. }
  192. /**
  193. * This is called when we receive a drop or a dragover.
  194. */
  195. NS_IMETHODIMP
  196. nsFileControlFrame::DnDListener::HandleEvent(nsIDOMEvent* aEvent)
  197. {
  198. NS_ASSERTION(mFrame, "We should have been unregistered");
  199. bool defaultPrevented = false;
  200. aEvent->GetDefaultPrevented(&defaultPrevented);
  201. if (defaultPrevented) {
  202. return NS_OK;
  203. }
  204. nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
  205. if (!dragEvent) {
  206. return NS_OK;
  207. }
  208. nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
  209. dragEvent->GetDataTransfer(getter_AddRefs(dataTransfer));
  210. if (!IsValidDropData(dataTransfer)) {
  211. return NS_OK;
  212. }
  213. nsCOMPtr<nsIContent> content = mFrame->GetContent();
  214. bool supportsMultiple = content && content->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple);
  215. if (!CanDropTheseFiles(dataTransfer, supportsMultiple)) {
  216. dataTransfer->SetDropEffect(NS_LITERAL_STRING("none"));
  217. aEvent->StopPropagation();
  218. return NS_OK;
  219. }
  220. nsAutoString eventType;
  221. aEvent->GetType(eventType);
  222. if (eventType.EqualsLiteral("dragover")) {
  223. // Prevent default if we can accept this drag data
  224. aEvent->PreventDefault();
  225. return NS_OK;
  226. }
  227. if (eventType.EqualsLiteral("drop")) {
  228. aEvent->StopPropagation();
  229. aEvent->PreventDefault();
  230. NS_ASSERTION(content, "The frame has no content???");
  231. HTMLInputElement* inputElement = HTMLInputElement::FromContent(content);
  232. NS_ASSERTION(inputElement, "No input element for this file upload control frame!");
  233. nsCOMPtr<nsIDOMFileList> fileList;
  234. dataTransfer->GetFiles(getter_AddRefs(fileList));
  235. RefPtr<BlobImpl> webkitDir;
  236. nsresult rv =
  237. GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir));
  238. NS_ENSURE_SUCCESS(rv, NS_OK);
  239. nsTArray<OwningFileOrDirectory> array;
  240. if (webkitDir) {
  241. AppendBlobImplAsDirectory(array, webkitDir, content);
  242. inputElement->MozSetDndFilesAndDirectories(array);
  243. } else {
  244. bool blinkFileSystemEnabled =
  245. Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false);
  246. if (blinkFileSystemEnabled) {
  247. FileList* files = static_cast<FileList*>(fileList.get());
  248. if (files) {
  249. for (uint32_t i = 0; i < files->Length(); ++i) {
  250. File* file = files->Item(i);
  251. if (file) {
  252. if (file->Impl() && file->Impl()->IsDirectory()) {
  253. AppendBlobImplAsDirectory(array, file->Impl(), content);
  254. } else {
  255. OwningFileOrDirectory* element = array.AppendElement();
  256. element->SetAsFile() = file;
  257. }
  258. }
  259. }
  260. }
  261. }
  262. // This is rather ugly. Pass the directories as Files using SetFiles,
  263. // but then if blink filesystem API is enabled, it wants
  264. // FileOrDirectory array.
  265. inputElement->SetFiles(fileList, true);
  266. if (blinkFileSystemEnabled) {
  267. inputElement->UpdateEntries(array);
  268. }
  269. nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
  270. NS_LITERAL_STRING("input"), true,
  271. false);
  272. nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
  273. NS_LITERAL_STRING("change"), true,
  274. false);
  275. }
  276. }
  277. return NS_OK;
  278. }
  279. nsresult
  280. nsFileControlFrame::DnDListener::GetBlobImplForWebkitDirectory(nsIDOMFileList* aFileList,
  281. BlobImpl** aBlobImpl)
  282. {
  283. *aBlobImpl = nullptr;
  284. HTMLInputElement* inputElement =
  285. HTMLInputElement::FromContent(mFrame->GetContent());
  286. bool webkitDirPicker =
  287. Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
  288. inputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory);
  289. if (!webkitDirPicker) {
  290. return NS_OK;
  291. }
  292. if (!aFileList) {
  293. return NS_ERROR_FAILURE;
  294. }
  295. FileList* files = static_cast<FileList*>(aFileList);
  296. // webkitdirectory doesn't care about the length of the file list but
  297. // only about the first item on it.
  298. uint32_t len = files->Length();
  299. if (len) {
  300. File* file = files->Item(0);
  301. if (file) {
  302. BlobImpl* impl = file->Impl();
  303. if (impl && impl->IsDirectory()) {
  304. RefPtr<BlobImpl> retVal = impl;
  305. retVal.swap(*aBlobImpl);
  306. return NS_OK;
  307. }
  308. }
  309. }
  310. return NS_ERROR_FAILURE;
  311. }
  312. bool
  313. nsFileControlFrame::DnDListener::IsValidDropData(nsIDOMDataTransfer* aDOMDataTransfer)
  314. {
  315. nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(aDOMDataTransfer);
  316. NS_ENSURE_TRUE(dataTransfer, false);
  317. // We only support dropping files onto a file upload control
  318. nsTArray<nsString> types;
  319. dataTransfer->GetTypes(types, *nsContentUtils::GetSystemPrincipal());
  320. return types.Contains(NS_LITERAL_STRING("Files"));
  321. }
  322. bool
  323. nsFileControlFrame::DnDListener::CanDropTheseFiles(nsIDOMDataTransfer* aDOMDataTransfer,
  324. bool aSupportsMultiple)
  325. {
  326. nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(aDOMDataTransfer);
  327. NS_ENSURE_TRUE(dataTransfer, false);
  328. nsCOMPtr<nsIDOMFileList> fileList;
  329. dataTransfer->GetFiles(getter_AddRefs(fileList));
  330. RefPtr<BlobImpl> webkitDir;
  331. nsresult rv =
  332. GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir));
  333. // Just check if either there isn't webkitdirectory attribute, or
  334. // fileList has a directory which can be dropped to the element.
  335. // No need to use webkitDir for anything here.
  336. NS_ENSURE_SUCCESS(rv, false);
  337. uint32_t listLength = 0;
  338. if (fileList) {
  339. fileList->GetLength(&listLength);
  340. }
  341. return listLength <= 1 || aSupportsMultiple;
  342. }
  343. nscoord
  344. nsFileControlFrame::GetMinISize(nsRenderingContext *aRenderingContext)
  345. {
  346. nscoord result;
  347. DISPLAY_MIN_WIDTH(this, result);
  348. // Our min width is our pref width
  349. result = GetPrefISize(aRenderingContext);
  350. return result;
  351. }
  352. void
  353. nsFileControlFrame::SyncDisabledState()
  354. {
  355. EventStates eventStates = mContent->AsElement()->State();
  356. if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
  357. mBrowseFilesOrDirs->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
  358. EmptyString(), true);
  359. } else {
  360. mBrowseFilesOrDirs->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
  361. }
  362. }
  363. nsresult
  364. nsFileControlFrame::AttributeChanged(int32_t aNameSpaceID,
  365. nsIAtom* aAttribute,
  366. int32_t aModType)
  367. {
  368. if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::tabindex) {
  369. if (aModType == nsIDOMMutationEvent::REMOVAL) {
  370. mBrowseFilesOrDirs->UnsetAttr(aNameSpaceID, aAttribute, true);
  371. } else {
  372. nsAutoString value;
  373. mContent->GetAttr(aNameSpaceID, aAttribute, value);
  374. mBrowseFilesOrDirs->SetAttr(aNameSpaceID, aAttribute, value, true);
  375. }
  376. }
  377. return nsBlockFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
  378. }
  379. void
  380. nsFileControlFrame::ContentStatesChanged(EventStates aStates)
  381. {
  382. if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
  383. nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
  384. }
  385. }
  386. #ifdef DEBUG_FRAME_DUMP
  387. nsresult
  388. nsFileControlFrame::GetFrameName(nsAString& aResult) const
  389. {
  390. return MakeFrameName(NS_LITERAL_STRING("FileControl"), aResult);
  391. }
  392. #endif
  393. void
  394. nsFileControlFrame::UpdateDisplayedValue(const nsAString& aValue, bool aNotify)
  395. {
  396. mTextContent->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, aNotify);
  397. }
  398. nsresult
  399. nsFileControlFrame::SetFormProperty(nsIAtom* aName,
  400. const nsAString& aValue)
  401. {
  402. if (nsGkAtoms::value == aName) {
  403. UpdateDisplayedValue(aValue, true);
  404. }
  405. return NS_OK;
  406. }
  407. void
  408. nsFileControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
  409. const nsDisplayListSet& aLists)
  410. {
  411. BuildDisplayListForInline(aBuilder, aLists);
  412. }
  413. #ifdef ACCESSIBILITY
  414. a11y::AccType
  415. nsFileControlFrame::AccessibleType()
  416. {
  417. return a11y::eHTMLFileInputType;
  418. }
  419. #endif
  420. ////////////////////////////////////////////////////////////
  421. // Mouse listener implementation
  422. NS_IMPL_ISUPPORTS(nsFileControlFrame::MouseListener,
  423. nsIDOMEventListener)