/* -*- 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 "nsIContent.h" #include "nsIAtom.h" #include "nsPresContext.h" #include "nsGkAtoms.h" #include "nsWidgetsCID.h" #include "nsIComponentManager.h" #include "nsHTMLParts.h" #include "nsIDOMHTMLInputElement.h" #include "nsIDOMHTMLButtonElement.h" #include "nsIFormControl.h" #include "nsINameSpaceManager.h" #include "nsCOMPtr.h" #include "nsIDOMElement.h" #include "nsIDocument.h" #include "nsIPresShell.h" #include "nsXPCOM.h" #include "nsISupportsPrimitives.h" #include "nsPIDOMWindow.h" #include "nsIFilePicker.h" #include "nsIDOMMouseEvent.h" #include "nsINodeInfo.h" #include "nsIDOMEventTarget.h" #include "nsIFile.h" #include "mozilla/dom/HTMLInputElement.h" #include "nsNodeInfoManager.h" #include "nsContentCreatorFunctions.h" #include "nsContentUtils.h" #include "nsDisplayList.h" #include "nsEventListenerManager.h" #include "nsInterfaceHashtable.h" #include "nsURIHashKey.h" #include "nsNetCID.h" #include "nsWeakReference.h" #include "nsIVariant.h" #include "mozilla/Services.h" #include "nsDirectoryServiceDefs.h" #include "nsDOMFile.h" #include "nsEventStates.h" #include "nsTextControlFrame.h" #include "nsIDOMDOMStringList.h" #include "nsIDOMDragEvent.h" #include "nsContentList.h" #include "nsIDOMMutationEvent.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, nsIFrame* 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(&mBrowse); mMouseListener->ForgetFrame(); nsBlockFrame::DestroyFrom(aDestructRoot); } nsresult nsFileControlFrame::CreateAnonymousContent(nsTArray& aElements) { nsCOMPtr doc = mContent->GetDocument(); nsCOMPtr nodeInfo; // Create and setup the file picking button. nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::button, nullptr, kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE); NS_NewHTMLElement(getter_AddRefs(mBrowse), nodeInfo.forget(), dom::NOT_FROM_PARSER); // NOTE: SetNativeAnonymous() has to be called before setting any attribute. mBrowse->SetNativeAnonymous(); mBrowse->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, "Browse", 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. nsCOMPtr textContent; nsresult rv = NS_NewTextNode(getter_AddRefs(textContent), mBrowse->NodeInfo()->NodeInfoManager()); NS_ENSURE_SUCCESS(rv, rv); textContent->SetText(buttonTxt, false); rv = mBrowse->AppendChildTo(textContent, false); NS_ENSURE_SUCCESS(rv, rv); // Make sure access key and tab order for the element actually redirect to the // file picking button. nsCOMPtr fileContent = do_QueryInterface(mContent); nsCOMPtr browseControl = do_QueryInterface(mBrowse); nsAutoString accessKey; fileContent->GetAccessKey(accessKey); browseControl->SetAccessKey(accessKey); int32_t tabIndex; fileContent->GetTabIndex(&tabIndex); browseControl->SetTabIndex(tabIndex); if (!aElements.AppendElement(mBrowse)) { return NS_ERROR_OUT_OF_MEMORY; } // Create and setup the text showing the selected files. nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::label, nullptr, kNameSpaceID_XUL, nsIDOMNode::ELEMENT_NODE); NS_TrustedNewXULElement(getter_AddRefs(mTextContent), nodeInfo.forget()); // NOTE: SetNativeAnonymous() has to be called before setting any attribute. mTextContent->SetNativeAnonymous(); 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(nsBaseContentList& aElements, uint32_t aFilter) { aElements.MaybeAppendElement(mBrowse); aElements.MaybeAppendElement(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) { } /** * 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->GetPreventDefault(&defaultPrevented); if (defaultPrevented) { return NS_OK; } nsCOMPtr dragEvent = do_QueryInterface(aEvent); if (!dragEvent || !IsValidDropData(dragEvent)) { 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(); nsIContent* content = mFrame->GetContent(); 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 dataTransfer; dragEvent->GetDataTransfer(getter_AddRefs(dataTransfer)); nsCOMPtr fileList; dataTransfer->GetFiles(getter_AddRefs(fileList)); inputElement->SetFiles(fileList, true); nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content, NS_LITERAL_STRING("change"), true, false); } return NS_OK; } /* static */ bool nsFileControlFrame::DnDListener::IsValidDropData(nsIDOMDragEvent* aEvent) { nsCOMPtr dataTransfer; aEvent->GetDataTransfer(getter_AddRefs(dataTransfer)); NS_ENSURE_TRUE(dataTransfer, false); nsCOMPtr types; dataTransfer->GetTypes(getter_AddRefs(types)); NS_ENSURE_TRUE(types, false); // We only support dropping files onto a file upload control bool typeSupported; types->Contains(NS_LITERAL_STRING("Files"), &typeSupported); return typeSupported; } nscoord nsFileControlFrame::GetMinWidth(nsRenderingContext *aRenderingContext) { nscoord result; DISPLAY_MIN_WIDTH(this, result); // Our min width is our pref width result = GetPrefWidth(aRenderingContext); return result; } void nsFileControlFrame::SyncDisabledState() { nsEventStates eventStates = mContent->AsElement()->State(); if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) { mBrowse->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, EmptyString(), true); } else { mBrowse->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true); } } NS_IMETHODIMP nsFileControlFrame::AttributeChanged(int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType) { if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::tabindex) { if (aModType == nsIDOMMutationEvent::REMOVAL) { mBrowse->UnsetAttr(aNameSpaceID, aAttribute, true); } else { nsAutoString value; mContent->GetAttr(aNameSpaceID, aAttribute, value); mBrowse->SetAttr(aNameSpaceID, aAttribute, value, true); } } return nsBlockFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); } void nsFileControlFrame::ContentStatesChanged(nsEventStates aStates) { if (aStates.HasState(NS_EVENT_STATE_DISABLED)) { nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this)); } } #ifdef DEBUG NS_IMETHODIMP 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 nsRect& aDirtyRect, const nsDisplayListSet& aLists) { // box-shadow if (StyleBorder()->mBoxShadow) { aLists.BorderBackground()->AppendNewToTop(new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, this)); } // Our background is inherited to the text input, and we don't really want to // paint it or out padding and borders (which we never have anyway, per // styles in forms.css) -- doing it just makes us look ugly in some cases and // has no effect in others. nsDisplayListCollection tempList; nsBlockFrame::BuildDisplayList(aBuilder, aDirtyRect, tempList); tempList.BorderBackground()->DeleteAll(); // Clip height only nsRect clipRect(aBuilder->ToReferenceFrame(this), GetSize()); clipRect.width = GetVisualOverflowRect().XMost(); nscoord radii[8] = {0, 0, 0, 0, 0, 0, 0, 0}; OverflowClip(aBuilder, tempList, aLists, clipRect, radii); // Disabled file controls don't pass mouse events to their children, so we // put an invisible item in the display list above the children // just to catch events nsEventStates eventStates = mContent->AsElement()->State(); if (eventStates.HasState(NS_EVENT_STATE_DISABLED) && IsVisibleForPainting(aBuilder)) { aLists.Content()->AppendNewToTop( new (aBuilder) nsDisplayEventReceiver(aBuilder, this)); } DisplaySelectionOverlay(aBuilder, aLists.Content()); } #ifdef ACCESSIBILITY a11y::AccType nsFileControlFrame::AccessibleType() { return a11y::eHTMLFileInputType; } #endif //////////////////////////////////////////////////////////// // Mouse listener implementation NS_IMPL_ISUPPORTS1(nsFileControlFrame::MouseListener, nsIDOMEventListener)