diff options
Diffstat (limited to 'layout/forms/nsDateTimeControlFrame.cpp')
-rw-r--r-- | layout/forms/nsDateTimeControlFrame.cpp | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/layout/forms/nsDateTimeControlFrame.cpp b/layout/forms/nsDateTimeControlFrame.cpp new file mode 100644 index 000000000..df2e43986 --- /dev/null +++ b/layout/forms/nsDateTimeControlFrame.cpp @@ -0,0 +1,414 @@ +/* -*- 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/. */ + +/** + * This frame type is used for input type=date, time, month, week, and + * datetime-local. + */ + +#include "nsDateTimeControlFrame.h" + +#include "nsContentUtils.h" +#include "nsFormControlFrame.h" +#include "nsGkAtoms.h" +#include "nsContentUtils.h" +#include "nsContentCreatorFunctions.h" +#include "nsContentList.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "nsNodeInfoManager.h" +#include "nsIDateTimeInputArea.h" +#include "nsIObserverService.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMMutationEvent.h" +#include "jsapi.h" +#include "nsJSUtils.h" +#include "nsThreadUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; + +nsIFrame* +NS_NewDateTimeControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsDateTimeControlFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsDateTimeControlFrame) + +NS_QUERYFRAME_HEAD(nsDateTimeControlFrame) + NS_QUERYFRAME_ENTRY(nsDateTimeControlFrame) + NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) +NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) + +nsDateTimeControlFrame::nsDateTimeControlFrame(nsStyleContext* aContext) + : nsContainerFrame(aContext) +{ +} + +void +nsDateTimeControlFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + nsContentUtils::DestroyAnonymousContent(&mInputAreaContent); + nsContainerFrame::DestroyFrom(aDestructRoot); +} + +void +nsDateTimeControlFrame::UpdateInputBoxValue() +{ + nsCOMPtr<nsIDateTimeInputArea> inputAreaContent = + do_QueryInterface(mInputAreaContent); + if (inputAreaContent) { + inputAreaContent->NotifyInputElementValueChanged(); + } +} + +void +nsDateTimeControlFrame::SetValueFromPicker(const DateTimeValue& aValue) +{ + nsCOMPtr<nsIDateTimeInputArea> inputAreaContent = + do_QueryInterface(mInputAreaContent); + if (inputAreaContent) { + AutoJSAPI api; + if (!api.Init(mContent->OwnerDoc()->GetScopeObject())) { + return; + } + + JSObject* wrapper = mContent->GetWrapper(); + if (!wrapper) { + return; + } + + JSObject* scope = xpc::GetXBLScope(api.cx(), wrapper); + AutoJSAPI jsapi; + if (!scope || !jsapi.Init(scope)) { + return; + } + + JS::Rooted<JS::Value> jsValue(jsapi.cx()); + if (!ToJSValue(jsapi.cx(), aValue, &jsValue)) { + return; + } + + inputAreaContent->SetValueFromPicker(jsValue); + } +} + +void +nsDateTimeControlFrame::SetPickerState(bool aOpen) +{ + nsCOMPtr<nsIDateTimeInputArea> inputAreaContent = + do_QueryInterface(mInputAreaContent); + if (inputAreaContent) { + inputAreaContent->SetPickerState(aOpen); + } +} + +void +nsDateTimeControlFrame::HandleFocusEvent() +{ + nsCOMPtr<nsIDateTimeInputArea> inputAreaContent = + do_QueryInterface(mInputAreaContent); + if (inputAreaContent) { + inputAreaContent->FocusInnerTextBox(); + } +} + +void +nsDateTimeControlFrame::HandleBlurEvent() +{ + nsCOMPtr<nsIDateTimeInputArea> inputAreaContent = + do_QueryInterface(mInputAreaContent); + if (inputAreaContent) { + inputAreaContent->BlurInnerTextBox(); + } +} + +nscoord +nsDateTimeControlFrame::GetMinISize(nsRenderingContext* aRenderingContext) +{ + nscoord result; + DISPLAY_MIN_WIDTH(this, result); + + nsIFrame* kid = mFrames.FirstChild(); + if (kid) { // display:none? + result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, + kid, + nsLayoutUtils::MIN_ISIZE); + } else { + result = 0; + } + + return result; +} + +nscoord +nsDateTimeControlFrame::GetPrefISize(nsRenderingContext* aRenderingContext) +{ + nscoord result; + DISPLAY_PREF_WIDTH(this, result); + + nsIFrame* kid = mFrames.FirstChild(); + if (kid) { // display:none? + result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, + kid, + nsLayoutUtils::PREF_ISIZE); + } else { + result = 0; + } + + return result; +} + +void +nsDateTimeControlFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + + DO_GLOBAL_REFLOW_COUNT("nsDateTimeControlFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus); + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("enter nsDateTimeControlFrame::Reflow: availSize=%d,%d", + aReflowInput.AvailableWidth(), + aReflowInput.AvailableHeight())); + + NS_ASSERTION(mInputAreaContent, "The input area content must exist!"); + + const WritingMode myWM = aReflowInput.GetWritingMode(); + + // The ISize of our content box, which is the available ISize + // for our anonymous content: + const nscoord contentBoxISize = aReflowInput.ComputedISize(); + nscoord contentBoxBSize = aReflowInput.ComputedBSize(); + + // Figure out our border-box sizes as well (by adding borderPadding to + // content-box sizes): + const nscoord borderBoxISize = contentBoxISize + + aReflowInput.ComputedLogicalBorderPadding().IStartEnd(myWM); + + nscoord borderBoxBSize; + if (contentBoxBSize != NS_INTRINSICSIZE) { + borderBoxBSize = contentBoxBSize + + aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM); + } // else, we'll figure out borderBoxBSize after we resolve contentBoxBSize. + + nsIFrame* inputAreaFrame = mFrames.FirstChild(); + if (!inputAreaFrame) { // display:none? + if (contentBoxBSize == NS_INTRINSICSIZE) { + contentBoxBSize = 0; + borderBoxBSize = + aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM); + } + } else { + NS_ASSERTION(inputAreaFrame->GetContent() == mInputAreaContent, + "What is this child doing here?"); + + ReflowOutput childDesiredSize(aReflowInput); + + WritingMode wm = inputAreaFrame->GetWritingMode(); + LogicalSize availSize = aReflowInput.ComputedSize(wm); + availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + + ReflowInput childReflowOuput(aPresContext, aReflowInput, + inputAreaFrame, availSize); + + // Convert input area margin into my own writing-mode (in case it differs): + LogicalMargin childMargin = + childReflowOuput.ComputedLogicalMargin().ConvertTo(myWM, wm); + + // offsets of input area frame within this frame: + LogicalPoint + childOffset(myWM, + aReflowInput.ComputedLogicalBorderPadding().IStart(myWM) + + childMargin.IStart(myWM), + aReflowInput.ComputedLogicalBorderPadding().BStart(myWM) + + childMargin.BStart(myWM)); + + nsReflowStatus childStatus; + // We initially reflow the child with a dummy containerSize; positioning + // will be fixed later. + const nsSize dummyContainerSize; + ReflowChild(inputAreaFrame, aPresContext, childDesiredSize, + childReflowOuput, myWM, childOffset, dummyContainerSize, 0, + childStatus); + MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(childStatus), + "We gave our child unconstrained available block-size, " + "so it should be complete"); + + nscoord childMarginBoxBSize = + childDesiredSize.BSize(myWM) + childMargin.BStartEnd(myWM); + + if (contentBoxBSize == NS_INTRINSICSIZE) { + // We are intrinsically sized -- we should shrinkwrap the input area's + // block-size: + contentBoxBSize = childMarginBoxBSize; + + // Make sure we obey min/max-bsize in the case when we're doing intrinsic + // sizing (we get it for free when we have a non-intrinsic + // aReflowInput.ComputedBSize()). Note that we do this before + // adjusting for borderpadding, since ComputedMaxBSize and + // ComputedMinBSize are content heights. + contentBoxBSize = + NS_CSS_MINMAX(contentBoxBSize, + aReflowInput.ComputedMinBSize(), + aReflowInput.ComputedMaxBSize()); + + borderBoxBSize = contentBoxBSize + + aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM); + } + + // Center child in block axis + nscoord extraSpace = contentBoxBSize - childMarginBoxBSize; + childOffset.B(myWM) += std::max(0, extraSpace / 2); + + // Needed in FinishReflowChild, for logical-to-physical conversion: + nsSize borderBoxSize = LogicalSize(myWM, borderBoxISize, borderBoxBSize). + GetPhysicalSize(myWM); + + // Place the child + FinishReflowChild(inputAreaFrame, aPresContext, childDesiredSize, + &childReflowOuput, myWM, childOffset, borderBoxSize, 0); + + nsSize contentBoxSize = + LogicalSize(myWM, contentBoxISize, contentBoxBSize). + GetPhysicalSize(myWM); + aDesiredSize.SetBlockStartAscent( + childDesiredSize.BlockStartAscent() + + inputAreaFrame->BStart(aReflowInput.GetWritingMode(), + contentBoxSize)); + } + + LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize); + aDesiredSize.SetSize(myWM, logicalDesiredSize); + + aDesiredSize.SetOverflowAreasToDesiredBounds(); + + if (inputAreaFrame) { + ConsiderChildOverflow(aDesiredSize.mOverflowAreas, inputAreaFrame); + } + + FinishAndStoreOverflow(&aDesiredSize); + + aStatus = NS_FRAME_COMPLETE; + + NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, + ("exit nsDateTimeControlFrame::Reflow: size=%d,%d", + aDesiredSize.Width(), aDesiredSize.Height())); + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize); +} + +nsresult +nsDateTimeControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements) +{ + // Set up "datetimebox" XUL element which will be XBL-bound to the + // actual controls. + nsNodeInfoManager* nodeInfoManager = + mContent->GetComposedDoc()->NodeInfoManager(); + RefPtr<NodeInfo> nodeInfo = + nodeInfoManager->GetNodeInfo(nsGkAtoms::datetimebox, nullptr, + kNameSpaceID_XUL, nsIDOMNode::ELEMENT_NODE); + NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY); + + NS_TrustedNewXULElement(getter_AddRefs(mInputAreaContent), nodeInfo.forget()); + aElements.AppendElement(mInputAreaContent); + + // Propogate our tabindex. + nsAutoString tabIndexStr; + if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr)) { + mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, + tabIndexStr, false); + } + + // Propagate our readonly state. + nsAutoString readonly; + if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly)) { + mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly, + false); + } + + SyncDisabledState(); + + return NS_OK; +} + +void +nsDateTimeControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, + uint32_t aFilter) +{ + if (mInputAreaContent) { + aElements.AppendElement(mInputAreaContent); + } +} + +void +nsDateTimeControlFrame::SyncDisabledState() +{ + EventStates eventStates = mContent->AsElement()->State(); + if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) { + mInputAreaContent->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, + EmptyString(), true); + } else { + mInputAreaContent->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true); + } +} + +nsresult +nsDateTimeControlFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + NS_ASSERTION(mInputAreaContent, "The input area content must exist!"); + + // nsGkAtoms::disabled is handled by SyncDisabledState + if (aNameSpaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::value || + aAttribute == nsGkAtoms::readonly || + aAttribute == nsGkAtoms::tabindex) { + MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast"); + auto contentAsInputElem = static_cast<dom::HTMLInputElement*>(mContent); + // If script changed the <input>'s type before setting these attributes + // then we don't need to do anything since we are going to be reframed. + if (contentAsInputElem->GetType() == NS_FORM_INPUT_TIME) { + if (aAttribute == nsGkAtoms::value) { + nsCOMPtr<nsIDateTimeInputArea> inputAreaContent = + do_QueryInterface(mInputAreaContent); + if (inputAreaContent) { + nsContentUtils::AddScriptRunner(NewRunnableMethod(inputAreaContent, + &nsIDateTimeInputArea::NotifyInputElementValueChanged)); + } + } else { + if (aModType == nsIDOMMutationEvent::REMOVAL) { + mInputAreaContent->UnsetAttr(aNameSpaceID, aAttribute, true); + } else { + MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION || + aModType == nsIDOMMutationEvent::MODIFICATION); + nsAutoString value; + mContent->GetAttr(aNameSpaceID, aAttribute, value); + mInputAreaContent->SetAttr(aNameSpaceID, aAttribute, value, true); + } + } + } + } + } + + return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); +} + +void +nsDateTimeControlFrame::ContentStatesChanged(EventStates aStates) +{ + if (aStates.HasState(NS_EVENT_STATE_DISABLED)) { + nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this)); + } +} + +nsIAtom* +nsDateTimeControlFrame::GetType() const +{ + return nsGkAtoms::dateTimeControlFrame; +} |