Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/forms/nsFileControlFrame.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
#include "nsFileControlFrame.h"
8
9
#include "nsGkAtoms.h"
10
#include "nsCOMPtr.h"
11
#include "nsIDocument.h"
12
#include "mozilla/dom/NodeInfo.h"
13
#include "mozilla/dom/Element.h"
14
#include "mozilla/dom/DOMStringList.h"
15
#include "mozilla/dom/DataTransfer.h"
16
#include "mozilla/dom/Directory.h"
17
#include "mozilla/dom/DragEvent.h"
18
#include "mozilla/dom/Event.h"
19
#include "mozilla/dom/FileList.h"
20
#include "mozilla/dom/HTMLButtonElement.h"
21
#include "mozilla/dom/HTMLInputElement.h"
22
#include "mozilla/dom/MutationEventBinding.h"
23
#include "mozilla/Preferences.h"
24
#include "mozilla/StaticPrefs.h"
25
#include "nsNodeInfoManager.h"
26
#include "nsContentCreatorFunctions.h"
27
#include "nsContentUtils.h"
28
#include "mozilla/EventStates.h"
29
#include "nsTextNode.h"
30
31
using namespace mozilla;
32
using namespace mozilla::dom;
33
34
nsIFrame*
35
NS_NewFileControlFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle)
36
0
{
37
0
  return new (aPresShell) nsFileControlFrame(aStyle);
38
0
}
39
40
NS_IMPL_FRAMEARENA_HELPERS(nsFileControlFrame)
41
42
nsFileControlFrame::nsFileControlFrame(ComputedStyle* aStyle)
43
  : nsBlockFrame(aStyle, kClassID)
44
0
{
45
0
  AddStateBits(NS_BLOCK_FLOAT_MGR);
46
0
}
47
48
49
void
50
nsFileControlFrame::Init(nsIContent*       aContent,
51
                         nsContainerFrame* aParent,
52
                         nsIFrame*         aPrevInFlow)
53
0
{
54
0
  nsBlockFrame::Init(aContent, aParent, aPrevInFlow);
55
0
56
0
  mMouseListener = new DnDListener(this);
57
0
}
58
59
void
60
nsFileControlFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
61
0
{
62
0
  NS_ENSURE_TRUE_VOID(mContent);
63
0
64
0
  // Remove the events.
65
0
  if (mContent) {
66
0
    mContent->RemoveSystemEventListener(NS_LITERAL_STRING("drop"),
67
0
                                        mMouseListener, false);
68
0
    mContent->RemoveSystemEventListener(NS_LITERAL_STRING("dragover"),
69
0
                                        mMouseListener, false);
70
0
  }
71
0
72
0
  aPostDestroyData.AddAnonymousContent(mTextContent.forget());
73
0
  aPostDestroyData.AddAnonymousContent(mBrowseFilesOrDirs.forget());
74
0
75
0
  mMouseListener->ForgetFrame();
76
0
  nsBlockFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
77
0
}
78
79
static already_AddRefed<Element>
80
MakeAnonButton(nsIDocument* aDoc, const char* labelKey,
81
               HTMLInputElement* aInputElement,
82
               const nsAString& aAccessKey)
83
0
{
84
0
  RefPtr<Element> button = aDoc->CreateHTMLElement(nsGkAtoms::button);
85
0
  // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
86
0
  // attribute.
87
0
  button->SetIsNativeAnonymousRoot();
88
0
  button->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
89
0
                  NS_LITERAL_STRING("button"), false);
90
0
91
0
  // Set the file picking button text depending on the current locale.
92
0
  nsAutoString buttonTxt;
93
0
  nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
94
0
                                     labelKey, buttonTxt);
95
0
96
0
  // Set the browse button text. It's a bit of a pain to do because we want to
97
0
  // make sure we are not notifying.
98
0
  RefPtr<nsTextNode> textContent =
99
0
    new nsTextNode(button->NodeInfo()->NodeInfoManager());
100
0
101
0
  textContent->SetText(buttonTxt, false);
102
0
103
0
  nsresult rv = button->AppendChildTo(textContent, false);
