Coverage Report

Created: 2018-09-25 14:53

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