michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsFileControlFrame.h" michael@0: michael@0: #include "nsGkAtoms.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsINodeInfo.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/DataTransfer.h" michael@0: #include "mozilla/dom/HTMLButtonElement.h" michael@0: #include "mozilla/dom/HTMLInputElement.h" michael@0: #include "nsNodeInfoManager.h" michael@0: #include "nsContentCreatorFunctions.h" michael@0: #include "nsContentUtils.h" michael@0: #include "mozilla/EventStates.h" michael@0: #include "mozilla/dom/DOMStringList.h" michael@0: #include "nsIDOMDragEvent.h" michael@0: #include "nsIDOMFileList.h" michael@0: #include "nsContentList.h" michael@0: #include "nsIDOMMutationEvent.h" michael@0: #include "nsTextNode.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: nsIFrame* michael@0: NS_NewFileControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsFileControlFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsFileControlFrame) michael@0: michael@0: nsFileControlFrame::nsFileControlFrame(nsStyleContext* aContext) michael@0: : nsBlockFrame(aContext) michael@0: { michael@0: AddStateBits(NS_BLOCK_FLOAT_MGR); michael@0: } michael@0: michael@0: michael@0: void michael@0: nsFileControlFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: nsBlockFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: mMouseListener = new DnDListener(this); michael@0: } michael@0: michael@0: void michael@0: nsFileControlFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: ENSURE_TRUE(mContent); michael@0: michael@0: // Remove the events. michael@0: if (mContent) { michael@0: mContent->RemoveSystemEventListener(NS_LITERAL_STRING("drop"), michael@0: mMouseListener, false); michael@0: mContent->RemoveSystemEventListener(NS_LITERAL_STRING("dragover"), michael@0: mMouseListener, false); michael@0: } michael@0: michael@0: nsContentUtils::DestroyAnonymousContent(&mTextContent); michael@0: nsContentUtils::DestroyAnonymousContent(&mBrowse); michael@0: michael@0: mMouseListener->ForgetFrame(); michael@0: nsBlockFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: nsresult michael@0: nsFileControlFrame::CreateAnonymousContent(nsTArray& aElements) michael@0: { michael@0: nsCOMPtr doc = mContent->GetDocument(); michael@0: michael@0: // Create and setup the file picking button. michael@0: mBrowse = doc->CreateHTMLElement(nsGkAtoms::button); michael@0: // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any michael@0: // attribute. michael@0: mBrowse->SetIsNativeAnonymousRoot(); michael@0: mBrowse->SetAttr(kNameSpaceID_None, nsGkAtoms::type, michael@0: NS_LITERAL_STRING("button"), false); michael@0: michael@0: // Set the file picking button text depending on the current locale. michael@0: nsXPIDLString buttonTxt; michael@0: nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES, michael@0: "Browse", buttonTxt); michael@0: michael@0: // Set the browse button text. It's a bit of a pain to do because we want to michael@0: // make sure we are not notifying. michael@0: nsRefPtr textContent = michael@0: new nsTextNode(mBrowse->NodeInfo()->NodeInfoManager()); michael@0: michael@0: textContent->SetText(buttonTxt, false); michael@0: michael@0: nsresult rv = mBrowse->AppendChildTo(textContent, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Make sure access key and tab order for the element actually redirect to the michael@0: // file picking button. michael@0: nsRefPtr fileContent = HTMLInputElement::FromContentOrNull(mContent); michael@0: nsRefPtr browseControl = HTMLButtonElement::FromContentOrNull(mBrowse); michael@0: michael@0: nsAutoString accessKey; michael@0: fileContent->GetAccessKey(accessKey); michael@0: browseControl->SetAccessKey(accessKey); michael@0: michael@0: int32_t tabIndex; michael@0: fileContent->GetTabIndex(&tabIndex); michael@0: browseControl->SetTabIndex(tabIndex); michael@0: michael@0: if (!aElements.AppendElement(mBrowse)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: // Create and setup the text showing the selected files. michael@0: nsCOMPtr nodeInfo; michael@0: nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::label, nullptr, michael@0: kNameSpaceID_XUL, michael@0: nsIDOMNode::ELEMENT_NODE); michael@0: NS_TrustedNewXULElement(getter_AddRefs(mTextContent), nodeInfo.forget()); michael@0: // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any michael@0: // attribute. michael@0: mTextContent->SetIsNativeAnonymousRoot(); michael@0: mTextContent->SetAttr(kNameSpaceID_None, nsGkAtoms::crop, michael@0: NS_LITERAL_STRING("center"), false); michael@0: michael@0: // Update the displayed text to reflect the current element's value. michael@0: nsAutoString value; michael@0: HTMLInputElement::FromContent(mContent)->GetDisplayFileName(value); michael@0: UpdateDisplayedValue(value, false); michael@0: michael@0: if (!aElements.AppendElement(mTextContent)) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: // We should be able to interact with the element by doing drag and drop. michael@0: mContent->AddSystemEventListener(NS_LITERAL_STRING("drop"), michael@0: mMouseListener, false); michael@0: mContent->AddSystemEventListener(NS_LITERAL_STRING("dragover"), michael@0: mMouseListener, false); michael@0: michael@0: SyncDisabledState(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFileControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, michael@0: uint32_t aFilter) michael@0: { michael@0: aElements.MaybeAppendElement(mBrowse); michael@0: aElements.MaybeAppendElement(mTextContent); michael@0: } michael@0: michael@0: NS_QUERYFRAME_HEAD(nsFileControlFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) michael@0: NS_QUERYFRAME_ENTRY(nsIFormControlFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame) michael@0: michael@0: void michael@0: nsFileControlFrame::SetFocus(bool aOn, bool aRepaint) michael@0: { michael@0: } michael@0: michael@0: /** michael@0: * This is called when we receive a drop or a dragover. michael@0: */ michael@0: NS_IMETHODIMP michael@0: nsFileControlFrame::DnDListener::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: NS_ASSERTION(mFrame, "We should have been unregistered"); michael@0: michael@0: bool defaultPrevented = false; michael@0: aEvent->GetDefaultPrevented(&defaultPrevented); michael@0: if (defaultPrevented) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr dragEvent = do_QueryInterface(aEvent); michael@0: if (!dragEvent || !IsValidDropData(dragEvent)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoString eventType; michael@0: aEvent->GetType(eventType); michael@0: if (eventType.EqualsLiteral("dragover")) { michael@0: // Prevent default if we can accept this drag data michael@0: aEvent->PreventDefault(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (eventType.EqualsLiteral("drop")) { michael@0: aEvent->StopPropagation(); michael@0: aEvent->PreventDefault(); michael@0: michael@0: nsIContent* content = mFrame->GetContent(); michael@0: NS_ASSERTION(content, "The frame has no content???"); michael@0: michael@0: HTMLInputElement* inputElement = HTMLInputElement::FromContent(content); michael@0: NS_ASSERTION(inputElement, "No input element for this file upload control frame!"); michael@0: michael@0: nsCOMPtr dataTransfer; michael@0: dragEvent->GetDataTransfer(getter_AddRefs(dataTransfer)); michael@0: michael@0: nsCOMPtr fileList; michael@0: dataTransfer->GetFiles(getter_AddRefs(fileList)); michael@0: michael@0: inputElement->SetFiles(fileList, true); michael@0: nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content, michael@0: NS_LITERAL_STRING("change"), true, michael@0: false); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsFileControlFrame::DnDListener::IsValidDropData(nsIDOMDragEvent* aEvent) michael@0: { michael@0: nsCOMPtr domDataTransfer; michael@0: aEvent->GetDataTransfer(getter_AddRefs(domDataTransfer)); michael@0: nsCOMPtr dataTransfer = do_QueryInterface(domDataTransfer); michael@0: NS_ENSURE_TRUE(dataTransfer, false); michael@0: michael@0: // We only support dropping files onto a file upload control michael@0: nsRefPtr types = dataTransfer->Types(); michael@0: return types->Contains(NS_LITERAL_STRING("Files")); michael@0: } michael@0: michael@0: nscoord michael@0: nsFileControlFrame::GetMinWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: nscoord result; michael@0: DISPLAY_MIN_WIDTH(this, result); michael@0: michael@0: // Our min width is our pref width michael@0: result = GetPrefWidth(aRenderingContext); michael@0: return result; michael@0: } michael@0: michael@0: void michael@0: nsFileControlFrame::SyncDisabledState() michael@0: { michael@0: EventStates eventStates = mContent->AsElement()->State(); michael@0: if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) { michael@0: mBrowse->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, EmptyString(), michael@0: true); michael@0: } else { michael@0: mBrowse->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsFileControlFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::tabindex) { michael@0: if (aModType == nsIDOMMutationEvent::REMOVAL) { michael@0: mBrowse->UnsetAttr(aNameSpaceID, aAttribute, true); michael@0: } else { michael@0: nsAutoString value; michael@0: mContent->GetAttr(aNameSpaceID, aAttribute, value); michael@0: mBrowse->SetAttr(aNameSpaceID, aAttribute, value, true); michael@0: } michael@0: } michael@0: michael@0: return nsBlockFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); michael@0: } michael@0: michael@0: void michael@0: nsFileControlFrame::ContentStatesChanged(EventStates aStates) michael@0: { michael@0: if (aStates.HasState(NS_EVENT_STATE_DISABLED)) { michael@0: nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this)); michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: nsresult michael@0: nsFileControlFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("FileControl"), aResult); michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: nsFileControlFrame::UpdateDisplayedValue(const nsAString& aValue, bool aNotify) michael@0: { michael@0: mTextContent->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, aNotify); michael@0: } michael@0: michael@0: nsresult michael@0: nsFileControlFrame::SetFormProperty(nsIAtom* aName, michael@0: const nsAString& aValue) michael@0: { michael@0: if (nsGkAtoms::value == aName) { michael@0: UpdateDisplayedValue(aValue, true); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFileControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: BuildDisplayListForInline(aBuilder, aDirtyRect, aLists); michael@0: } michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: a11y::AccType michael@0: nsFileControlFrame::AccessibleType() michael@0: { michael@0: return a11y::eHTMLFileInputType; michael@0: } michael@0: #endif michael@0: michael@0: //////////////////////////////////////////////////////////// michael@0: // Mouse listener implementation michael@0: michael@0: NS_IMPL_ISUPPORTS(nsFileControlFrame::MouseListener, michael@0: nsIDOMEventListener)