104
0
  if (NS_FAILED(rv)) {
105
0
    return nullptr;
106
0
  }
107
0
108
0
  // Make sure access key and tab order for the element actually redirect to the
109
0
  // file picking button.
110
0
  RefPtr<HTMLButtonElement> buttonElement =
111
0
    HTMLButtonElement::FromNodeOrNull(button);
112
0
113
0
  if (!aAccessKey.IsEmpty()) {
114
0
    buttonElement->SetAccessKey(aAccessKey, IgnoreErrors());
115
0
  }
116
0
117
0
  // Both elements are given the same tab index so that the user can tab
118
0
  // to the file control at the correct index, and then between the two
119
0
  // buttons.
120
0
  buttonElement->SetTabIndex(aInputElement->TabIndex(), IgnoreErrors());
121
0
122
0
  return button.forget();
123
0
}
124
125
nsresult
126
nsFileControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
127
0
{
128
0
  nsCOMPtr<nsIDocument> doc = mContent->GetComposedDoc();
129
0
130
0
  RefPtr<HTMLInputElement> fileContent = HTMLInputElement::FromNodeOrNull(mContent);
131
0
132
0
  // The access key is transferred to the "Choose files..." button only. In
133
0
  // effect that access key allows access to the control via that button, then
134
0
  // the user can tab between the two buttons.
135
0
  nsAutoString accessKey;
136
0
  fileContent->GetAccessKey(accessKey);
137
0
138
0
  mBrowseFilesOrDirs = MakeAnonButton(doc, "Browse", fileContent, accessKey);
139
0
  if (!mBrowseFilesOrDirs || !aElements.AppendElement(mBrowseFilesOrDirs)) {
140
0
    return NS_ERROR_OUT_OF_MEMORY;
141
0
  }
142
0
143
0
  // Create and setup the text showing the selected files.
144
0
  RefPtr<NodeInfo> nodeInfo;
145
0
  nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::label, nullptr,
146
0
                                                 kNameSpaceID_XUL,
147
0
                                                 nsINode::ELEMENT_NODE);
148
0
  NS_TrustedNewXULElement(getter_AddRefs(mTextContent), nodeInfo.forget());
149
0
  // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
150
0
  // attribute.
151
0
  mTextContent->SetIsNativeAnonymousRoot();
152
0
  mTextContent->SetAttr(kNameSpaceID_None, nsGkAtoms::crop,
153
0
                        NS_LITERAL_STRING("center"), false);
154
0
155
0
  // Update the displayed text to reflect the current element's value.
156
0
  nsAutoString value;
157
0
  HTMLInputElement::FromNode(mContent)->GetDisplayFileName(value);
158
0
  UpdateDisplayedValue(value, false);
159
0
160
0
  if (!aElements.AppendElement(mTextContent)) {
161
0
    return NS_ERROR_OUT_OF_MEMORY;
162
0
  }
163
0
164
0
  // We should be able to interact with the element by doing drag and drop.
165
0
  mContent->AddSystemEventListener(NS_LITERAL_STRING("drop"),
166
0
                                   mMouseListener, false);
167
0
  mContent->AddSystemEventListener(NS_LITERAL_STRING("dragover"),
168
0
                                   mMouseListener, false);
169
0
170
0
  SyncDisabledState();
171
0
172
0
  return NS_OK;
173
0
}
174
175
void
176
nsFileControlFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
177
                                             uint32_t aFilter)
178
0
{
179
0
  if (mBrowseFilesOrDirs) {
180
0
    aElements.AppendElement(mBrowseFilesOrDirs);
181
0
  }
182
0
183
0
  if (mTextContent) {
184
0
    aElements.AppendElement(mTextContent);
185
0
  }
186
0
}
187
188
0
NS_QUERYFRAME_HEAD(nsFileControlFrame)
189
0
  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
190
0
  NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
191
0
NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
192
193
void
194
nsFileControlFrame::SetFocus(bool aOn, bool aRepaint)
195
0
{
196
0
}
197
198
static void
199
AppendBlobImplAsDirectory(nsTArray<OwningFileOrDirectory>& aArray,
200
                          BlobImpl* aBlobImpl,
201
                          nsIContent* aContent)
