Line data Source code
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 :
6 : /**
7 : * This frame type is used for input type=date, time, month, week, and
8 : * datetime-local.
9 : */
10 :
11 : #include "nsDateTimeControlFrame.h"
12 :
13 : #include "nsContentUtils.h"
14 : #include "nsFormControlFrame.h"
15 : #include "nsGkAtoms.h"
16 : #include "nsContentUtils.h"
17 : #include "nsContentCreatorFunctions.h"
18 : #include "nsContentList.h"
19 : #include "mozilla/dom/HTMLInputElement.h"
20 : #include "nsNodeInfoManager.h"
21 : #include "nsIDateTimeInputArea.h"
22 : #include "nsIObserverService.h"
23 : #include "nsIDOMHTMLInputElement.h"
24 : #include "nsIDOMMutationEvent.h"
25 : #include "jsapi.h"
26 : #include "nsJSUtils.h"
27 : #include "nsThreadUtils.h"
28 :
29 : using namespace mozilla;
30 : using namespace mozilla::dom;
31 :
32 : nsIFrame*
33 0 : NS_NewDateTimeControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
34 : {
35 0 : return new (aPresShell) nsDateTimeControlFrame(aContext);
36 : }
37 :
38 0 : NS_IMPL_FRAMEARENA_HELPERS(nsDateTimeControlFrame)
39 :
40 0 : NS_QUERYFRAME_HEAD(nsDateTimeControlFrame)
41 0 : NS_QUERYFRAME_ENTRY(nsDateTimeControlFrame)
42 0 : NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
43 0 : NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
44 :
45 0 : nsDateTimeControlFrame::nsDateTimeControlFrame(nsStyleContext* aContext)
46 0 : : nsContainerFrame(aContext, kClassID)
47 : {
48 0 : }
49 :
50 : void
51 0 : nsDateTimeControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
52 : {
53 0 : nsContentUtils::DestroyAnonymousContent(&mInputAreaContent);
54 0 : nsContainerFrame::DestroyFrom(aDestructRoot);
55 0 : }
56 :
57 : void
58 0 : nsDateTimeControlFrame::OnValueChanged()
59 : {
60 : nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
61 0 : do_QueryInterface(mInputAreaContent);
62 0 : if (inputAreaContent) {
63 0 : inputAreaContent->NotifyInputElementValueChanged();
64 : }
65 0 : }
66 :
67 : void
68 0 : nsDateTimeControlFrame::OnMinMaxStepAttrChanged()
69 : {
70 : nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
71 0 : do_QueryInterface(mInputAreaContent);
72 0 : if (inputAreaContent) {
73 0 : inputAreaContent->NotifyMinMaxStepAttrChanged();
74 : }
75 0 : }
76 :
77 : void
78 0 : nsDateTimeControlFrame::SetValueFromPicker(const DateTimeValue& aValue)
79 : {
80 : nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
81 0 : do_QueryInterface(mInputAreaContent);
82 0 : if (inputAreaContent) {
83 0 : AutoJSAPI api;
84 0 : if (!api.Init(mContent->OwnerDoc()->GetScopeObject())) {
85 0 : return;
86 : }
87 :
88 0 : JSObject* wrapper = mContent->GetWrapper();
89 0 : if (!wrapper) {
90 0 : return;
91 : }
92 :
93 0 : JSObject* scope = xpc::GetXBLScope(api.cx(), wrapper);
94 0 : AutoJSAPI jsapi;
95 0 : if (!scope || !jsapi.Init(scope)) {
96 0 : return;
97 : }
98 :
99 0 : JS::Rooted<JS::Value> jsValue(jsapi.cx());
100 0 : if (!ToJSValue(jsapi.cx(), aValue, &jsValue)) {
101 0 : return;
102 : }
103 :
104 0 : inputAreaContent->SetValueFromPicker(jsValue);
105 : }
106 : }
107 :
108 : void
109 0 : nsDateTimeControlFrame::SetPickerState(bool aOpen)
110 : {
111 : nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
112 0 : do_QueryInterface(mInputAreaContent);
113 0 : if (inputAreaContent) {
114 0 : inputAreaContent->SetPickerState(aOpen);
115 : }
116 0 : }
117 :
118 : void
119 0 : nsDateTimeControlFrame::HandleFocusEvent()
120 : {
121 : nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
122 0 : do_QueryInterface(mInputAreaContent);
123 0 : if (inputAreaContent) {
124 0 : inputAreaContent->FocusInnerTextBox();
125 : }
126 0 : }
127 :
128 : void
129 0 : nsDateTimeControlFrame::HandleBlurEvent()
130 : {
131 : nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
132 0 : do_QueryInterface(mInputAreaContent);
133 0 : if (inputAreaContent) {
134 0 : inputAreaContent->BlurInnerTextBox();
135 : }
136 0 : }
137 :
138 : bool
139 0 : nsDateTimeControlFrame::HasBadInput()
140 : {
141 : nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
142 0 : do_QueryInterface(mInputAreaContent);
143 :
144 0 : bool result = false;
145 0 : if (inputAreaContent) {
146 0 : inputAreaContent->HasBadInput(&result);
147 : }
148 :
149 0 : return result;
150 : }
151 :
152 : nscoord
153 0 : nsDateTimeControlFrame::GetMinISize(gfxContext* aRenderingContext)
154 : {
155 : nscoord result;
156 0 : DISPLAY_MIN_WIDTH(this, result);
157 :
158 0 : nsIFrame* kid = mFrames.FirstChild();
159 0 : if (kid) { // display:none?
160 0 : result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
161 : kid,
162 : nsLayoutUtils::MIN_ISIZE);
163 : } else {
164 0 : result = 0;
165 : }
166 :
167 0 : return result;
168 : }
169 :
170 : nscoord
171 0 : nsDateTimeControlFrame::GetPrefISize(gfxContext* aRenderingContext)
172 : {
173 : nscoord result;
174 0 : DISPLAY_PREF_WIDTH(this, result);
175 :
176 0 : nsIFrame* kid = mFrames.FirstChild();
177 0 : if (kid) { // display:none?
178 0 : result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
179 : kid,
180 : nsLayoutUtils::PREF_ISIZE);
181 : } else {
182 0 : result = 0;
183 : }
184 :
185 0 : return result;
186 : }
187 :
188 : void
189 0 : nsDateTimeControlFrame::Reflow(nsPresContext* aPresContext,
190 : ReflowOutput& aDesiredSize,
191 : const ReflowInput& aReflowInput,
192 : nsReflowStatus& aStatus)
193 : {
194 0 : MarkInReflow();
195 :
196 0 : DO_GLOBAL_REFLOW_COUNT("nsDateTimeControlFrame");
197 0 : DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
198 0 : NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
199 : ("enter nsDateTimeControlFrame::Reflow: availSize=%d,%d",
200 : aReflowInput.AvailableWidth(),
201 : aReflowInput.AvailableHeight()));
202 :
203 0 : NS_ASSERTION(mInputAreaContent, "The input area content must exist!");
204 :
205 0 : const WritingMode myWM = aReflowInput.GetWritingMode();
206 :
207 : // The ISize of our content box, which is the available ISize
208 : // for our anonymous content:
209 0 : const nscoord contentBoxISize = aReflowInput.ComputedISize();
210 0 : nscoord contentBoxBSize = aReflowInput.ComputedBSize();
211 :
212 : // Figure out our border-box sizes as well (by adding borderPadding to
213 : // content-box sizes):
214 : const nscoord borderBoxISize = contentBoxISize +
215 0 : aReflowInput.ComputedLogicalBorderPadding().IStartEnd(myWM);
216 :
217 : nscoord borderBoxBSize;
218 0 : if (contentBoxBSize != NS_INTRINSICSIZE) {
219 0 : borderBoxBSize = contentBoxBSize +
220 0 : aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
221 : } // else, we'll figure out borderBoxBSize after we resolve contentBoxBSize.
222 :
223 0 : nsIFrame* inputAreaFrame = mFrames.FirstChild();
224 0 : if (!inputAreaFrame) { // display:none?
225 0 : if (contentBoxBSize == NS_INTRINSICSIZE) {
226 0 : contentBoxBSize = 0;
227 : borderBoxBSize =
228 0 : aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
229 : }
230 : } else {
231 0 : NS_ASSERTION(inputAreaFrame->GetContent() == mInputAreaContent,
232 : "What is this child doing here?");
233 :
234 0 : ReflowOutput childDesiredSize(aReflowInput);
235 :
236 0 : WritingMode wm = inputAreaFrame->GetWritingMode();
237 0 : LogicalSize availSize = aReflowInput.ComputedSize(wm);
238 0 : availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
239 :
240 : ReflowInput childReflowOuput(aPresContext, aReflowInput,
241 0 : inputAreaFrame, availSize);
242 :
243 : // Convert input area margin into my own writing-mode (in case it differs):
244 : LogicalMargin childMargin =
245 0 : childReflowOuput.ComputedLogicalMargin().ConvertTo(myWM, wm);
246 :
247 : // offsets of input area frame within this frame:
248 : LogicalPoint
249 : childOffset(myWM,
250 0 : aReflowInput.ComputedLogicalBorderPadding().IStart(myWM) +
251 0 : childMargin.IStart(myWM),
252 0 : aReflowInput.ComputedLogicalBorderPadding().BStart(myWM) +
253 0 : childMargin.BStart(myWM));
254 :
255 0 : nsReflowStatus childStatus;
256 : // We initially reflow the child with a dummy containerSize; positioning
257 : // will be fixed later.
258 0 : const nsSize dummyContainerSize;
259 0 : ReflowChild(inputAreaFrame, aPresContext, childDesiredSize,
260 : childReflowOuput, myWM, childOffset, dummyContainerSize, 0,
261 0 : childStatus);
262 0 : MOZ_ASSERT(childStatus.IsFullyComplete(),
263 : "We gave our child unconstrained available block-size, "
264 : "so it should be complete");
265 :
266 : nscoord childMarginBoxBSize =
267 0 : childDesiredSize.BSize(myWM) + childMargin.BStartEnd(myWM);
268 :
269 0 : if (contentBoxBSize == NS_INTRINSICSIZE) {
270 : // We are intrinsically sized -- we should shrinkwrap the input area's
271 : // block-size:
272 0 : contentBoxBSize = childMarginBoxBSize;
273 :
274 : // Make sure we obey min/max-bsize in the case when we're doing intrinsic
275 : // sizing (we get it for free when we have a non-intrinsic
276 : // aReflowInput.ComputedBSize()). Note that we do this before
277 : // adjusting for borderpadding, since ComputedMaxBSize and
278 : // ComputedMinBSize are content heights.
279 : contentBoxBSize =
280 0 : NS_CSS_MINMAX(contentBoxBSize,
281 : aReflowInput.ComputedMinBSize(),
282 0 : aReflowInput.ComputedMaxBSize());
283 :
284 0 : borderBoxBSize = contentBoxBSize +
285 0 : aReflowInput.ComputedLogicalBorderPadding().BStartEnd(myWM);
286 : }
287 :
288 : // Center child in block axis
289 0 : nscoord extraSpace = contentBoxBSize - childMarginBoxBSize;
290 0 : childOffset.B(myWM) += std::max(0, extraSpace / 2);
291 :
292 : // Needed in FinishReflowChild, for logical-to-physical conversion:
293 0 : nsSize borderBoxSize = LogicalSize(myWM, borderBoxISize, borderBoxBSize).
294 0 : GetPhysicalSize(myWM);
295 :
296 : // Place the child
297 : FinishReflowChild(inputAreaFrame, aPresContext, childDesiredSize,
298 0 : &childReflowOuput, myWM, childOffset, borderBoxSize, 0);
299 :
300 : nsSize contentBoxSize =
301 0 : LogicalSize(myWM, contentBoxISize, contentBoxBSize).
302 0 : GetPhysicalSize(myWM);
303 0 : aDesiredSize.SetBlockStartAscent(
304 0 : childDesiredSize.BlockStartAscent() +
305 0 : inputAreaFrame->BStart(aReflowInput.GetWritingMode(),
306 0 : contentBoxSize));
307 : }
308 :
309 0 : LogicalSize logicalDesiredSize(myWM, borderBoxISize, borderBoxBSize);
310 0 : aDesiredSize.SetSize(myWM, logicalDesiredSize);
311 :
312 0 : aDesiredSize.SetOverflowAreasToDesiredBounds();
313 :
314 0 : if (inputAreaFrame) {
315 0 : ConsiderChildOverflow(aDesiredSize.mOverflowAreas, inputAreaFrame);
316 : }
317 :
318 0 : FinishAndStoreOverflow(&aDesiredSize);
319 :
320 0 : aStatus.Reset();
321 :
322 0 : NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
323 : ("exit nsDateTimeControlFrame::Reflow: size=%d,%d",
324 : aDesiredSize.Width(), aDesiredSize.Height()));
325 0 : NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
326 0 : }
327 :
328 : nsresult
329 0 : nsDateTimeControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
330 : {
331 : // Set up "datetimebox" XUL element which will be XBL-bound to the
332 : // actual controls.
333 : nsNodeInfoManager* nodeInfoManager =
334 0 : mContent->GetComposedDoc()->NodeInfoManager();
335 : RefPtr<NodeInfo> nodeInfo =
336 0 : nodeInfoManager->GetNodeInfo(nsGkAtoms::datetimebox, nullptr,
337 0 : kNameSpaceID_XUL, nsIDOMNode::ELEMENT_NODE);
338 0 : NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
339 :
340 0 : NS_TrustedNewXULElement(getter_AddRefs(mInputAreaContent), nodeInfo.forget());
341 0 : aElements.AppendElement(mInputAreaContent);
342 :
343 : nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
344 0 : do_QueryInterface(mInputAreaContent);
345 0 : if (inputAreaContent) {
346 : // Propogate our tabindex.
347 0 : nsAutoString tabIndexStr;
348 0 : if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr)) {
349 0 : inputAreaContent->SetEditAttribute(NS_LITERAL_STRING("tabindex"),
350 0 : tabIndexStr);
351 : }
352 :
353 : // Propagate our readonly state.
354 0 : nsAutoString readonly;
355 0 : if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly)) {
356 0 : inputAreaContent->SetEditAttribute(NS_LITERAL_STRING("readonly"),
357 0 : readonly);
358 : }
359 :
360 0 : SyncDisabledState();
361 : }
362 :
363 0 : return NS_OK;
364 : }
365 :
366 : void
367 0 : nsDateTimeControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
368 : uint32_t aFilter)
369 : {
370 0 : if (mInputAreaContent) {
371 0 : aElements.AppendElement(mInputAreaContent);
372 : }
373 0 : }
374 :
375 : void
376 0 : nsDateTimeControlFrame::SyncDisabledState()
377 : {
378 0 : NS_ASSERTION(mInputAreaContent, "The input area content must exist!");
379 : nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
380 0 : do_QueryInterface(mInputAreaContent);
381 0 : if (!inputAreaContent) {
382 0 : return;
383 : }
384 :
385 0 : EventStates eventStates = mContent->AsElement()->State();
386 0 : if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
387 0 : inputAreaContent->SetEditAttribute(NS_LITERAL_STRING("disabled"),
388 0 : EmptyString());
389 : } else {
390 0 : inputAreaContent->RemoveEditAttribute(NS_LITERAL_STRING("disabled"));
391 : }
392 : }
393 :
394 : nsresult
395 0 : nsDateTimeControlFrame::AttributeChanged(int32_t aNameSpaceID,
396 : nsIAtom* aAttribute,
397 : int32_t aModType)
398 : {
399 0 : NS_ASSERTION(mInputAreaContent, "The input area content must exist!");
400 :
401 : // nsGkAtoms::disabled is handled by SyncDisabledState
402 0 : if (aNameSpaceID == kNameSpaceID_None) {
403 0 : if (aAttribute == nsGkAtoms::value ||
404 0 : aAttribute == nsGkAtoms::readonly ||
405 0 : aAttribute == nsGkAtoms::tabindex) {
406 0 : MOZ_ASSERT(mContent->IsHTMLElement(nsGkAtoms::input), "bad cast");
407 0 : auto contentAsInputElem = static_cast<dom::HTMLInputElement*>(mContent);
408 : // If script changed the <input>'s type before setting these attributes
409 : // then we don't need to do anything since we are going to be reframed.
410 0 : if (contentAsInputElem->ControlType() == NS_FORM_INPUT_TIME ||
411 0 : contentAsInputElem->ControlType() == NS_FORM_INPUT_DATE) {
412 : nsCOMPtr<nsIDateTimeInputArea> inputAreaContent =
413 0 : do_QueryInterface(mInputAreaContent);
414 0 : if (aAttribute == nsGkAtoms::value) {
415 0 : if (inputAreaContent) {
416 0 : nsContentUtils::AddScriptRunner(NewRunnableMethod(
417 : "nsIDateTimeInputArea::NotifyInputElementValueChanged",
418 : inputAreaContent,
419 0 : &nsIDateTimeInputArea::NotifyInputElementValueChanged));
420 : }
421 : } else {
422 0 : if (aModType == nsIDOMMutationEvent::REMOVAL) {
423 0 : if (inputAreaContent) {
424 0 : nsAtomString name(aAttribute);
425 0 : inputAreaContent->RemoveEditAttribute(name);
426 : }
427 : } else {
428 0 : MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION ||
429 : aModType == nsIDOMMutationEvent::MODIFICATION);
430 0 : if (inputAreaContent) {
431 0 : nsAtomString name(aAttribute);
432 0 : nsAutoString value;
433 0 : mContent->GetAttr(aNameSpaceID, aAttribute, value);
434 0 : inputAreaContent->SetEditAttribute(name, value);
435 : }
436 : }
437 : }
438 : }
439 : }
440 : }
441 :
442 0 : return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
443 0 : aModType);
444 : }
445 :
446 : void
447 0 : nsDateTimeControlFrame::ContentStatesChanged(EventStates aStates)
448 : {
449 0 : if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
450 0 : nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
451 : }
452 0 : }
|