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 }