202
0
{
203
0
  MOZ_ASSERT(aBlobImpl);
204
0
  MOZ_ASSERT(aBlobImpl->IsDirectory());
205
0
206
0
  nsAutoString fullpath;
207
0
  ErrorResult err;
208
0
  aBlobImpl->GetMozFullPath(fullpath, SystemCallerGuarantee(), err);
209
0
  if (err.Failed()) {
210
0
    err.SuppressException();
211
0
    return;
212
0
  }
213
0
214
0
  nsCOMPtr<nsIFile> file;
215
0
  nsresult rv = NS_NewLocalFile(fullpath, true, getter_AddRefs(file));
216
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
217
0
    return;
218
0
  }
219
0
220
0
  nsPIDOMWindowInner* inner = aContent->OwnerDoc()->GetInnerWindow();
221
0
  if (!inner || !inner->IsCurrentInnerWindow()) {
222
0
    return;
223
0
  }
224
0
225
0
  RefPtr<Directory> directory =
226
0
    Directory::Create(inner, file);
227
0
  MOZ_ASSERT(directory);
228
0
229
0
  OwningFileOrDirectory* element = aArray.AppendElement();
230
0
  element->SetAsDirectory() = directory;
231
0
}
232
233
/**
234
 * This is called when we receive a drop or a dragover.
235
 */
236
NS_IMETHODIMP
237
nsFileControlFrame::DnDListener::HandleEvent(Event* aEvent)
238
0
{
239
0
  NS_ASSERTION(mFrame, "We should have been unregistered");
240
0
241
0
  if (aEvent->DefaultPrevented()) {
242
0
    return NS_OK;
243
0
  }
244
0
245
0
  DragEvent* dragEvent = aEvent->AsDragEvent();
246
0
  if (!dragEvent) {
247
0
    return NS_OK;
248
0
  }
249
0
250
0
  RefPtr<DataTransfer> dataTransfer = dragEvent->GetDataTransfer();
251
0
  if (!IsValidDropData(dataTransfer)) {
252
0
    return NS_OK;
253
0
  }
254
0
255
0
256
0
  RefPtr<HTMLInputElement> inputElement =
257
0
    HTMLInputElement::FromNode(mFrame->GetContent());
258
0
  bool supportsMultiple =
259
0
    inputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::multiple);
260
0
  if (!CanDropTheseFiles(dataTransfer, supportsMultiple)) {
261
0
    dataTransfer->SetDropEffect(NS_LITERAL_STRING("none"));
262
0
    aEvent->StopPropagation();
263
0
    return NS_OK;
264
0
  }
265
0
266
0
  nsAutoString eventType;
267
0
  aEvent->GetType(eventType);
268
0
  if (eventType.EqualsLiteral("dragover")) {
269
0
    // Prevent default if we can accept this drag data
270
0
    aEvent->PreventDefault();
271
0
    return NS_OK;
272
0
  }
273
0
274
0
  if (eventType.EqualsLiteral("drop")) {
275
0
    aEvent->StopPropagation();
276
0
    aEvent->PreventDefault();
277
0
278
0
    RefPtr<FileList> fileList =
279
0
      dataTransfer->GetFiles(*nsContentUtils::GetSystemPrincipal());
280
0
281
0
    RefPtr<BlobImpl> webkitDir;
282
0
    nsresult rv =
283
0
      GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir));
284
0
    NS_ENSURE_SUCCESS(rv, NS_OK);
285
0
286
0
    nsTArray<OwningFileOrDirectory> array;
287
0
    if (webkitDir) {
288
0
      AppendBlobImplAsDirectory(array, webkitDir, inputElement);
289
0
      inputElement->MozSetDndFilesAndDirectories(array);
290
0
    } else {
291
0
      bool blinkFileSystemEnabled =
292
0
        Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false);
293
0
      bool dirPickerEnabled =
294
0
        Preferences::GetBool("dom.input.dirpicker", false);
