tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsFileControlFrame.cpp (13843B)


      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 "MiddleCroppingBlockFrame.h"
     10 #include "gfxContext.h"
     11 #include "mozilla/Preferences.h"
     12 #include "mozilla/PresShell.h"
     13 #include "mozilla/StaticPrefs_dom.h"
     14 #include "mozilla/TextEditor.h"
     15 #include "mozilla/dom/BlobImpl.h"
     16 #include "mozilla/dom/DOMStringList.h"
     17 #include "mozilla/dom/DataTransfer.h"
     18 #include "mozilla/dom/Directory.h"
     19 #include "mozilla/dom/Document.h"
     20 #include "mozilla/dom/DragEvent.h"
     21 #include "mozilla/dom/Element.h"
     22 #include "mozilla/dom/Event.h"
     23 #include "mozilla/dom/FileList.h"
     24 #include "mozilla/dom/HTMLButtonElement.h"
     25 #include "mozilla/dom/HTMLInputElement.h"
     26 #include "mozilla/dom/NodeInfo.h"
     27 #include "mozilla/dom/UnionTypes.h"
     28 #include "nsCOMPtr.h"
     29 #include "nsContentCreatorFunctions.h"
     30 #include "nsContentUtils.h"
     31 #include "nsGkAtoms.h"
     32 #include "nsIFile.h"
     33 #include "nsIFrame.h"
     34 #include "nsLayoutUtils.h"
     35 #include "nsNodeInfoManager.h"
     36 #include "nsTextFrame.h"
     37 #include "nsTextNode.h"
     38 
     39 using namespace mozilla;
     40 using namespace mozilla::dom;
     41 
     42 nsIFrame* NS_NewFileControlFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
     43  return new (aPresShell)
     44      nsFileControlFrame(aStyle, aPresShell->GetPresContext());
     45 }
     46 
     47 NS_IMPL_FRAMEARENA_HELPERS(nsFileControlFrame)
     48 
     49 nsFileControlFrame::nsFileControlFrame(ComputedStyle* aStyle,
     50                                       nsPresContext* aPresContext)
     51    : nsBlockFrame(aStyle, aPresContext, kClassID) {}
     52 
     53 void nsFileControlFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
     54                              nsIFrame* aPrevInFlow) {
     55  nsBlockFrame::Init(aContent, aParent, aPrevInFlow);
     56 
     57  mMouseListener = new DnDListener(this);
     58 }
     59 
     60 void nsFileControlFrame::Reflow(nsPresContext* aPresContext,
     61                                ReflowOutput& aReflowOutput,
     62                                const ReflowInput& aReflowInput,
     63                                nsReflowStatus& aStatus) {
     64  nsBlockFrame::Reflow(aPresContext, aReflowOutput, aReflowInput, aStatus);
     65 
     66  // Form control frame should be monolithic, and cannot be split, so our reflow
     67  // status should be fully-complete.
     68  aStatus.Reset();
     69 }
     70 
     71 void nsFileControlFrame::Destroy(DestroyContext& aContext) {
     72  NS_ENSURE_TRUE_VOID(mContent);
     73 
     74  // Remove the events.
     75  if (mContent) {
     76    mContent->RemoveSystemEventListener(u"drop"_ns, mMouseListener, false);
     77    mContent->RemoveSystemEventListener(u"dragover"_ns, mMouseListener, false);
     78  }
     79 
     80  aContext.AddAnonymousContent(mTextContent.forget());
     81  aContext.AddAnonymousContent(mBrowseFilesOrDirs.forget());
     82 
     83  mMouseListener->ForgetFrame();
     84  nsBlockFrame::Destroy(aContext);
     85 }
     86 
     87 static already_AddRefed<Element> MakeAnonButton(
     88    Document* aDoc, const char* labelKey, HTMLInputElement* aInputElement) {
     89  RefPtr<Element> button = aDoc->CreateHTMLElement(nsGkAtoms::button);
     90  // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
     91  // attribute.
     92  button->SetIsNativeAnonymousRoot();
     93  button->SetPseudoElementType(PseudoStyleType::fileSelectorButton);
     94 
     95  // Set the file picking button text depending on the current locale.
     96  nsAutoString buttonTxt;
     97  nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
     98                                          labelKey, aDoc, buttonTxt);
     99 
    100  auto* nim = aDoc->NodeInfoManager();
    101  // Set the browse button text. It's a bit of a pain to do because we want to
    102  // make sure we are not notifying.
    103  RefPtr textContent = new (nim) nsTextNode(nim);
    104  textContent->SetText(buttonTxt, false);
    105 
    106  IgnoredErrorResult error;
    107  button->AppendChildTo(textContent, false, error);
    108  if (error.Failed()) {
    109    return nullptr;
    110  }
    111 
    112  auto* buttonElement = HTMLButtonElement::FromNode(button);
    113  // We allow tabbing over the input itself, not the button.
    114  buttonElement->SetTabIndex(-1, IgnoreErrors());
    115  return button.forget();
    116 }
    117 
    118 nsresult nsFileControlFrame::CreateAnonymousContent(
    119    nsTArray<ContentInfo>& aElements) {
    120  nsCOMPtr<Document> doc = mContent->GetComposedDoc();
    121  RefPtr fileContent = HTMLInputElement::FromNode(mContent);
    122 
    123  mBrowseFilesOrDirs = MakeAnonButton(doc, "Browse", fileContent);
    124  if (!mBrowseFilesOrDirs) {
    125    return NS_ERROR_OUT_OF_MEMORY;
    126  }
    127  // XXX(Bug 1631371) Check if this should use a fallible operation as it
    128  // pretended earlier, or change the return type to void.
    129  aElements.AppendElement(mBrowseFilesOrDirs);
    130 
    131  // Create and setup the text showing the selected files.
    132  mTextContent = doc->CreateHTMLElement(nsGkAtoms::label);
    133  // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
    134  // attribute.
    135  mTextContent->SetIsNativeAnonymousRoot();
    136  RefPtr<nsTextNode> text =
    137      new (doc->NodeInfoManager()) nsTextNode(doc->NodeInfoManager());
    138  mTextContent->AppendChildTo(text, false, IgnoreErrors());
    139 
    140  aElements.AppendElement(mTextContent);
    141 
    142  // We should be able to interact with the element by doing drag and drop.
    143  mContent->AddSystemEventListener(u"drop"_ns, mMouseListener, false);
    144  mContent->AddSystemEventListener(u"dragover"_ns, mMouseListener, false);
    145 
    146  SyncDisabledState();
    147 
    148  return NS_OK;
    149 }
    150 
    151 void nsFileControlFrame::AppendAnonymousContentTo(
    152    nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
    153  if (mBrowseFilesOrDirs) {
    154    aElements.AppendElement(mBrowseFilesOrDirs);
    155  }
    156 
    157  if (mTextContent) {
    158    aElements.AppendElement(mTextContent);
    159  }
    160 }
    161 
    162 NS_QUERYFRAME_HEAD(nsFileControlFrame)
    163  NS_QUERYFRAME_ENTRY(nsFileControlFrame)
    164  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
    165 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
    166 
    167 static void AppendBlobImplAsDirectory(nsTArray<OwningFileOrDirectory>& aArray,
    168                                      BlobImpl* aBlobImpl,
    169                                      nsIContent* aContent) {
    170  MOZ_ASSERT(aBlobImpl);
    171  MOZ_ASSERT(aBlobImpl->IsDirectory());
    172 
    173  nsAutoString fullpath;
    174  ErrorResult err;
    175  aBlobImpl->GetMozFullPath(fullpath, SystemCallerGuarantee(), err);
    176  if (err.Failed()) {
    177    err.SuppressException();
    178    return;
    179  }
    180 
    181  nsCOMPtr<nsIFile> file;
    182  nsresult rv = NS_NewLocalFile(fullpath, getter_AddRefs(file));
    183  if (NS_WARN_IF(NS_FAILED(rv))) {
    184    return;
    185  }
    186 
    187  nsPIDOMWindowInner* inner = aContent->OwnerDoc()->GetInnerWindow();
    188  if (!inner || !inner->IsCurrentInnerWindow()) {
    189    return;
    190  }
    191 
    192  RefPtr<Directory> directory = Directory::Create(inner->AsGlobal(), file);
    193  MOZ_ASSERT(directory);
    194 
    195  OwningFileOrDirectory* element = aArray.AppendElement();
    196  element->SetAsDirectory() = directory;
    197 }
    198 
    199 /**
    200 * This is called when we receive a drop or a dragover.
    201 */
    202 NS_IMETHODIMP
    203 nsFileControlFrame::DnDListener::HandleEvent(Event* aEvent) {
    204  NS_ASSERTION(mFrame, "We should have been unregistered");
    205 
    206  if (aEvent->DefaultPrevented()) {
    207    return NS_OK;
    208  }
    209 
    210  DragEvent* dragEvent = aEvent->AsDragEvent();
    211  if (!dragEvent) {
    212    return NS_OK;
    213  }
    214 
    215  RefPtr<DataTransfer> dataTransfer = dragEvent->GetDataTransfer();
    216  if (!IsValidDropData(dataTransfer)) {
    217    return NS_OK;
    218  }
    219 
    220  RefPtr<HTMLInputElement> inputElement =
    221      HTMLInputElement::FromNode(mFrame->GetContent());
    222  bool supportsMultiple = inputElement->HasAttr(nsGkAtoms::multiple);
    223  if (!CanDropTheseFiles(dataTransfer, supportsMultiple)) {
    224    dataTransfer->SetDropEffect(u"none"_ns);
    225    aEvent->StopPropagation();
    226    return NS_OK;
    227  }
    228 
    229  nsAutoString eventType;
    230  aEvent->GetType(eventType);
    231  if (eventType.EqualsLiteral("dragover")) {
    232    // Prevent default if we can accept this drag data
    233    aEvent->PreventDefault();
    234    return NS_OK;
    235  }
    236 
    237  if (eventType.EqualsLiteral("drop")) {
    238    aEvent->StopPropagation();
    239    aEvent->PreventDefault();
    240 
    241    RefPtr<FileList> fileList =
    242        dataTransfer->GetFiles(*nsContentUtils::GetSystemPrincipal());
    243 
    244    RefPtr<BlobImpl> webkitDir;
    245    nsresult rv =
    246        GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir));
    247    NS_ENSURE_SUCCESS(rv, NS_OK);
    248 
    249    nsTArray<OwningFileOrDirectory> array;
    250    if (webkitDir) {
    251      AppendBlobImplAsDirectory(array, webkitDir, inputElement);
    252      inputElement->MozSetDndFilesAndDirectories(array);
    253    } else {
    254      bool blinkFileSystemEnabled =
    255          StaticPrefs::dom_webkitBlink_filesystem_enabled();
    256      if (blinkFileSystemEnabled) {
    257        FileList* files = static_cast<FileList*>(fileList.get());
    258        if (files) {
    259          for (uint32_t i = 0; i < files->Length(); ++i) {
    260            File* file = files->Item(i);
    261            if (file) {
    262              if (file->Impl() && file->Impl()->IsDirectory()) {
    263                AppendBlobImplAsDirectory(array, file->Impl(), inputElement);
    264              } else {
    265                OwningFileOrDirectory* element = array.AppendElement();
    266                element->SetAsFile() = file;
    267              }
    268            }
    269          }
    270        }
    271      }
    272 
    273      // Entries API.
    274      if (blinkFileSystemEnabled) {
    275        // This is rather ugly. Pass the directories as Files using SetFiles,
    276        // but then if blink filesystem API is enabled, it wants
    277        // FileOrDirectory array.
    278        inputElement->SetFiles(fileList, true);
    279        inputElement->UpdateEntries(array);
    280      }
    281      // Normal DnD
    282      else {
    283        inputElement->SetFiles(fileList, true);
    284      }
    285 
    286      RefPtr<TextEditor> textEditor;
    287      DebugOnly<nsresult> rvIgnored =
    288          nsContentUtils::DispatchInputEvent(inputElement);
    289      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
    290                           "Failed to dispatch input event");
    291      nsContentUtils::DispatchTrustedEvent(inputElement->OwnerDoc(),
    292                                           inputElement, u"change"_ns,
    293                                           CanBubble::eYes, Cancelable::eNo);
    294    }
    295  }
    296 
    297  return NS_OK;
    298 }
    299 
    300 nsresult nsFileControlFrame::DnDListener::GetBlobImplForWebkitDirectory(
    301    FileList* aFileList, BlobImpl** aBlobImpl) {
    302  *aBlobImpl = nullptr;
    303 
    304  HTMLInputElement* inputElement =
    305      HTMLInputElement::FromNode(mFrame->GetContent());
    306  bool webkitDirPicker = StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
    307                         inputElement->HasAttr(nsGkAtoms::webkitdirectory);
    308  if (!webkitDirPicker) {
    309    return NS_OK;
    310  }
    311 
    312  if (!aFileList) {
    313    return NS_ERROR_FAILURE;
    314  }
    315 
    316  // webkitdirectory doesn't care about the length of the file list but
    317  // only about the first item on it.
    318  uint32_t len = aFileList->Length();
    319  if (len) {
    320    File* file = aFileList->Item(0);
    321    if (file) {
    322      BlobImpl* impl = file->Impl();
    323      if (impl && impl->IsDirectory()) {
    324        RefPtr<BlobImpl> retVal = impl;
    325        retVal.swap(*aBlobImpl);
    326        return NS_OK;
    327      }
    328    }
    329  }
    330 
    331  return NS_ERROR_FAILURE;
    332 }
    333 
    334 bool nsFileControlFrame::DnDListener::IsValidDropData(
    335    DataTransfer* aDataTransfer) {
    336  if (!aDataTransfer) {
    337    return false;
    338  }
    339 
    340  // We only support dropping files onto a file upload control
    341  return aDataTransfer->HasFile();
    342 }
    343 
    344 bool nsFileControlFrame::DnDListener::CanDropTheseFiles(
    345    DataTransfer* aDataTransfer, bool aSupportsMultiple) {
    346  RefPtr<FileList> fileList =
    347      aDataTransfer->GetFiles(*nsContentUtils::GetSystemPrincipal());
    348 
    349  RefPtr<BlobImpl> webkitDir;
    350  nsresult rv =
    351      GetBlobImplForWebkitDirectory(fileList, getter_AddRefs(webkitDir));
    352  // Just check if either there isn't webkitdirectory attribute, or
    353  // fileList has a directory which can be dropped to the element.
    354  // No need to use webkitDir for anything here.
    355  NS_ENSURE_SUCCESS(rv, false);
    356 
    357  uint32_t listLength = 0;
    358  if (fileList) {
    359    listLength = fileList->Length();
    360  }
    361  return listLength <= 1 || aSupportsMultiple;
    362 }
    363 
    364 void nsFileControlFrame::SyncDisabledState() {
    365  if (mContent->AsElement()->State().HasState(ElementState::DISABLED)) {
    366    mBrowseFilesOrDirs->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, u""_ns,
    367                                true);
    368  } else {
    369    mBrowseFilesOrDirs->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true);
    370  }
    371 }
    372 
    373 void nsFileControlFrame::ElementStateChanged(ElementState aStates) {
    374  if (aStates.HasState(ElementState::DISABLED)) {
    375    nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this));
    376  }
    377 }
    378 
    379 #ifdef DEBUG_FRAME_DUMP
    380 nsresult nsFileControlFrame::GetFrameName(nsAString& aResult) const {
    381  return MakeFrameName(u"FileControl"_ns, aResult);
    382 }
    383 #endif
    384 
    385 void nsFileControlFrame::SelectedFilesUpdated() {
    386  if (MiddleCroppingBlockFrame* f =
    387          do_QueryFrame(mTextContent->GetPrimaryFrame())) {
    388    f->UpdateDisplayedValueToUncroppedValue(true);
    389  }
    390 }
    391 
    392 #ifdef ACCESSIBILITY
    393 a11y::AccType nsFileControlFrame::AccessibleType() {
    394  return a11y::eHTMLFileInputType;
    395 }
    396 #endif
    397 
    398 NS_IMPL_ISUPPORTS(nsFileControlFrame::MouseListener, nsIDOMEventListener)
    399 
    400 class FileControlLabelFrame final : public MiddleCroppingBlockFrame {
    401 public:
    402  NS_DECL_QUERYFRAME
    403  NS_DECL_FRAMEARENA_HELPERS(FileControlLabelFrame)
    404 
    405  FileControlLabelFrame(ComputedStyle* aStyle, nsPresContext* aPresContext)
    406      : MiddleCroppingBlockFrame(aStyle, aPresContext, kClassID) {}
    407 
    408  HTMLInputElement& FileInput() const {
    409    return *HTMLInputElement::FromNode(mContent->GetParent());
    410  }
    411 
    412  void GetUncroppedValue(nsAString& aValue) override {
    413    return FileInput().GetDisplayFileName(aValue);
    414  }
    415 };
    416 
    417 NS_QUERYFRAME_HEAD(FileControlLabelFrame)
    418  NS_QUERYFRAME_ENTRY(FileControlLabelFrame)
    419 NS_QUERYFRAME_TAIL_INHERITING(MiddleCroppingBlockFrame)
    420 NS_IMPL_FRAMEARENA_HELPERS(FileControlLabelFrame)
    421 
    422 nsIFrame* NS_NewFileControlLabelFrame(PresShell* aPresShell,
    423                                      ComputedStyle* aStyle) {
    424  return new (aPresShell)
    425      FileControlLabelFrame(aStyle, aPresShell->GetPresContext());
    426 }