123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* 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 "nsFileControlFrame.h"
- #include "nsGkAtoms.h"
- #include "nsCOMPtr.h"
- #include "nsIDocument.h"
- #include "mozilla/dom/NodeInfo.h"
- #include "mozilla/dom/Element.h"
- #include "mozilla/dom/DataTransfer.h"
- #include "mozilla/dom/HTMLButtonElement.h"
- #include "mozilla/dom/HTMLInputElement.h"
- #include "mozilla/Preferences.h"
- #include "nsNodeInfoManager.h"
- #include "nsContentCreatorFunctions.h"
- #include "nsContentUtils.h"
- #include "mozilla/EventStates.h"
- #include "mozilla/dom/DOMStringList.h"
- #include "mozilla/dom/Directory.h"
- #include "mozilla/dom/FileList.h"
- #include "nsIDOMDragEvent.h"
- #include "nsIDOMFileList.h"
- #include "nsContentList.h"
- #include "nsIDOMMutationEvent.h"
- #include "nsTextNode.h"
- using namespace mozilla;
- using namespace mozilla::dom;
- nsIFrame*
- NS_NewFileControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
- {
- return new (aPresShell) nsFileControlFrame(aContext);
- }
- NS_IMPL_FRAMEARENA_HELPERS(nsFileControlFrame)
- nsFileControlFrame::nsFileControlFrame(nsStyleContext* aContext)
- : nsBlockFrame(aContext)
- {
- AddStateBits(NS_BLOCK_FLOAT_MGR);
- }
- void
- nsFileControlFrame::Init(nsIContent* aContent,
- nsContainerFrame* aParent,
- nsIFrame* aPrevInFlow)
- {
- nsBlockFrame::Init(aContent, aParent, aPrevInFlow);
- mMouseListener = new DnDListener(this);
- }
- void
- nsFileControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
- {
- ENSURE_TRUE(mContent);
- // Remove the events.
- if (mContent) {
- mContent->RemoveSystemEventListener(NS_LITERAL_STRING("drop"),
- mMouseListener, false);
- mContent->RemoveSystemEventListener(NS_LITERAL_STRING("dragover"),
- mMouseListener, false);
- }
- nsContentUtils::DestroyAnonymousContent(&mTextContent);
- nsContentUtils::DestroyAnonymousContent(&mBrowseFilesOrDirs);
- mMouseListener->ForgetFrame();
- nsBlockFrame::DestroyFrom(aDestructRoot);
- }
- static already_AddRefed<Element>
- MakeAnonButton(nsIDocument* aDoc, const char* labelKey,
- HTMLInputElement* aInputElement,
- const nsAString& aAccessKey)
- {
- RefPtr<Element> button = aDoc->CreateHTMLElement(nsGkAtoms::button);
- // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
- // attribute.
- button->SetIsNativeAnonymousRoot();
- button->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
- NS_LITERAL_STRING("button"), false);
- // Set the file picking button text depending on the current locale.
- nsXPIDLString buttonTxt;
- nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
- labelKey, buttonTxt);
- // Set the browse button text. It's a bit of a pain to do because we want to
- // make sure we are not notifying.
- RefPtr<nsTextNode> textContent =
- new nsTextNode(button->NodeInfo()->NodeInfoManager());
- textContent->SetText(buttonTxt, false);
- nsresult rv = button->AppendChildTo(textContent, false);
- if (NS_FAILED(rv)) {
- return nullptr;
- }
- // Make sure access key and tab order for the element actually redirect to the
- // file picking button.
- RefPtr<HTMLButtonElement> buttonElement =
- HTMLButtonElement::FromContentOrNull(button);
- if (!aAccessKey.IsEmpty()) {
- buttonElement->SetAccessKey(aAccessKey);
- }
- // Both elements are given the same tab index so that the user can tab
- // to the file control at the correct index, and then between the two
- // buttons.
- int32_t tabIndex;
- aInputElement->GetTabIndex(&tabIndex);
- buttonElement->SetTabIndex(tabIndex);
- return button.forget();
- }
- nsresult
- nsFileControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
- {
- nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
- RefPtr<HTMLInputElement> fileContent = HTMLInputElement::FromContentOrNull(mContent);
- // The access key is transferred to the "Choose files..." button only. In
- // effect that access key allows access to the control via that button, then
- // the user can tab between the two buttons.
- nsAutoString accessKey;
- fileContent->GetAccessKey(accessKey);
- mBrowseFilesOrDirs = MakeAnonButton(doc, "Browse", fileContent, accessKey);
- if (!mBrowseFilesOrDirs || !aElements.AppendElement(mBrowseFilesOrDirs)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- // Create and setup the text showing the selected files.
- RefPtr<NodeInfo> nodeInfo;
- nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::label, nullptr,
- kNameSpaceID_XUL,
- nsIDOMNode::ELEMENT_NODE);
- NS_TrustedNewXULElement(getter_AddRefs(mTextContent), nodeInfo.forget());
- // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
- // attribute.
- mTextContent->SetIsNativeAnonymousRoot();
- mTextContent->SetAttr(kNameSpaceID_None, nsGkAtoms::crop,
- NS_LITERAL_STRING("center"), false);
- // Update the displayed text to reflect the current element's value.
- nsAutoString value;
- HTMLInputElement::FromContent(mContent)->GetDisplayFileName(value);
- UpdateDisplayedValue(value, false);
- if (!aElements.AppendElement(mTextContent)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- // We should be able to interact with the element by doing drag and drop.
- mContent->AddSystemEventListener(NS_LITERAL_STRING("drop"),
- mMouseListener, false);
- mContent->AddSystemEventListener(NS_LITERAL_STRING("dragover"),
- mMouseListener, false);
- SyncDisabledState();
- return NS_OK;
- }
- void
- nsFileControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
- uint32_t aFilter)
- {
- if (mBrowseFilesOrDirs) {
- aElements.AppendElement(mBrowseFilesOrDirs);
- }
- if (mTextContent) {
- aElements.AppendElement(mTextContent);
- }
- }
- NS_QUERYFRAME_HEAD(nsFileControlFrame)
- NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
- NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
- NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
- void
- nsFileControlFrame::SetFocus(bool aOn, bool aRepaint)
- {
- }
- static void
- AppendBlobImplAsDirectory(nsTArray<OwningFileOrDirectory>& aArray,
- BlobImpl* aBlobImpl,
- nsIContent* aContent)
- {
- MOZ_ASSERT(aBlobImpl);
- MOZ_ASSERT(aBlobImpl->IsDirectory());
- nsAutoString fullpath;
- ErrorResult err;
- aBlobImpl->GetMozFullPath(fullpath, err);
- if (err.Failed()) {
- err.SuppressException();
- return;
- }
- nsCOMPtr<nsIFile> file;
- NS_ConvertUTF16toUTF8 path(fullpath);
- nsresult rv = NS_NewNativeLocalFile(path, true, getter_AddRefs(file));
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return;
- }
- nsPIDOMWindowInner* inner = aContent->OwnerDoc()->GetInnerWindow();
- if (!inner || !inner->IsCurrentInnerWindow()) {
- return;
- }
- RefPtr<Directory> directory =
- Directory::Create(inner, file);
- MOZ_ASSERT(directory);
- OwningFileOrDirectory* element = aArray.AppendElement();
- element->SetAsDirectory() = directory;
- }
- /**
- * This is called when we receive a drop or a dragover.
- */
- NS_IMETHODIMP
- nsFileControlFrame::DnDListener::HandleEvent(nsIDOMEvent* aEvent)
- {
- NS_ASSERTION(mFrame, "We should have been unregistered");
- bool defaultPrevented = false;
- aEvent->GetDefaultPrevented(&defaultPrevented);
- if (defaultPrevented) {
- return NS_OK;
- }
- nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
- if (!dragEvent) {
- return NS_OK;
- }
- nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
- dragEvent->GetDataTransfer(getter_AddRefs(dataTransfer));
- if (!IsValidDropData(dataTransfer)) {
- return NS_OK;
- }
- nsCOMPtr<nsIContent> content = mFrame->GetContent();
- bool supportsMultiple = content && content->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple);
- if (!CanDropTheseFiles(dataTransfer, supportsMultiple)) {
- dataTransfer->SetDropEffect(NS_LITERAL_STRING("none"));
- aEvent->StopPropagation();
- return NS_OK;
- }
- nsAutoString eventType;
- aEvent->GetType(eventType);
- if (eventType.EqualsLiteral("dragover")) {
- // Prevent default if we can accept this drag data
- aEvent->PreventDefault();
- return NS_OK;
- }
- if (eventType.EqualsLiteral("drop")) {
- aEvent->StopPropagation();
- aEvent->PreventDefault();
- NS_ASSERTION(content, "The frame has no content???");
- HTMLInputElement* inputElement = HTMLInputElement::FromContent(content);
- NS_ASSERTION(inputElement, "No input element for this file upload control frame!");
- nsCOMPtr<nsIDOMFileList> fileList;
- dataTransfer->GetFiles(getter_AddRefs(fileList));
- RefPtr<BlobImpl> webkitDir;
- nsresult rv =
- GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir));
- NS_ENSURE_SUCCESS(rv, NS_OK);
- nsTArray<OwningFileOrDirectory> array;
- if (webkitDir) {
- AppendBlobImplAsDirectory(array, webkitDir, content);
- inputElement->MozSetDndFilesAndDirectories(array);
- } else {
- bool blinkFileSystemEnabled =
- Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false);
- if (blinkFileSystemEnabled) {
- FileList* files = static_cast<FileList*>(fileList.get());
- if (files) {
- for (uint32_t i = 0; i < files->Length(); ++i) {
- File* file = files->Item(i);
- if (file) {
- if (file->Impl() && file->Impl()->IsDirectory()) {
- AppendBlobImplAsDirectory(array, file->Impl(), content);
- } else {
- OwningFileOrDirectory* element = array.AppendElement();
- element->SetAsFile() = file;
- }
- }
- }
- }
- }
- // This is rather ugly. Pass the directories as Files using SetFiles,
- // but then if blink filesystem API is enabled, it wants
- // FileOrDirectory array.
- inputElement->SetFiles(fileList, true);
- if (blinkFileSystemEnabled) {
- inputElement->UpdateEntries(array);
- }
- nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
- NS_LITERAL_STRING("input"), true,
- false);
- nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
- NS_LITERAL_STRING("change"), true,
- false);
- }
- }
- return NS_OK;
- }
- nsresult
- nsFileControlFrame::DnDListener::GetBlobImplForWebkitDirectory(nsIDOMFileList* aFileList,
- BlobImpl** aBlobImpl)
- {
- *aBlobImpl = nullptr;
- HTMLInputElement* inputElement =
- HTMLInputElement::FromContent(mFrame->GetContent());
- bool webkitDirPicker =
- Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
- inputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory);
- if (!webkitDirPicker) {
- return NS_OK;
- }
- if (!aFileList) {
- return NS_ERROR_FAILURE;
- }
- FileList* files = static_cast<FileList*>(aFileList);
- // webkitdirectory doesn't care about the length of the file list but
- // only about the first item on it.
- uint32_t len = files->Length();
- if (len) {
- File* file = files->Item(0);
- if (file) {
- BlobImpl* impl = file->Impl();
- if (impl && impl->IsDirectory()) {
- RefPtr<BlobImpl> retVal = impl;
- retVal.swap(*aBlobImpl);
- return NS_OK;
- }
- }
- }
- return NS_ERROR_FAILURE;
- }
- bool
- nsFileControlFrame::DnDListener::IsValidDropData(nsIDOMDataTransfer* aDOMDataTransfer)
- {
- nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(aDOMDataTransfer);
- NS_ENSURE_TRUE(dataTransfer, false);
- // We only support dropping files onto a file upload control
- nsTArray<nsString> types;
- dataTransfer->GetTypes(types, *nsContentUtils::GetSystemPrincipal());
- return types.Contains(NS_LITERAL_STRING("Files"));
- }
- bool
- nsFileControlFrame::DnDListener::CanDropTheseFiles(nsIDOMDataTransfer* aDOMDataTransfer,
- bool aSupportsMultiple)
- {
- nsCOMPtr<DataTransfer> dataTransfer = do_QueryInterface(aDOMDataTransfer);
- NS_ENSURE_TRUE(dataTransfer, false);
- nsCOMPtr<nsIDOMFileList> fileList;
- dataTransfer->GetFiles(getter_AddRefs(fileList));
- RefPtr<BlobImpl> webkitDir;
- nsresult rv =
- GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir));
- // Just check if either there isn't webkitdirectory attribute, or
- // fileList has a directory which can be dropped to the element.
- // No need to use webkitDir for anything here.
- NS_ENSURE_SUCCESS(rv, false);
- uint32_t listLength = 0;
- if (fileList) {
- fileList->GetLength(&listLength);
- }
- return listLength <= 1 || aSupportsMultiple;
- }
- nscoord
- nsFileControlFrame::GetMinISize(nsRenderingContext *aRenderingContext)
- {
- nscoord result;
- DISPLAY_MIN_WIDTH(this, result);
- // Our min width is our pref width
- result = GetPrefISize(aRenderingContext);
- return result;
- }
- void
- nsFileControlFrame::SyncDisabledState()
- {
- EventStates eventStates = mContent->AsElement()->State();
- if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
- mBrowseFilesOrDirs->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
- EmptyString(), true);
- } else {
- mBrowseFilesOrDirs->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
- }
- }
- nsresult
- nsFileControlFrame::AttributeChanged(int32_t aNameSpaceID,
- nsIAtom* aAttribute,
- int32_t aModType)
- {
- if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::tabindex) {
- if (aModType == nsIDOMMutationEvent::REMOVAL) {
- mBrowseFilesOrDirs->UnsetAttr(aNameSpaceID, aAttribute, true);
- } else {
- nsAutoString value;
- mContent->GetAttr(aNameSpaceID, aAttribute, value);
- mBrowseFilesOrDirs->SetAttr(aNameSpaceID, aAttribute, value, true);
- }
- }
- return nsBlockFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
- }
- void
- nsFileControlFrame::ContentStatesChanged(EventStates aStates)
- {
- if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
- nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
- }
- }
- #ifdef DEBUG_FRAME_DUMP
- nsresult
- nsFileControlFrame::GetFrameName(nsAString& aResult) const
- {
- return MakeFrameName(NS_LITERAL_STRING("FileControl"), aResult);
- }
- #endif
- void
- nsFileControlFrame::UpdateDisplayedValue(const nsAString& aValue, bool aNotify)
- {
- mTextContent->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, aNotify);
- }
- nsresult
- nsFileControlFrame::SetFormProperty(nsIAtom* aName,
- const nsAString& aValue)
- {
- if (nsGkAtoms::value == aName) {
- UpdateDisplayedValue(aValue, true);
- }
- return NS_OK;
- }
- void
- nsFileControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
- const nsDisplayListSet& aLists)
- {
- BuildDisplayListForInline(aBuilder, aLists);
- }
- #ifdef ACCESSIBILITY
- a11y::AccType
- nsFileControlFrame::AccessibleType()
- {
- return a11y::eHTMLFileInputType;
- }
- #endif
- ////////////////////////////////////////////////////////////
- // Mouse listener implementation
- NS_IMPL_ISUPPORTS(nsFileControlFrame::MouseListener,
- nsIDOMEventListener)
|