295
0
      if (blinkFileSystemEnabled || dirPickerEnabled) {
296
0
        FileList* files = static_cast<FileList*>(fileList.get());
297
0
        if (files) {
298
0
          for (uint32_t i = 0; i < files->Length(); ++i) {
299
0
            File* file = files->Item(i);
300
0
            if (file) {
301
0
              if (file->Impl() && file->Impl()->IsDirectory()) {
302
0
                AppendBlobImplAsDirectory(array, file->Impl(), inputElement);
303
0
              } else {
304
0
                OwningFileOrDirectory* element = array.AppendElement();
305
0
                element->SetAsFile() = file;
306
0
              }
307
0
            }
308
0
          }
309
0
        }
310
0
      }
311
0
312
0
      // Entries API.
313
0
      if (blinkFileSystemEnabled) {
314
0
        // This is rather ugly. Pass the directories as Files using SetFiles,
315
0
        // but then if blink filesystem API is enabled, it wants
316
0
        // FileOrDirectory array.
317
0
        inputElement->SetFiles(fileList, true);
318
0
        inputElement->UpdateEntries(array);
319
0
      }
320
0
      // Directory Upload API
321
0
      else if (dirPickerEnabled) {
322
0
        inputElement->SetFilesOrDirectories(array, true);
323
0
      }
324
0
      // Normal DnD
325
0
      else {
326
0
        inputElement->SetFiles(fileList, true);
327
0
      }
328
0
329
0
      nsContentUtils::DispatchTrustedEvent(inputElement->OwnerDoc(),
330
0
                                           static_cast<nsINode*>(inputElement),
331
0
                                           NS_LITERAL_STRING("input"),
332
0
                                           CanBubble::eYes,
333
0
                                           Cancelable::eNo);
334
0
      nsContentUtils::DispatchTrustedEvent(inputElement->OwnerDoc(),
335
0
                                           static_cast<nsINode*>(inputElement),
336
0
                                           NS_LITERAL_STRING("change"),
337
0
                                           CanBubble::eYes,
338
0
                                           Cancelable::eNo);
339
0
    }
340
0
  }
341
0
342
0
  return NS_OK;
343
0
}
344
345
nsresult
346
nsFileControlFrame::DnDListener::GetBlobImplForWebkitDirectory(FileList* aFileList,
347
                                                               BlobImpl** aBlobImpl)
348
0
{
349
0
  *aBlobImpl = nullptr;
350
0
351
0
  HTMLInputElement* inputElement =
352
0
    HTMLInputElement::FromNode(mFrame->GetContent());
353
0
  bool webkitDirPicker =
354
0
    StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
355
0
    inputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory);
356
0
  if (!webkitDirPicker) {
357
0
    return NS_OK;
358
0
  }
359
0
360
0
  if (!aFileList) {
361
0
    return NS_ERROR_FAILURE;
362
0
  }
363
0
364
0
  // webkitdirectory doesn't care about the length of the file list but
365
0
  // only about the first item on it.
366
0
  uint32_t len = aFileList->Length();
367
0
  if (len) {
368
0
    File* file = aFileList->Item(0);
369
0
    if (file) {
370
0
      BlobImpl* impl = file->Impl();
371
0
      if (impl && impl->IsDirectory()) {
372
0
        RefPtr<BlobImpl> retVal = impl;
373
0
        retVal.swap(*aBlobImpl);
374
0
        return NS_OK;
375
0
      }
376
0
    }
377
0
  }
378
0
379
0
  return NS_ERROR_FAILURE;
380
0
}
381
382
bool
383
nsFileControlFrame::DnDListener::IsValidDropData(DataTransfer* aDataTransfer)
384
0
{
385
0
  if (!aDataTransfer) {
386
0
    return false;
387
0
  }
388
0
389
0
  // We only support dropping files onto a file upload control
390
0
  nsTArray<nsString> types;
391
0
  aDataTransfer->GetTypes(types, CallerType::System);
392
0
393
0
  return types.Contains(NS_LITERAL_STRING("Files"));
394
0
}
395
396
bool
397
nsFileControlFrame::DnDListener::CanDropTheseFiles(DataTransfer* aDataTransfer,
398
                                                   bool aSupportsMultiple)
