tor-browser

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

nsGlobalWindowCommands.cpp (33303B)


      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 "nsGlobalWindowCommands.h"
      8 
      9 #include "ContentEventHandler.h"
     10 #include "ErrorList.h"
     11 #include "mozilla/Attributes.h"
     12 #include "mozilla/BasicEvents.h"
     13 #include "mozilla/ControllerCommand.h"
     14 #include "mozilla/HTMLEditor.h"
     15 #include "mozilla/PresShell.h"
     16 #include "mozilla/StaticPrefs_accessibility.h"
     17 #include "mozilla/TextEditor.h"
     18 #include "mozilla/TextEvents.h"
     19 #include "mozilla/dom/DataTransfer.h"
     20 #include "mozilla/dom/Document.h"
     21 #include "mozilla/dom/Selection.h"
     22 #include "mozilla/intl/WordBreaker.h"
     23 #include "mozilla/layers/KeyboardMap.h"
     24 #include "nsCRT.h"
     25 #include "nsCommandParams.h"
     26 #include "nsContentUtils.h"
     27 #include "nsControllerCommandTable.h"
     28 #include "nsCopySupport.h"
     29 #include "nsFocusManager.h"
     30 #include "nsIClipboard.h"
     31 #include "nsIDocShell.h"
     32 #include "nsIDocumentViewer.h"
     33 #include "nsIDocumentViewerEdit.h"
     34 #include "nsIInterfaceRequestor.h"
     35 #include "nsIInterfaceRequestorUtils.h"
     36 #include "nsISelectionController.h"
     37 #include "nsIWebNavigation.h"
     38 #include "nsPIDOMWindow.h"
     39 #include "nsString.h"
     40 
     41 using namespace mozilla;
     42 using namespace mozilla::layers;
     43 
     44 constexpr nsLiteralCString kSelectMoveScrollCommands[] = {
     45    "cmd_beginLine"_ns,      "cmd_charNext"_ns,     "cmd_charPrevious"_ns,
     46    "cmd_endLine"_ns,        "cmd_lineNext"_ns,     "cmd_linePrevious"_ns,
     47    "cmd_moveBottom"_ns,     "cmd_movePageDown"_ns, "cmd_movePageUp"_ns,
     48    "cmd_moveTop"_ns,        "cmd_scrollBottom"_ns, "cmd_scrollLeft"_ns,
     49    "cmd_scrollLineDown"_ns, "cmd_scrollLineUp"_ns, "cmd_scrollPageDown"_ns,
     50    "cmd_scrollPageUp"_ns,   "cmd_scrollRight"_ns,  "cmd_scrollTop"_ns,
     51    "cmd_wordNext"_ns,       "cmd_wordPrevious"_ns,
     52 };
     53 
     54 // These are so the browser can use editor navigation key bindings
     55 // helps with accessibility (boolean pref accessibility.browsewithcaret)
     56 constexpr nsLiteralCString kSelectCommands[] = {
     57    "cmd_selectBeginLine"_ns,    "cmd_selectBottom"_ns,
     58    "cmd_selectCharNext"_ns,     "cmd_selectCharPrevious"_ns,
     59    "cmd_selectEndLine"_ns,      "cmd_selectLineNext"_ns,
     60    "cmd_selectLinePrevious"_ns, "cmd_selectPageDown"_ns,
     61    "cmd_selectPageUp"_ns,       "cmd_selectTop"_ns,
     62    "cmd_selectWordNext"_ns,     "cmd_selectWordPrevious"_ns,
     63 };
     64 
     65 // Physical-direction movement and selection commands
     66 constexpr nsLiteralCString kPhysicalSelectMoveScrollCommands[] = {
     67    "cmd_moveDown"_ns,  "cmd_moveDown2"_ns, "cmd_moveLeft"_ns,
     68    "cmd_moveLeft2"_ns, "cmd_moveRight"_ns, "cmd_moveRight2"_ns,
     69    "cmd_moveUp"_ns,    "cmd_moveUp2"_ns,
     70 };
     71 
     72 constexpr nsLiteralCString kPhysicalSelectCommands[] = {
     73    "cmd_selectDown"_ns,  "cmd_selectDown2"_ns, "cmd_selectLeft"_ns,
     74    "cmd_selectLeft2"_ns, "cmd_selectRight"_ns, "cmd_selectRight2"_ns,
     75    "cmd_selectUp"_ns,    "cmd_selectUp2"_ns,
     76 };
     77 
     78 // a base class for selection-related commands, for code sharing
     79 class nsSelectionCommandsBase : public ControllerCommand {
     80 public:
     81  bool IsCommandEnabled(const nsACString&, nsISupports*) override {
     82    return true;
     83  }
     84  void GetCommandStateParams(const nsACString&, nsICommandParams*,
     85                             nsISupports*) override {}
     86 
     87 protected:
     88  virtual ~nsSelectionCommandsBase() = default;
     89 
     90  static nsresult GetPresShellFromWindow(nsPIDOMWindowOuter* aWindow,
     91                                         PresShell** aPresShell);
     92  static nsresult GetSelectionControllerFromWindow(
     93      nsPIDOMWindowOuter* aWindow, nsISelectionController** aSelCon);
     94 
     95  // no member variables, please, we're stateless!
     96 };
     97 
     98 // this class implements commands whose behavior depends on the 'browse with
     99 // caret' setting
    100 class nsSelectMoveScrollCommand : public nsSelectionCommandsBase {
    101 public:
    102  MOZ_CAN_RUN_SCRIPT nsresult DoCommand(const nsACString& aCommandName,
    103                                        nsICommandParams*,
    104                                        nsISupports* aCommandContext) override;
    105 };
    106 
    107 // this class implements physical-movement versions of the above
    108 class nsPhysicalSelectMoveScrollCommand : public nsSelectionCommandsBase {
    109 public:
    110  MOZ_CAN_RUN_SCRIPT nsresult DoCommand(const nsACString& aCommandName,
    111                                        nsICommandParams*,
    112                                        nsISupports* aCommandContext) override;
    113 };
    114 
    115 // this class implements other selection commands
    116 class nsSelectCommand : public nsSelectionCommandsBase {
    117 public:
    118  MOZ_CAN_RUN_SCRIPT nsresult DoCommand(const nsACString& aCommandName,
    119                                        nsICommandParams*,
    120                                        nsISupports* aCommandContext) override;
    121 };
    122 
    123 // this class implements physical-movement versions of selection commands
    124 class nsPhysicalSelectCommand : public nsSelectionCommandsBase {
    125 public:
    126  MOZ_CAN_RUN_SCRIPT nsresult DoCommand(const nsACString& aCommandName,
    127                                        nsICommandParams*,
    128                                        nsISupports* aCommandContext) override;
    129 
    130  // no member variables, please, we're stateless!
    131 };
    132 
    133 nsresult nsSelectionCommandsBase::GetPresShellFromWindow(
    134    nsPIDOMWindowOuter* aWindow, PresShell** aPresShell) {
    135  *aPresShell = nullptr;
    136  NS_ENSURE_TRUE(aWindow, NS_ERROR_FAILURE);
    137 
    138  nsIDocShell* docShell = aWindow->GetDocShell();
    139  NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
    140 
    141  NS_IF_ADDREF(*aPresShell = docShell->GetPresShell());
    142  return NS_OK;
    143 }
    144 
    145 nsresult nsSelectionCommandsBase::GetSelectionControllerFromWindow(
    146    nsPIDOMWindowOuter* aWindow, nsISelectionController** aSelCon) {
    147  RefPtr<PresShell> presShell;
    148  GetPresShellFromWindow(aWindow, getter_AddRefs(presShell));
    149  if (!presShell) {
    150    *aSelCon = nullptr;
    151    return NS_ERROR_FAILURE;
    152  }
    153  *aSelCon = presShell.forget().take();
    154  return NS_OK;
    155 }
    156 
    157 // Helpers for nsSelectMoveScrollCommand and nsPhysicalSelectMoveScrollCommand
    158 static void AdjustFocusAfterCaretMove(nsPIDOMWindowOuter* aWindow) {
    159  // adjust the focus to the new caret position
    160  nsFocusManager* fm = nsFocusManager::GetFocusManager();
    161  if (fm) {
    162    RefPtr<dom::Element> result;
    163    fm->MoveFocus(aWindow, nullptr, nsIFocusManager::MOVEFOCUS_CARET,
    164                  nsIFocusManager::FLAG_NOSCROLL, getter_AddRefs(result));
    165  }
    166 }
    167 
    168 static bool IsCaretOnInWindow(nsPIDOMWindowOuter* aWindow,
    169                              nsISelectionController* aSelCont) {
    170  // We allow the caret to be moved with arrow keys on any window for which
    171  // the caret is enabled. In particular, this includes caret-browsing mode
    172  // in non-chrome documents.
    173  bool caretOn = false;
    174  aSelCont->GetCaretEnabled(&caretOn);
    175  if (!caretOn) {
    176    caretOn = StaticPrefs::accessibility_browsewithcaret();
    177    if (caretOn) {
    178      nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
    179      if (docShell && docShell->ItemType() == nsIDocShellTreeItem::typeChrome) {
    180        caretOn = false;
    181      }
    182    }
    183  }
    184  return caretOn;
    185 }
    186 
    187 static constexpr struct BrowseCommand {
    188  Command reverse, forward;
    189  KeyboardScrollAction::KeyboardScrollActionType scrollAction;
    190  nsresult (NS_STDCALL nsISelectionController::*scroll)(bool);
    191  nsresult (NS_STDCALL nsISelectionController::*move)(bool, bool);
    192 } browseCommands[] = {
    193    {Command::ScrollTop, Command::ScrollBottom,
    194     KeyboardScrollAction::eScrollComplete,
    195     &nsISelectionController::CompleteScroll},
    196    {Command::ScrollPageUp, Command::ScrollPageDown,
    197     KeyboardScrollAction::eScrollPage, &nsISelectionController::ScrollPage},
    198    {Command::ScrollLineUp, Command::ScrollLineDown,
    199     KeyboardScrollAction::eScrollLine, &nsISelectionController::ScrollLine},
    200    {Command::ScrollLeft, Command::ScrollRight,
    201     KeyboardScrollAction::eScrollCharacter,
    202     &nsISelectionController::ScrollCharacter},
    203    {Command::MoveTop, Command::MoveBottom,
    204     KeyboardScrollAction::eScrollComplete,
    205     &nsISelectionController::CompleteScroll,
    206     &nsISelectionController::CompleteMove},
    207    {Command::MovePageUp, Command::MovePageDown,
    208     KeyboardScrollAction::eScrollPage, &nsISelectionController::ScrollPage,
    209     &nsISelectionController::PageMove},
    210    {Command::LinePrevious, Command::LineNext,
    211     KeyboardScrollAction::eScrollLine, &nsISelectionController::ScrollLine,
    212     &nsISelectionController::LineMove},
    213    {Command::WordPrevious, Command::WordNext,
    214     KeyboardScrollAction::eScrollCharacter,
    215     &nsISelectionController::ScrollCharacter,
    216     &nsISelectionController::WordMove},
    217    {Command::CharPrevious, Command::CharNext,
    218     KeyboardScrollAction::eScrollCharacter,
    219     &nsISelectionController::ScrollCharacter,
    220     &nsISelectionController::CharacterMove},
    221    {Command::BeginLine, Command::EndLine,
    222     KeyboardScrollAction::eScrollComplete,
    223     &nsISelectionController::CompleteScroll,
    224     &nsISelectionController::IntraLineMove}};
    225 
    226 nsresult nsSelectMoveScrollCommand::DoCommand(const nsACString& aCommandName,
    227                                              nsICommandParams*,
    228                                              nsISupports* aCommandContext) {
    229  nsCOMPtr<nsPIDOMWindowOuter> piWindow(do_QueryInterface(aCommandContext));
    230  nsCOMPtr<nsISelectionController> selCont;
    231  GetSelectionControllerFromWindow(piWindow, getter_AddRefs(selCont));
    232  NS_ENSURE_TRUE(selCont, NS_ERROR_NOT_INITIALIZED);
    233 
    234  const bool caretOn = IsCaretOnInWindow(piWindow, selCont);
    235  const Command command = GetInternalCommand(aCommandName);
    236  for (const BrowseCommand& browseCommand : browseCommands) {
    237    const bool forward = command == browseCommand.forward;
    238    if (!forward && command != browseCommand.reverse) {
    239      continue;
    240    }
    241    RefPtr<HTMLEditor> htmlEditor =
    242        HTMLEditor::GetFrom(nsContentUtils::GetActiveEditor(piWindow));
    243    if (htmlEditor) {
    244      htmlEditor->PreHandleSelectionChangeCommand(command);
    245    }
    246    nsresult rv = NS_OK;
    247    if (caretOn && browseCommand.move &&
    248        NS_SUCCEEDED((selCont->*(browseCommand.move))(forward, false))) {
    249      AdjustFocusAfterCaretMove(piWindow);
    250    } else {
    251      rv = (selCont->*(browseCommand.scroll))(forward);
    252    }
    253    if (htmlEditor) {
    254      htmlEditor->PostHandleSelectionChangeCommand(command);
    255    }
    256    return rv;
    257  }
    258 
    259  MOZ_ASSERT(false, "Forgot to handle new command?");
    260  return NS_ERROR_NOT_IMPLEMENTED;
    261 }
    262 
    263 // XXX It's not clear to me yet how we should handle the "scroll" option
    264 // for these commands; for now, I'm mapping them back to ScrollCharacter,
    265 // ScrollLine, etc., as if for horizontal-mode content, but this may need
    266 // to be reconsidered once we have more experience with vertical content.
    267 static const struct PhysicalBrowseCommand {
    268  Command command;
    269  int16_t direction, amount;
    270  KeyboardScrollAction::KeyboardScrollActionType scrollAction;
    271  nsresult (NS_STDCALL nsISelectionController::*scroll)(bool);
    272 } physicalBrowseCommands[] = {
    273    {Command::MoveLeft, nsISelectionController::MOVE_LEFT, 0,
    274     KeyboardScrollAction::eScrollCharacter,
    275     &nsISelectionController::ScrollCharacter},
    276    {Command::MoveRight, nsISelectionController::MOVE_RIGHT, 0,
    277     KeyboardScrollAction::eScrollCharacter,
    278     &nsISelectionController::ScrollCharacter},
    279    {Command::MoveUp, nsISelectionController::MOVE_UP, 0,
    280     KeyboardScrollAction::eScrollLine, &nsISelectionController::ScrollLine},
    281    {Command::MoveDown, nsISelectionController::MOVE_DOWN, 0,
    282     KeyboardScrollAction::eScrollLine, &nsISelectionController::ScrollLine},
    283    {Command::MoveLeft2, nsISelectionController::MOVE_LEFT, 1,
    284     KeyboardScrollAction::eScrollCharacter,
    285     &nsISelectionController::ScrollCharacter},
    286    {Command::MoveRight2, nsISelectionController::MOVE_RIGHT, 1,
    287     KeyboardScrollAction::eScrollCharacter,
    288     &nsISelectionController::ScrollCharacter},
    289    {Command::MoveUp2, nsISelectionController::MOVE_UP, 1,
    290     KeyboardScrollAction::eScrollComplete,
    291     &nsISelectionController::CompleteScroll},
    292    {Command::MoveDown2, nsISelectionController::MOVE_DOWN, 1,
    293     KeyboardScrollAction::eScrollComplete,
    294     &nsISelectionController::CompleteScroll},
    295 };
    296 
    297 nsresult nsPhysicalSelectMoveScrollCommand::DoCommand(
    298    const nsACString& aCommandName, nsICommandParams*,
    299    nsISupports* aCommandContext) {
    300  nsCOMPtr<nsPIDOMWindowOuter> piWindow(do_QueryInterface(aCommandContext));
    301  nsCOMPtr<nsISelectionController> selCont;
    302  GetSelectionControllerFromWindow(piWindow, getter_AddRefs(selCont));
    303  NS_ENSURE_TRUE(selCont, NS_ERROR_NOT_INITIALIZED);
    304 
    305  const bool caretOn = IsCaretOnInWindow(piWindow, selCont);
    306  Command command = GetInternalCommand(aCommandName);
    307  for (const PhysicalBrowseCommand& browseCommand : physicalBrowseCommands) {
    308    if (command != browseCommand.command) {
    309      continue;
    310    }
    311    RefPtr<HTMLEditor> htmlEditor =
    312        HTMLEditor::GetFrom(nsContentUtils::GetActiveEditor(piWindow));
    313    if (htmlEditor) {
    314      htmlEditor->PreHandleSelectionChangeCommand(command);
    315    }
    316    nsresult rv = NS_OK;
    317    if (caretOn && NS_SUCCEEDED(selCont->PhysicalMove(
    318                       browseCommand.direction, browseCommand.amount, false))) {
    319      AdjustFocusAfterCaretMove(piWindow);
    320    } else {
    321      const bool forward =
    322          (browseCommand.direction == nsISelectionController::MOVE_RIGHT ||
    323           browseCommand.direction == nsISelectionController::MOVE_DOWN);
    324      rv = (selCont->*(browseCommand.scroll))(forward);
    325    }
    326    if (htmlEditor) {
    327      htmlEditor->PostHandleSelectionChangeCommand(command);
    328    }
    329    return rv;
    330  }
    331 
    332  MOZ_ASSERT(false, "Forgot to handle new command?");
    333  return NS_ERROR_NOT_IMPLEMENTED;
    334 }
    335 
    336 static const struct SelectCommand {
    337  Command reverse, forward;
    338  nsresult (NS_STDCALL nsISelectionController::*select)(bool, bool);
    339 } selectCommands[] = {{Command::SelectCharPrevious, Command::SelectCharNext,
    340                       &nsISelectionController::CharacterMove},
    341                      {Command::SelectWordPrevious, Command::SelectWordNext,
    342                       &nsISelectionController::WordMove},
    343                      {Command::SelectBeginLine, Command::SelectEndLine,
    344                       &nsISelectionController::IntraLineMove},
    345                      {Command::SelectLinePrevious, Command::SelectLineNext,
    346                       &nsISelectionController::LineMove},
    347                      {Command::SelectPageUp, Command::SelectPageDown,
    348                       &nsISelectionController::PageMove},
    349                      {Command::SelectTop, Command::SelectBottom,
    350                       &nsISelectionController::CompleteMove}};
    351 
    352 nsresult nsSelectCommand::DoCommand(const nsACString& aCommandName,
    353                                    nsICommandParams*,
    354                                    nsISupports* aCommandContext) {
    355  nsCOMPtr<nsPIDOMWindowOuter> piWindow(do_QueryInterface(aCommandContext));
    356  nsCOMPtr<nsISelectionController> selCont;
    357  GetSelectionControllerFromWindow(piWindow, getter_AddRefs(selCont));
    358  NS_ENSURE_TRUE(selCont, NS_ERROR_NOT_INITIALIZED);
    359 
    360  // These commands are so the browser can use caret navigation key bindings -
    361  // Helps with accessibility - aaronl@netscape.com
    362  const Command command = GetInternalCommand(aCommandName);
    363  for (const SelectCommand& selectCommand : selectCommands) {
    364    const bool forward = command == selectCommand.forward;
    365    if (!forward && command != selectCommand.reverse) {
    366      continue;
    367    }
    368    RefPtr<HTMLEditor> htmlEditor =
    369        HTMLEditor::GetFrom(nsContentUtils::GetActiveEditor(piWindow));
    370    if (htmlEditor) {
    371      htmlEditor->PreHandleSelectionChangeCommand(command);
    372    }
    373    nsresult rv = (selCont->*(selectCommand.select))(forward, true);
    374    if (htmlEditor) {
    375      htmlEditor->PostHandleSelectionChangeCommand(command);
    376    }
    377    return rv;
    378  }
    379 
    380  MOZ_ASSERT(false, "Forgot to handle new command?");
    381  return NS_ERROR_NOT_IMPLEMENTED;
    382 }
    383 
    384 static const struct PhysicalSelectCommand {
    385  Command command;
    386  int16_t direction, amount;
    387 } physicalSelectCommands[] = {
    388    {Command::SelectLeft, nsISelectionController::MOVE_LEFT, 0},
    389    {Command::SelectRight, nsISelectionController::MOVE_RIGHT, 0},
    390    {Command::SelectUp, nsISelectionController::MOVE_UP, 0},
    391    {Command::SelectDown, nsISelectionController::MOVE_DOWN, 0},
    392    {Command::SelectLeft2, nsISelectionController::MOVE_LEFT, 1},
    393    {Command::SelectRight2, nsISelectionController::MOVE_RIGHT, 1},
    394    {Command::SelectUp2, nsISelectionController::MOVE_UP, 1},
    395    {Command::SelectDown2, nsISelectionController::MOVE_DOWN, 1}};
    396 
    397 nsresult nsPhysicalSelectCommand::DoCommand(const nsACString& aCommandName,
    398                                            nsICommandParams* aParams,
    399                                            nsISupports* aCommandContext) {
    400  nsCOMPtr<nsPIDOMWindowOuter> piWindow(do_QueryInterface(aCommandContext));
    401  nsCOMPtr<nsISelectionController> selCont;
    402  GetSelectionControllerFromWindow(piWindow, getter_AddRefs(selCont));
    403  NS_ENSURE_TRUE(selCont, NS_ERROR_NOT_INITIALIZED);
    404 
    405  const Command command = GetInternalCommand(aCommandName);
    406  for (const PhysicalSelectCommand& selectCommand : physicalSelectCommands) {
    407    if (command != selectCommand.command) {
    408      continue;
    409    }
    410    RefPtr<HTMLEditor> htmlEditor =
    411        HTMLEditor::GetFrom(nsContentUtils::GetActiveEditor(piWindow));
    412    if (htmlEditor) {
    413      htmlEditor->PreHandleSelectionChangeCommand(command);
    414    }
    415    nsresult rv = selCont->PhysicalMove(selectCommand.direction,
    416                                        selectCommand.amount, true);
    417    if (htmlEditor) {
    418      htmlEditor->PostHandleSelectionChangeCommand(command);
    419    }
    420    return rv;
    421  }
    422 
    423  MOZ_ASSERT(false, "Forgot to handle new command?");
    424  return NS_ERROR_NOT_IMPLEMENTED;
    425 }
    426 
    427 class nsClipboardCommand final : public ControllerCommand {
    428 public:
    429  DECL_CONTROLLER_COMMAND_NO_PARAMS
    430 };
    431 
    432 bool nsClipboardCommand::IsCommandEnabled(const nsACString& aCommandName,
    433                                          nsISupports* aContext) {
    434  nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aContext);
    435  if (!window) {
    436    return false;
    437  }
    438  RefPtr<dom::Document> doc = window->GetExtantDoc();
    439  if (!doc) {
    440    return false;
    441  }
    442  if (doc->AreClipboardCommandsUnconditionallyEnabled()) {
    443    // In HTML and XHTML documents, we always want the cut, copy and paste
    444    // commands to be enabled, but if the document is chrome, let it control it.
    445    return true;
    446  }
    447  if (aCommandName.EqualsLiteral("cmd_copy")) {
    448    // Cut isn't enabled in xul documents which use nsClipboardCommand
    449    return nsCopySupport::CanCopy(doc);
    450  }
    451  return false;
    452 }
    453 
    454 nsresult nsClipboardCommand::DoCommand(const nsACString& aCommandName,
    455                                       nsICommandParams*,
    456                                       nsISupports* aContext) {
    457  nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aContext);
    458  NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
    459 
    460  nsIDocShell* docShell = window->GetDocShell();
    461  NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
    462 
    463  RefPtr<PresShell> presShell = docShell->GetPresShell();
    464  NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
    465 
    466  const EventMessage eventMessage = [&] {
    467    if (aCommandName.EqualsLiteral("cmd_cut")) {
    468      return eCut;
    469    }
    470    if (aCommandName.EqualsLiteral("cmd_paste")) {
    471      return ePaste;
    472    }
    473    MOZ_ASSERT(aCommandName.EqualsLiteral("cmd_copy"));
    474    return eCopy;
    475  }();
    476 
    477  RefPtr<dom::DataTransfer> dataTransfer;
    478  if (ePaste == eventMessage) {
    479    nsCOMPtr<nsIPrincipal> subjectPrincipal =
    480        nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();
    481    MOZ_ASSERT(subjectPrincipal);
    482 
    483    // If we don't need to get user confirmation for clipboard access, we could
    484    // just let nsCopySupport::FireClipboardEvent() to create DataTransfer
    485    // instance synchronously for paste event. Otherwise, we need to spin the
    486    // event loop to wait for the clipboard paste contextmenu to be shown and
    487    // get user confirmation which are all handled in parent process before
    488    // sending the paste event.
    489    if (!nsContentUtils::PrincipalHasPermission(*subjectPrincipal,
    490                                                nsGkAtoms::clipboardRead)) {
    491      MOZ_DIAGNOSTIC_ASSERT(StaticPrefs::dom_execCommand_paste_enabled(),
    492                            "How did we get here?");
    493      // This will spin the event loop.
    494      dataTransfer = dom::DataTransfer::WaitForClipboardDataSnapshotAndCreate(
    495          window, subjectPrincipal);
    496      if (!dataTransfer) {
    497        return NS_SUCCESS_DOM_NO_OPERATION;
    498      }
    499    }
    500  }
    501 
    502  bool actionTaken = false;
    503  nsCopySupport::FireClipboardEvent(
    504      eventMessage, Some(nsIClipboard::kGlobalClipboard), presShell, nullptr,
    505      dataTransfer, &actionTaken);
    506 
    507  return actionTaken ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
    508 }
    509 
    510 class nsSelectionCommand : public ControllerCommand {
    511 public:
    512  DECL_CONTROLLER_COMMAND_NO_PARAMS
    513 
    514 protected:
    515  virtual ~nsSelectionCommand() = default;
    516 
    517  virtual bool IsClipboardCommandEnabled(const nsACString& aCommandName,
    518                                         nsIDocumentViewerEdit* aEdit) = 0;
    519  virtual nsresult DoClipboardCommand(const nsACString& aCommandName,
    520                                      nsIDocumentViewerEdit* aEdit,
    521                                      nsICommandParams* aParams) = 0;
    522 
    523  static already_AddRefed<nsIDocumentViewerEdit>
    524  GetDocumentViewerEditFromContext(nsISupports* aContext);
    525 
    526  // no member variables, please, we're stateless!
    527 };
    528 
    529 bool nsSelectionCommand::IsCommandEnabled(const nsACString& aCommandName,
    530                                          nsISupports* aCommandContext) {
    531  nsCOMPtr<nsIDocumentViewerEdit> documentEdit =
    532      GetDocumentViewerEditFromContext(aCommandContext);
    533  return IsClipboardCommandEnabled(aCommandName, documentEdit);
    534 }
    535 
    536 nsresult nsSelectionCommand::DoCommand(const nsACString& aCommandName,
    537                                       nsICommandParams* aParams,
    538                                       nsISupports* aCommandContext) {
    539  nsCOMPtr<nsIDocumentViewerEdit> documentEdit =
    540      GetDocumentViewerEditFromContext(aCommandContext);
    541  NS_ENSURE_TRUE(documentEdit, NS_ERROR_NOT_INITIALIZED);
    542  return DoClipboardCommand(aCommandName, documentEdit, aParams);
    543 }
    544 
    545 already_AddRefed<nsIDocumentViewerEdit>
    546 nsSelectionCommand::GetDocumentViewerEditFromContext(nsISupports* aContext) {
    547  nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aContext);
    548  if (!window) {
    549    return nullptr;
    550  }
    551 
    552  nsIDocShell* docShell = window->GetDocShell();
    553  NS_ENSURE_TRUE(docShell, nullptr);
    554 
    555  nsCOMPtr<nsIDocumentViewer> viewer;
    556  docShell->GetDocViewer(getter_AddRefs(viewer));
    557  nsCOMPtr<nsIDocumentViewerEdit> edit(do_QueryInterface(viewer));
    558  return edit.forget();
    559 }
    560 
    561 #define NS_DECL_CLIPBOARD_COMMAND(_cmd)                                    \
    562  class _cmd : public nsSelectionCommand {                                 \
    563   protected:                                                              \
    564    bool IsClipboardCommandEnabled(const nsACString& aCommandName,         \
    565                                   nsIDocumentViewerEdit* aEdit) override; \
    566    nsresult DoClipboardCommand(const nsACString& aCommandName,            \
    567                                nsIDocumentViewerEdit* aEdit,              \
    568                                nsICommandParams* aParams) override;       \
    569    /* no member variables, please, we're stateless! */                    \
    570  };
    571 
    572 NS_DECL_CLIPBOARD_COMMAND(nsClipboardCopyLinkCommand)
    573 NS_DECL_CLIPBOARD_COMMAND(nsClipboardImageCommands)
    574 NS_DECL_CLIPBOARD_COMMAND(nsClipboardSelectAllNoneCommands)
    575 
    576 bool nsClipboardCopyLinkCommand::IsClipboardCommandEnabled(
    577    const nsACString& aCommandName, nsIDocumentViewerEdit* aEdit) {
    578  return aEdit && aEdit->GetInLink();
    579 }
    580 
    581 nsresult nsClipboardCopyLinkCommand::DoClipboardCommand(
    582    const nsACString& aCommandName, nsIDocumentViewerEdit* aEdit,
    583    nsICommandParams* aParams) {
    584  return aEdit->CopyLinkLocation();
    585 }
    586 
    587 bool nsClipboardImageCommands::IsClipboardCommandEnabled(
    588    const nsACString& aCommandName, nsIDocumentViewerEdit* aEdit) {
    589  return aEdit && aEdit->GetInImage();
    590 }
    591 
    592 nsresult nsClipboardImageCommands::DoClipboardCommand(
    593    const nsACString& aCommandName, nsIDocumentViewerEdit* aEdit,
    594    nsICommandParams* aParams) {
    595  if (aCommandName.EqualsLiteral("cmd_copyImageLocation")) {
    596    return aEdit->CopyImage(nsIDocumentViewerEdit::COPY_IMAGE_TEXT);
    597  }
    598  if (aCommandName.EqualsLiteral("cmd_copyImageContents")) {
    599    return aEdit->CopyImage(nsIDocumentViewerEdit::COPY_IMAGE_DATA);
    600  }
    601  int32_t copyFlags = nsIDocumentViewerEdit::COPY_IMAGE_DATA |
    602                      nsIDocumentViewerEdit::COPY_IMAGE_HTML;
    603  if (aParams) {
    604    copyFlags = aParams->AsCommandParams()->GetInt("imageCopy");
    605  }
    606  return aEdit->CopyImage(copyFlags);
    607 }
    608 
    609 bool nsClipboardSelectAllNoneCommands::IsClipboardCommandEnabled(
    610    const nsACString& aCommandName, nsIDocumentViewerEdit* aEdit) {
    611  return true;
    612 }
    613 
    614 nsresult nsClipboardSelectAllNoneCommands::DoClipboardCommand(
    615    const nsACString& aCommandName, nsIDocumentViewerEdit* aEdit,
    616    nsICommandParams* aParams) {
    617  if (aCommandName.EqualsLiteral("cmd_selectAll")) {
    618    return aEdit->SelectAll();
    619  }
    620  return aEdit->ClearSelection();
    621 }
    622 
    623 class nsLookUpDictionaryCommand final : public ControllerCommand {
    624 public:
    625  DECL_CONTROLLER_COMMAND_NO_PARAMS
    626 };
    627 
    628 bool nsLookUpDictionaryCommand::IsCommandEnabled(const nsACString& aCommandName,
    629                                                 nsISupports* aCommandContext) {
    630  return true;
    631 }
    632 
    633 nsresult nsLookUpDictionaryCommand::DoCommand(const nsACString& aCommandName,
    634                                              nsICommandParams* aParams,
    635                                              nsISupports* aCommandContext) {
    636  if (NS_WARN_IF(!nsContentUtils::IsSafeToRunScript())) {
    637    return NS_ERROR_NOT_AVAILABLE;
    638  }
    639  if (!aParams) {
    640    return NS_ERROR_NOT_IMPLEMENTED;
    641  }
    642  nsCommandParams* params = aParams->AsCommandParams();
    643 
    644  ErrorResult error;
    645  int32_t x = params->GetInt("x", error);
    646  if (NS_WARN_IF(error.Failed())) {
    647    return error.StealNSResult();
    648  }
    649  int32_t y = params->GetInt("y", error);
    650  if (NS_WARN_IF(error.Failed())) {
    651    return error.StealNSResult();
    652  }
    653 
    654  LayoutDeviceIntPoint point(x, y);
    655 
    656  nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aCommandContext);
    657  if (NS_WARN_IF(!window)) {
    658    return NS_ERROR_FAILURE;
    659  }
    660 
    661  nsIDocShell* docShell = window->GetDocShell();
    662  if (NS_WARN_IF(!docShell)) {
    663    return NS_ERROR_FAILURE;
    664  }
    665 
    666  PresShell* presShell = docShell->GetPresShell();
    667  if (NS_WARN_IF(!presShell)) {
    668    return NS_ERROR_FAILURE;
    669  }
    670 
    671  nsPresContext* presContext = presShell->GetPresContext();
    672  if (NS_WARN_IF(!presContext)) {
    673    return NS_ERROR_FAILURE;
    674  }
    675 
    676  nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget();
    677  if (NS_WARN_IF(!widget)) {
    678    return NS_ERROR_FAILURE;
    679  }
    680 
    681  WidgetQueryContentEvent queryCharAtPointEvent(true, eQueryCharacterAtPoint,
    682                                                widget);
    683  queryCharAtPointEvent.mRefPoint.x = x;
    684  queryCharAtPointEvent.mRefPoint.y = y;
    685  ContentEventHandler handler(presContext);
    686  handler.OnQueryCharacterAtPoint(&queryCharAtPointEvent);
    687 
    688  if (NS_WARN_IF(queryCharAtPointEvent.Failed()) ||
    689      queryCharAtPointEvent.DidNotFindChar()) {
    690    return NS_ERROR_FAILURE;
    691  }
    692 
    693  WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
    694                                                 widget);
    695  handler.OnQuerySelectedText(&querySelectedTextEvent);
    696  if (NS_WARN_IF(querySelectedTextEvent.DidNotFindSelection())) {
    697    return NS_ERROR_FAILURE;
    698  }
    699 
    700  uint32_t offset = queryCharAtPointEvent.mReply->StartOffset();
    701  uint32_t begin, length;
    702 
    703  // macOS prioritizes user selected text if the current point falls within the
    704  // selection range. So we check the selection first.
    705  if (querySelectedTextEvent.FoundSelection() &&
    706      querySelectedTextEvent.mReply->IsOffsetInRange(offset)) {
    707    begin = querySelectedTextEvent.mReply->StartOffset();
    708    length = querySelectedTextEvent.mReply->DataLength();
    709  } else {
    710    WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
    711                                                  widget);
    712    // OSX 10.7 queries 50 characters before/after current point.  So we fetch
    713    // same length.
    714    if (offset > 50) {
    715      offset -= 50;
    716    } else {
    717      offset = 0;
    718    }
    719    queryTextContentEvent.InitForQueryTextContent(offset, 100);
    720    handler.OnQueryTextContent(&queryTextContentEvent);
    721    if (NS_WARN_IF(queryTextContentEvent.Failed()) ||
    722        NS_WARN_IF(queryTextContentEvent.mReply->IsDataEmpty())) {
    723      return NS_ERROR_FAILURE;
    724    }
    725 
    726    intl::WordRange range = intl::WordBreaker::FindWord(
    727        queryTextContentEvent.mReply->DataRef(),
    728        queryCharAtPointEvent.mReply->StartOffset() - offset);
    729    if (range.mEnd == range.mBegin) {
    730      return NS_ERROR_FAILURE;
    731    }
    732    begin = range.mBegin + offset;
    733    length = range.mEnd - range.mBegin;
    734  }
    735 
    736  WidgetQueryContentEvent queryLookUpContentEvent(true, eQueryTextContent,
    737                                                  widget);
    738  queryLookUpContentEvent.InitForQueryTextContent(begin, length);
    739  queryLookUpContentEvent.RequestFontRanges();
    740  handler.OnQueryTextContent(&queryLookUpContentEvent);
    741  if (NS_WARN_IF(queryLookUpContentEvent.Failed()) ||
    742      NS_WARN_IF(queryLookUpContentEvent.mReply->IsDataEmpty())) {
    743    return NS_ERROR_FAILURE;
    744  }
    745 
    746  WidgetQueryContentEvent queryTextRectEvent(true, eQueryTextRect, widget);
    747  queryTextRectEvent.InitForQueryTextRect(begin, length);
    748  handler.OnQueryTextRect(&queryTextRectEvent);
    749  if (NS_WARN_IF(queryTextRectEvent.Failed())) {
    750    return NS_ERROR_FAILURE;
    751  }
    752 
    753  widget->LookUpDictionary(queryLookUpContentEvent.mReply->DataRef(),
    754                           queryLookUpContentEvent.mReply->mFontRanges,
    755                           queryTextRectEvent.mReply->mWritingMode.IsVertical(),
    756                           queryTextRectEvent.mReply->mRect.TopLeft());
    757 
    758  return NS_OK;
    759 }
    760 
    761 /*---------------------------------------------------------------------------
    762 
    763  RegisterWindowCommands
    764 
    765 ----------------------------------------------------------------------------*/
    766 
    767 // static
    768 void nsWindowCommandRegistration::RegisterWindowCommands(
    769    nsControllerCommandTable* aCommandTable) {
    770  {
    771    RefPtr command = new nsSelectMoveScrollCommand();
    772    for (const auto& name : kSelectMoveScrollCommands) {
    773      aCommandTable->RegisterCommand(name, command);
    774    }
    775  }
    776 
    777  {
    778    RefPtr command = new nsPhysicalSelectMoveScrollCommand();
    779    for (const auto& name : kPhysicalSelectMoveScrollCommands) {
    780      aCommandTable->RegisterCommand(name, command);
    781    }
    782  }
    783 
    784  {
    785    RefPtr command = new nsSelectCommand();
    786    for (const auto& name : kSelectCommands) {
    787      aCommandTable->RegisterCommand(name, command);
    788    }
    789  }
    790 
    791  {
    792    RefPtr command = new nsPhysicalSelectCommand();
    793    for (const auto& name : kPhysicalSelectCommands) {
    794      aCommandTable->RegisterCommand(name, command);
    795    }
    796  }
    797 
    798  {
    799    RefPtr command = new nsClipboardCommand();
    800    for (const auto& name : {"cmd_cut"_ns, "cmd_copy"_ns, "cmd_paste"_ns}) {
    801      aCommandTable->RegisterCommand(name, command);
    802    }
    803  }
    804 
    805  aCommandTable->RegisterCommand("cmd_copyLink"_ns,
    806                                 new nsClipboardCopyLinkCommand());
    807 
    808  {
    809    RefPtr command = new nsClipboardImageCommands();
    810    for (const auto& name : {
    811             "cmd_copyImageLocation"_ns,
    812             "cmd_copyImageContents"_ns,
    813             "cmd_copyImage"_ns,
    814         }) {
    815      aCommandTable->RegisterCommand(name, command);
    816    }
    817  }
    818 
    819  {
    820    RefPtr command = new nsClipboardSelectAllNoneCommands();
    821    for (const auto& name : {"cmd_selectAll"_ns, "cmd_selectNone"_ns}) {
    822      aCommandTable->RegisterCommand(name, command);
    823    }
    824  }
    825 
    826  aCommandTable->RegisterCommand("cmd_lookUpDictionary"_ns,
    827                                 new nsLookUpDictionaryCommand());
    828 }
    829 
    830 /* static */
    831 bool nsGlobalWindowCommands::FindScrollCommand(
    832    const nsACString& aCommandName, KeyboardScrollAction* aOutAction) {
    833  // Search for a keyboard scroll action to do for this command in
    834  // browseCommands and physicalBrowseCommands. Each command exists in only one
    835  // of them, so the order we examine browseCommands and physicalBrowseCommands
    836  // doesn't matter.
    837 
    838  const Command command = GetInternalCommand(aCommandName);
    839  if (command == Command::DoNothing) {
    840    return false;
    841  }
    842  for (const BrowseCommand& browseCommand : browseCommands) {
    843    const bool forward = command == browseCommand.forward;
    844    const bool reverse = command == browseCommand.reverse;
    845    if (forward || reverse) {
    846      *aOutAction = KeyboardScrollAction(browseCommand.scrollAction, forward);
    847      return true;
    848    }
    849  }
    850 
    851  for (const PhysicalBrowseCommand& browseCommand : physicalBrowseCommands) {
    852    if (command != browseCommand.command) {
    853      continue;
    854    }
    855    const bool forward =
    856        (browseCommand.direction == nsISelectionController::MOVE_RIGHT ||
    857         browseCommand.direction == nsISelectionController::MOVE_DOWN);
    858 
    859    *aOutAction = KeyboardScrollAction(browseCommand.scrollAction, forward);
    860    return true;
    861  }
    862 
    863  return false;
    864 }