399
0
{
400
0
  RefPtr<FileList> fileList =
401
0
    aDataTransfer->GetFiles(*nsContentUtils::GetSystemPrincipal());
402
0
403
0
  RefPtr<BlobImpl> webkitDir;
404
0
  nsresult rv =
405
0
    GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir));
406
0
  // Just check if either there isn't webkitdirectory attribute, or
407
0
  // fileList has a directory which can be dropped to the element.
408
0
  // No need to use webkitDir for anything here.
409
0
  NS_ENSURE_SUCCESS(rv, false);
410
0
411
0
  uint32_t listLength = 0;
412
0
  if (fileList) {
413
0
    listLength = fileList->Length();
414
0
  }
415
0
  return listLength <= 1 || aSupportsMultiple;
416
0
}
417
418
nscoord
419
nsFileControlFrame::GetMinISize(gfxContext *aRenderingContext)
420
0
{
421
0
  nscoord result;
422
0
  DISPLAY_MIN_INLINE_SIZE(this, result);
423
0
424
0
  // Our min inline size is our pref inline size
425
0
  result = GetPrefISize(aRenderingContext);
426
0
  return result;
427
0
}
428
429
void
430
nsFileControlFrame::SyncDisabledState()
431
0
{
432
0
  EventStates eventStates = mContent->AsElement()->State();
433
0
  if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) {
434
0
    mBrowseFilesOrDirs->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled,
435
0
                                EmptyString(), true);
436
0
  } else {
437
0
    mBrowseFilesOrDirs->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
438
0
  }
439
0
}
440
441
nsresult
442
nsFileControlFrame::AttributeChanged(int32_t  aNameSpaceID,
443
                                     nsAtom* aAttribute,
444
                                     int32_t  aModType)
445
0
{
446
0
  if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::tabindex) {
447
0
    if (aModType == MutationEvent_Binding::REMOVAL) {
448
0
      mBrowseFilesOrDirs->UnsetAttr(aNameSpaceID, aAttribute, true);
449
0
    } else {
450
0
      nsAutoString value;
451
0
      mContent->AsElement()->GetAttr(aNameSpaceID, aAttribute, value);
452
0
      mBrowseFilesOrDirs->SetAttr(aNameSpaceID, aAttribute, value, true);
453
0
    }
454
0
  }
455
0
456
0
  return nsBlockFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
457
0
}
458
459
void
460
nsFileControlFrame::ContentStatesChanged(EventStates aStates)
461
0
{
462
0
  if (aStates.HasState(NS_EVENT_STATE_DISABLED)) {
463
0
    nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
464
0
  }
465
0
}
466
467
#ifdef DEBUG_FRAME_DUMP
468
nsresult
469
nsFileControlFrame::GetFrameName(nsAString& aResult) const
470
{
471
  return MakeFrameName(NS_LITERAL_STRING("FileControl"), aResult);
472
}
473
#endif
474
475
void
476
nsFileControlFrame::UpdateDisplayedValue(const nsAString& aValue, bool aNotify)
477
0
{
478
0
  mTextContent->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, aNotify);
479
0
}
480
481
nsresult
482
nsFileControlFrame::SetFormProperty(nsAtom* aName,
483
                                    const nsAString& aValue)
484
0
{
485
0
  if (nsGkAtoms::value == aName) {
486
0
    UpdateDisplayedValue(aValue, true);
487
0
  }
488
0
  return NS_OK;
489
0
}
490
491
void
492
nsFileControlFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
493
                                     const nsDisplayListSet& aLists)
494
0
{
495
0
  BuildDisplayListForInline(aBuilder, aLists);
496
0
}
497
498
#ifdef ACCESSIBILITY
499
a11y::AccType
500
nsFileControlFrame::AccessibleType()
501
0
{
502
0
  return a11y::eHTMLFileInputType;
503
0
}
504
#endif
505
506
////////////////////////////////////////////////////////////
507
// Mouse listener implementation
508
509
NS_IMPL_ISUPPORTS(nsFileControlFrame::MouseListener,
510
                  nsIDOMEventListener)