EditorCommands.cpp (38089B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "EditorCommands.h" 7 8 #include "mozilla/Assertions.h" 9 #include "mozilla/EditorBase.h" 10 #include "mozilla/FlushType.h" 11 #include "mozilla/HTMLEditor.h" 12 #include "mozilla/Maybe.h" 13 #include "mozilla/MozPromise.h" // for mozilla::detail::Any 14 #include "mozilla/dom/DataTransfer.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/Selection.h" 17 #include "nsCommandParams.h" 18 #include "nsIClipboard.h" 19 #include "nsIEditingSession.h" 20 #include "nsIPrincipal.h" 21 #include "nsISelectionController.h" 22 #include "nsITransferable.h" 23 #include "nsString.h" 24 #include "nsAString.h" 25 26 class nsISupports; 27 28 #define STATE_ENABLED "state_enabled" 29 #define STATE_ATTRIBUTE "state_attribute" 30 #define STATE_DATA "state_data" 31 32 namespace mozilla { 33 34 using detail::Any; 35 36 /****************************************************************************** 37 * mozilla::EditorCommand 38 ******************************************************************************/ 39 40 bool EditorCommand::IsCommandEnabled(const nsACString& aCommandName, 41 nsISupports* aCommandRefCon) { 42 nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon); 43 EditorBase* editorBase = editor ? editor->AsEditorBase() : nullptr; 44 return IsCommandEnabled(GetInternalCommand(aCommandName), 45 MOZ_KnownLive(editorBase)); 46 } 47 48 nsresult EditorCommand::DoCommand(const nsACString& aCommandName, 49 nsICommandParams* aParams, 50 nsISupports* aCommandRefCon) { 51 if (NS_WARN_IF(!aCommandRefCon)) { 52 return NS_ERROR_INVALID_ARG; 53 } 54 nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon); 55 if (NS_WARN_IF(!editor)) { 56 return NS_ERROR_INVALID_ARG; 57 } 58 nsCommandParams* params = aParams ? aParams->AsCommandParams() : nullptr; 59 Command command = GetInternalCommand(aCommandName, params); 60 EditorCommandParamType paramType = EditorCommand::GetParamType(command); 61 if (paramType == EditorCommandParamType::None) { 62 nsresult rv = DoCommandParam( 63 command, MOZ_KnownLive(*editor->AsEditorBase()), nullptr); 64 NS_WARNING_ASSERTION( 65 NS_SUCCEEDED(rv), 66 "Failed to do command from nsIControllerCommand::DoCommandParams()"); 67 return rv; 68 } 69 70 if (Any(paramType & EditorCommandParamType::Bool)) { 71 if (Any(paramType & EditorCommandParamType::StateAttribute)) { 72 Maybe<bool> boolParam = Nothing(); 73 if (params) { 74 ErrorResult error; 75 boolParam = Some(params->GetBool(STATE_ATTRIBUTE, error)); 76 if (NS_WARN_IF(error.Failed())) { 77 return error.StealNSResult(); 78 } 79 } 80 nsresult rv = DoCommandParam( 81 command, boolParam, MOZ_KnownLive(*editor->AsEditorBase()), nullptr); 82 NS_WARNING_ASSERTION( 83 NS_SUCCEEDED(rv), 84 "Failed to do command from nsIControllerCommand::DoCommandParams()"); 85 return rv; 86 } 87 MOZ_ASSERT_UNREACHABLE("Unexpected state for bool"); 88 return NS_ERROR_NOT_IMPLEMENTED; 89 } 90 91 // Special case for MultiStateCommandBase. It allows both CString and String 92 // in STATE_ATTRIBUTE and CString is preferred. 93 if (Any(paramType & EditorCommandParamType::CString) && 94 Any(paramType & EditorCommandParamType::String)) { 95 if (!params) { 96 nsresult rv = 97 DoCommandParam(command, VoidString(), 98 MOZ_KnownLive(*editor->AsEditorBase()), nullptr); 99 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 100 "Failed to do command from " 101 "nsIControllerCommand::DoCommandParams()"); 102 return rv; 103 } 104 if (Any(paramType & EditorCommandParamType::StateAttribute)) { 105 nsCString cStringParam; 106 nsresult rv = params->GetCString(STATE_ATTRIBUTE, cStringParam); 107 if (NS_SUCCEEDED(rv)) { 108 NS_ConvertUTF8toUTF16 stringParam(cStringParam); 109 nsresult rv = 110 DoCommandParam(command, stringParam, 111 MOZ_KnownLive(*editor->AsEditorBase()), nullptr); 112 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 113 "Failed to do command from " 114 "nsIControllerCommand::DoCommandParams()"); 115 return rv; 116 } 117 nsString stringParam; 118 DebugOnly<nsresult> rvIgnored = 119 params->GetString(STATE_ATTRIBUTE, stringParam); 120 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 121 "Failed to get string from STATE_ATTRIBUTE"); 122 rv = DoCommandParam(command, stringParam, 123 MOZ_KnownLive(*editor->AsEditorBase()), nullptr); 124 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 125 "Failed to do command from " 126 "nsIControllerCommand::DoCommandParams()"); 127 return rv; 128 } 129 MOZ_ASSERT_UNREACHABLE("Unexpected state for CString/String"); 130 return NS_ERROR_NOT_IMPLEMENTED; 131 } 132 133 if (Any(paramType & EditorCommandParamType::CString)) { 134 if (!params) { 135 nsresult rv = 136 DoCommandParam(command, VoidCString(), 137 MOZ_KnownLive(*editor->AsEditorBase()), nullptr); 138 NS_WARNING_ASSERTION( 139 NS_SUCCEEDED(rv), 140 "Failed to do command from nsIControllerCommand::DoCommandParams()"); 141 return rv; 142 } 143 if (Any(paramType & EditorCommandParamType::StateAttribute)) { 144 nsCString cStringParam; 145 nsresult rv = params->GetCString(STATE_ATTRIBUTE, cStringParam); 146 if (NS_WARN_IF(NS_FAILED(rv))) { 147 return rv; 148 } 149 rv = DoCommandParam(command, cStringParam, 150 MOZ_KnownLive(*editor->AsEditorBase()), nullptr); 151 NS_WARNING_ASSERTION( 152 NS_SUCCEEDED(rv), 153 "Failed to do command from nsIControllerCommand::DoCommandParams()"); 154 return rv; 155 } 156 MOZ_ASSERT_UNREACHABLE("Unexpected state for CString"); 157 return NS_ERROR_NOT_IMPLEMENTED; 158 } 159 160 if (Any(paramType & EditorCommandParamType::String)) { 161 if (!params) { 162 nsresult rv = 163 DoCommandParam(command, VoidString(), 164 MOZ_KnownLive(*editor->AsEditorBase()), nullptr); 165 NS_WARNING_ASSERTION( 166 NS_SUCCEEDED(rv), 167 "Failed to do command from nsIControllerCommand::DoCommandParams()"); 168 return rv; 169 } 170 nsString stringParam; 171 if (Any(paramType & EditorCommandParamType::StateAttribute)) { 172 nsresult rv = params->GetString(STATE_ATTRIBUTE, stringParam); 173 if (NS_WARN_IF(NS_FAILED(rv))) { 174 return rv; 175 } 176 } else if (Any(paramType & EditorCommandParamType::StateData)) { 177 nsresult rv = params->GetString(STATE_DATA, stringParam); 178 if (NS_WARN_IF(NS_FAILED(rv))) { 179 return rv; 180 } 181 } else { 182 MOZ_ASSERT_UNREACHABLE("Unexpected state for String"); 183 return NS_ERROR_NOT_IMPLEMENTED; 184 } 185 nsresult rv = DoCommandParam( 186 command, stringParam, MOZ_KnownLive(*editor->AsEditorBase()), nullptr); 187 NS_WARNING_ASSERTION( 188 NS_SUCCEEDED(rv), 189 "Failed to do command from nsIControllerCommand::DoCommandParams()"); 190 return rv; 191 } 192 193 if (Any(paramType & EditorCommandParamType::Transferable)) { 194 nsCOMPtr<nsITransferable> transferable; 195 if (params) { 196 nsCOMPtr<nsISupports> supports = params->GetISupports("transferable"); 197 transferable = do_QueryInterface(supports); 198 } 199 nsresult rv = DoCommandParam( 200 command, transferable, MOZ_KnownLive(*editor->AsEditorBase()), nullptr); 201 NS_WARNING_ASSERTION( 202 NS_SUCCEEDED(rv), 203 "Failed to do command from nsIControllerCommand::DoCommandParams()"); 204 return rv; 205 } 206 207 MOZ_ASSERT_UNREACHABLE("Unexpected param type"); 208 return NS_ERROR_NOT_IMPLEMENTED; 209 } 210 211 void EditorCommand::GetCommandStateParams(const nsACString& aCommandName, 212 nsICommandParams* aParams, 213 nsISupports* aCommandRefCon) { 214 MOZ_ASSERT(aParams); 215 if (nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon)) { 216 GetCommandStateParams(GetInternalCommand(aCommandName), 217 MOZ_KnownLive(*aParams->AsCommandParams()), 218 MOZ_KnownLive(editor->AsEditorBase()), nullptr); 219 return; 220 } 221 nsCOMPtr<nsIEditingSession> editingSession = 222 do_QueryInterface(aCommandRefCon); 223 GetCommandStateParams(GetInternalCommand(aCommandName), 224 MOZ_KnownLive(*aParams->AsCommandParams()), nullptr, 225 editingSession); 226 } 227 228 /****************************************************************************** 229 * mozilla::UndoCommand 230 ******************************************************************************/ 231 232 StaticRefPtr<UndoCommand> UndoCommand::sInstance; 233 234 bool UndoCommand::IsCommandEnabled(Command aCommand, 235 EditorBase* aEditorBase) const { 236 if (!aEditorBase) { 237 return false; 238 } 239 return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable() && 240 aEditorBase->CanUndo(); 241 } 242 243 nsresult UndoCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, 244 nsIPrincipal* aPrincipal) const { 245 nsresult rv = aEditorBase.UndoAsAction(1, aPrincipal); 246 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::UndoAsAction() failed"); 247 return rv; 248 } 249 250 nsresult UndoCommand::GetCommandStateParams( 251 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 252 nsIEditingSession* aEditingSession) const { 253 return aParams.SetBool(STATE_ENABLED, 254 IsCommandEnabled(aCommand, aEditorBase)); 255 } 256 257 /****************************************************************************** 258 * mozilla::RedoCommand 259 ******************************************************************************/ 260 261 StaticRefPtr<RedoCommand> RedoCommand::sInstance; 262 263 bool RedoCommand::IsCommandEnabled(Command aCommand, 264 EditorBase* aEditorBase) const { 265 if (!aEditorBase) { 266 return false; 267 } 268 return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable() && 269 aEditorBase->CanRedo(); 270 } 271 272 nsresult RedoCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, 273 nsIPrincipal* aPrincipal) const { 274 nsresult rv = aEditorBase.RedoAsAction(1, aPrincipal); 275 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::RedoAsAction() failed"); 276 return rv; 277 } 278 279 nsresult RedoCommand::GetCommandStateParams( 280 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 281 nsIEditingSession* aEditingSession) const { 282 return aParams.SetBool(STATE_ENABLED, 283 IsCommandEnabled(aCommand, aEditorBase)); 284 } 285 286 /****************************************************************************** 287 * mozilla::CutCommand 288 ******************************************************************************/ 289 290 StaticRefPtr<CutCommand> CutCommand::sInstance; 291 292 bool CutCommand::IsCommandEnabled(Command aCommand, 293 EditorBase* aEditorBase) const { 294 if (!aEditorBase) { 295 return false; 296 } 297 return aEditorBase->IsSelectionEditable() && 298 aEditorBase->IsCutCommandEnabled(); 299 } 300 301 nsresult CutCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, 302 nsIPrincipal* aPrincipal) const { 303 nsresult rv = aEditorBase.CutAsAction(aPrincipal); 304 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CutAsAction() failed"); 305 return rv; 306 } 307 308 nsresult CutCommand::GetCommandStateParams( 309 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 310 nsIEditingSession* aEditingSession) const { 311 return aParams.SetBool(STATE_ENABLED, 312 IsCommandEnabled(aCommand, aEditorBase)); 313 } 314 315 /****************************************************************************** 316 * mozilla::CutOrDeleteCommand 317 ******************************************************************************/ 318 319 StaticRefPtr<CutOrDeleteCommand> CutOrDeleteCommand::sInstance; 320 321 bool CutOrDeleteCommand::IsCommandEnabled(Command aCommand, 322 EditorBase* aEditorBase) const { 323 if (!aEditorBase) { 324 return false; 325 } 326 return aEditorBase->IsSelectionEditable(); 327 } 328 329 nsresult CutOrDeleteCommand::DoCommand(Command aCommand, 330 EditorBase& aEditorBase, 331 nsIPrincipal* aPrincipal) const { 332 dom::Selection* selection = aEditorBase.GetSelection(); 333 if (selection && selection->IsCollapsed()) { 334 nsresult rv = aEditorBase.DeleteSelectionAsAction( 335 nsIEditor::eNext, nsIEditor::eStrip, aPrincipal); 336 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 337 "EditorBase::DeleteSelectionAsAction() failed"); 338 return rv; 339 } 340 nsresult rv = aEditorBase.CutAsAction(aPrincipal); 341 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CutAsAction() failed"); 342 return rv; 343 } 344 345 nsresult CutOrDeleteCommand::GetCommandStateParams( 346 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 347 nsIEditingSession* aEditingSession) const { 348 return aParams.SetBool(STATE_ENABLED, 349 IsCommandEnabled(aCommand, aEditorBase)); 350 } 351 352 /****************************************************************************** 353 * mozilla::CopyCommand 354 ******************************************************************************/ 355 356 StaticRefPtr<CopyCommand> CopyCommand::sInstance; 357 358 bool CopyCommand::IsCommandEnabled(Command aCommand, 359 EditorBase* aEditorBase) const { 360 if (!aEditorBase) { 361 return false; 362 } 363 return aEditorBase->IsCopyCommandEnabled(); 364 } 365 366 nsresult CopyCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, 367 nsIPrincipal* aPrincipal) const { 368 // Shouldn't cause "beforeinput" event so that we don't need to specify 369 // the given principal. 370 return aEditorBase.Copy(); 371 } 372 373 nsresult CopyCommand::GetCommandStateParams( 374 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 375 nsIEditingSession* aEditingSession) const { 376 return aParams.SetBool(STATE_ENABLED, 377 IsCommandEnabled(aCommand, aEditorBase)); 378 } 379 380 /****************************************************************************** 381 * mozilla::CopyOrDeleteCommand 382 ******************************************************************************/ 383 384 StaticRefPtr<CopyOrDeleteCommand> CopyOrDeleteCommand::sInstance; 385 386 bool CopyOrDeleteCommand::IsCommandEnabled(Command aCommand, 387 EditorBase* aEditorBase) const { 388 if (!aEditorBase) { 389 return false; 390 } 391 return aEditorBase->IsSelectionEditable(); 392 } 393 394 nsresult CopyOrDeleteCommand::DoCommand(Command aCommand, 395 EditorBase& aEditorBase, 396 nsIPrincipal* aPrincipal) const { 397 dom::Selection* selection = aEditorBase.GetSelection(); 398 if (selection && selection->IsCollapsed()) { 399 nsresult rv = aEditorBase.DeleteSelectionAsAction( 400 nsIEditor::eNextWord, nsIEditor::eStrip, aPrincipal); 401 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 402 "EditorBase::DeleteSelectionAsAction() failed"); 403 return rv; 404 } 405 // Shouldn't cause "beforeinput" event so that we don't need to specify 406 // the given principal. 407 nsresult rv = aEditorBase.Copy(); 408 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::Copy() failed"); 409 return rv; 410 } 411 412 nsresult CopyOrDeleteCommand::GetCommandStateParams( 413 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 414 nsIEditingSession* aEditingSession) const { 415 return aParams.SetBool(STATE_ENABLED, 416 IsCommandEnabled(aCommand, aEditorBase)); 417 } 418 419 /****************************************************************************** 420 * mozilla::PasteCommand 421 ******************************************************************************/ 422 423 StaticRefPtr<PasteCommand> PasteCommand::sInstance; 424 425 bool PasteCommand::IsCommandEnabled(Command aCommand, 426 EditorBase* aEditorBase) const { 427 if (!aEditorBase) { 428 return false; 429 } 430 return aEditorBase->IsSelectionEditable() && 431 aEditorBase->CanPaste(nsIClipboard::kGlobalClipboard); 432 } 433 434 nsresult PasteCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, 435 nsIPrincipal* aPrincipal) const { 436 RefPtr<dom::DataTransfer> dataTransfer; 437 nsCOMPtr<nsIPrincipal> subjectPrincipal = 438 aPrincipal ? aPrincipal 439 : nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); 440 MOZ_ASSERT(subjectPrincipal); 441 442 // If we don't need to get user confirmation for clipboard access, we could 443 // just let EditorBase::PasteAsAction() to create DataTransfer instance 444 // synchronously for paste event. Otherwise, we need to spin the event loop to 445 // wait for the clipboard paste contextmenu to be shown and get user 446 // confirmation which are all handled in parent process before sending the 447 // paste event. 448 if (!nsContentUtils::PrincipalHasPermission(*subjectPrincipal, 449 nsGkAtoms::clipboardRead)) { 450 MOZ_DIAGNOSTIC_ASSERT(StaticPrefs::dom_execCommand_paste_enabled(), 451 "How did we get here?"); 452 // This will spin the event loop. 453 nsCOMPtr<nsPIDOMWindowOuter> window = aEditorBase.GetWindow(); 454 dataTransfer = dom::DataTransfer::WaitForClipboardDataSnapshotAndCreate( 455 window, subjectPrincipal); 456 if (!dataTransfer) { 457 return NS_SUCCESS_DOM_NO_OPERATION; 458 } 459 } 460 461 nsresult rv = aEditorBase.PasteAsAction(nsIClipboard::kGlobalClipboard, 462 EditorBase::DispatchPasteEvent::Yes, 463 dataTransfer, aPrincipal); 464 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 465 "EditorBase::PasteAsAction(nsIClipboard::" 466 "kGlobalClipboard, DispatchPasteEvent::Yes) failed"); 467 return rv; 468 } 469 470 nsresult PasteCommand::GetCommandStateParams( 471 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 472 nsIEditingSession* aEditingSession) const { 473 return aParams.SetBool(STATE_ENABLED, 474 IsCommandEnabled(aCommand, aEditorBase)); 475 } 476 477 /****************************************************************************** 478 * mozilla::PasteTransferableCommand 479 ******************************************************************************/ 480 481 StaticRefPtr<PasteTransferableCommand> PasteTransferableCommand::sInstance; 482 483 bool PasteTransferableCommand::IsCommandEnabled(Command aCommand, 484 EditorBase* aEditorBase) const { 485 if (!aEditorBase) { 486 return false; 487 } 488 return aEditorBase->IsSelectionEditable() && 489 aEditorBase->CanPasteTransferable(nullptr); 490 } 491 492 nsresult PasteTransferableCommand::DoCommand(Command aCommand, 493 EditorBase& aEditorBase, 494 nsIPrincipal* aPrincipal) const { 495 return NS_ERROR_FAILURE; 496 } 497 498 nsresult PasteTransferableCommand::DoCommandParam( 499 Command aCommand, nsITransferable* aTransferableParam, 500 EditorBase& aEditorBase, nsIPrincipal* aPrincipal) const { 501 if (NS_WARN_IF(!aTransferableParam)) { 502 return NS_ERROR_INVALID_ARG; 503 } 504 nsresult rv = aEditorBase.PasteTransferableAsAction( 505 aTransferableParam, EditorBase::DispatchPasteEvent::Yes, aPrincipal); 506 NS_WARNING_ASSERTION( 507 NS_SUCCEEDED(rv), 508 "EditorBase::PasteTransferableAsAction(DispatchPasteEvent::Yes) failed"); 509 return rv; 510 } 511 512 nsresult PasteTransferableCommand::GetCommandStateParams( 513 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 514 nsIEditingSession* aEditingSession) const { 515 if (NS_WARN_IF(!aEditorBase)) { 516 return NS_ERROR_INVALID_ARG; 517 } 518 519 nsCOMPtr<nsISupports> supports = aParams.GetISupports("transferable"); 520 if (NS_WARN_IF(!supports)) { 521 return NS_ERROR_FAILURE; 522 } 523 524 nsCOMPtr<nsITransferable> trans; 525 trans = do_QueryInterface(supports); 526 if (NS_WARN_IF(!trans)) { 527 return NS_ERROR_FAILURE; 528 } 529 530 return aParams.SetBool(STATE_ENABLED, 531 aEditorBase->CanPasteTransferable(trans)); 532 } 533 534 /****************************************************************************** 535 * mozilla::SwitchTextDirectionCommand 536 ******************************************************************************/ 537 538 StaticRefPtr<SwitchTextDirectionCommand> SwitchTextDirectionCommand::sInstance; 539 540 bool SwitchTextDirectionCommand::IsCommandEnabled( 541 Command aCommand, EditorBase* aEditorBase) const { 542 if (!aEditorBase) { 543 return false; 544 } 545 return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable(); 546 } 547 548 nsresult SwitchTextDirectionCommand::DoCommand(Command aCommand, 549 EditorBase& aEditorBase, 550 nsIPrincipal* aPrincipal) const { 551 nsresult rv = aEditorBase.ToggleTextDirectionAsAction(aPrincipal); 552 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 553 "EditorBase::ToggleTextDirectionAsAction() failed"); 554 return rv; 555 } 556 557 nsresult SwitchTextDirectionCommand::GetCommandStateParams( 558 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 559 nsIEditingSession* aEditingSession) const { 560 return aParams.SetBool(STATE_ENABLED, 561 IsCommandEnabled(aCommand, aEditorBase)); 562 } 563 564 /****************************************************************************** 565 * mozilla::DeleteCommand 566 ******************************************************************************/ 567 568 StaticRefPtr<DeleteCommand> DeleteCommand::sInstance; 569 570 bool DeleteCommand::IsCommandEnabled(Command aCommand, 571 EditorBase* aEditorBase) const { 572 if (!aEditorBase) { 573 return false; 574 } 575 // We can generally delete whenever the selection is editable. However, 576 // cmd_delete doesn't make sense if the selection is collapsed because it's 577 // directionless. 578 bool isEnabled = 579 aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable(); 580 581 if (aCommand == Command::Delete && isEnabled) { 582 return aEditorBase->CanDeleteSelection(); 583 } 584 return isEnabled; 585 } 586 587 nsresult DeleteCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, 588 nsIPrincipal* aPrincipal) const { 589 nsIEditor::EDirection deleteDir = nsIEditor::eNone; 590 switch (aCommand) { 591 case Command::Delete: 592 // Really this should probably be eNone, but it only makes a difference 593 // if the selection is collapsed, and then this command is disabled. So 594 // let's keep it as it always was to avoid breaking things. 595 deleteDir = nsIEditor::ePrevious; 596 break; 597 case Command::DeleteCharForward: 598 deleteDir = nsIEditor::eNext; 599 break; 600 case Command::DeleteCharBackward: 601 deleteDir = nsIEditor::ePrevious; 602 break; 603 case Command::DeleteWordBackward: 604 deleteDir = nsIEditor::ePreviousWord; 605 break; 606 case Command::DeleteWordForward: 607 deleteDir = nsIEditor::eNextWord; 608 break; 609 case Command::DeleteToBeginningOfLine: 610 deleteDir = nsIEditor::eToBeginningOfLine; 611 break; 612 case Command::DeleteToEndOfLine: 613 deleteDir = nsIEditor::eToEndOfLine; 614 break; 615 default: 616 MOZ_CRASH("Unrecognized nsDeleteCommand"); 617 } 618 nsresult rv = aEditorBase.DeleteSelectionAsAction( 619 deleteDir, nsIEditor::eStrip, aPrincipal); 620 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 621 "EditorBase::DeleteSelectionAsAction() failed"); 622 return rv; 623 } 624 625 nsresult DeleteCommand::GetCommandStateParams( 626 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 627 nsIEditingSession* aEditingSession) const { 628 return aParams.SetBool(STATE_ENABLED, 629 IsCommandEnabled(aCommand, aEditorBase)); 630 } 631 632 /****************************************************************************** 633 * mozilla::SelectAllCommand 634 ******************************************************************************/ 635 636 StaticRefPtr<SelectAllCommand> SelectAllCommand::sInstance; 637 638 bool SelectAllCommand::IsCommandEnabled(Command aCommand, 639 EditorBase* aEditorBase) const { 640 // You can always select all, unless the selection is editable, 641 // and the editable region is empty! 642 if (!aEditorBase) { 643 return true; 644 } 645 646 // You can select all if there is an editor which is non-empty 647 return !aEditorBase->IsEmpty(); 648 } 649 650 nsresult SelectAllCommand::DoCommand(Command aCommand, EditorBase& aEditorBase, 651 nsIPrincipal* aPrincipal) const { 652 // Shouldn't cause "beforeinput" event so that we don't need to specify 653 // aPrincipal. 654 nsresult rv = aEditorBase.SelectAll(); 655 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::SelectAll() failed"); 656 return rv; 657 } 658 659 nsresult SelectAllCommand::GetCommandStateParams( 660 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 661 nsIEditingSession* aEditingSession) const { 662 return aParams.SetBool(STATE_ENABLED, 663 IsCommandEnabled(aCommand, aEditorBase)); 664 } 665 666 /****************************************************************************** 667 * mozilla::SelectionMoveCommands 668 ******************************************************************************/ 669 670 StaticRefPtr<SelectionMoveCommands> SelectionMoveCommands::sInstance; 671 672 bool SelectionMoveCommands::IsCommandEnabled(Command aCommand, 673 EditorBase* aEditorBase) const { 674 if (!aEditorBase) { 675 return false; 676 } 677 return aEditorBase->IsSelectionEditable(); 678 } 679 680 static const struct ScrollCommand { 681 Command mReverseScroll; 682 Command mForwardScroll; 683 nsresult (NS_STDCALL nsISelectionController::*scroll)(bool); 684 } scrollCommands[] = {{Command::ScrollTop, Command::ScrollBottom, 685 &nsISelectionController::CompleteScroll}, 686 {Command::ScrollPageUp, Command::ScrollPageDown, 687 &nsISelectionController::ScrollPage}, 688 {Command::ScrollLineUp, Command::ScrollLineDown, 689 &nsISelectionController::ScrollLine}}; 690 691 static const struct MoveCommand { 692 Command mReverseMove; 693 Command mForwardMove; 694 Command mReverseSelect; 695 Command mForwardSelect; 696 nsresult (NS_STDCALL nsISelectionController::*move)(bool, bool); 697 } moveCommands[] = { 698 {Command::CharPrevious, Command::CharNext, Command::SelectCharPrevious, 699 Command::SelectCharNext, &nsISelectionController::CharacterMove}, 700 {Command::LinePrevious, Command::LineNext, Command::SelectLinePrevious, 701 Command::SelectLineNext, &nsISelectionController::LineMove}, 702 {Command::WordPrevious, Command::WordNext, Command::SelectWordPrevious, 703 Command::SelectWordNext, &nsISelectionController::WordMove}, 704 {Command::BeginLine, Command::EndLine, Command::SelectBeginLine, 705 Command::SelectEndLine, &nsISelectionController::IntraLineMove}, 706 {Command::MovePageUp, Command::MovePageDown, Command::SelectPageUp, 707 Command::SelectPageDown, &nsISelectionController::PageMove}, 708 {Command::MoveTop, Command::MoveBottom, Command::SelectTop, 709 Command::SelectBottom, &nsISelectionController::CompleteMove}}; 710 711 static const struct PhysicalCommand { 712 Command mMove; 713 Command mSelect; 714 int16_t direction; 715 int16_t amount; 716 } physicalCommands[] = { 717 {Command::MoveLeft, Command::SelectLeft, nsISelectionController::MOVE_LEFT, 718 0}, 719 {Command::MoveRight, Command::SelectRight, 720 nsISelectionController::MOVE_RIGHT, 0}, 721 {Command::MoveUp, Command::SelectUp, nsISelectionController::MOVE_UP, 0}, 722 {Command::MoveDown, Command::SelectDown, nsISelectionController::MOVE_DOWN, 723 0}, 724 {Command::MoveLeft2, Command::SelectLeft2, 725 nsISelectionController::MOVE_LEFT, 1}, 726 {Command::MoveRight2, Command::SelectRight2, 727 nsISelectionController::MOVE_RIGHT, 1}, 728 {Command::MoveUp2, Command::SelectUp2, nsISelectionController::MOVE_UP, 1}, 729 {Command::MoveDown2, Command::SelectDown2, 730 nsISelectionController::MOVE_DOWN, 1}}; 731 732 nsresult SelectionMoveCommands::DoCommand(Command aCommand, 733 EditorBase& aEditorBase, 734 nsIPrincipal* aPrincipal) const { 735 RefPtr<dom::Document> document = aEditorBase.GetDocument(); 736 if (document) { 737 // Most of the commands below (possibly all of them) need layout to 738 // be up to date. 739 document->FlushPendingNotifications(FlushType::Layout); 740 } 741 742 nsCOMPtr<nsISelectionController> selectionController = 743 aEditorBase.GetSelectionController(); 744 if (NS_WARN_IF(!selectionController)) { 745 return NS_ERROR_FAILURE; 746 } 747 748 // scroll commands 749 for (size_t i = 0; i < std::size(scrollCommands); i++) { 750 const ScrollCommand& cmd = scrollCommands[i]; 751 if (aCommand == cmd.mReverseScroll) { 752 return (selectionController->*(cmd.scroll))(false); 753 } 754 if (aCommand == cmd.mForwardScroll) { 755 return (selectionController->*(cmd.scroll))(true); 756 } 757 } 758 759 // caret movement/selection commands 760 for (size_t i = 0; i < std::size(moveCommands); i++) { 761 const MoveCommand& cmd = moveCommands[i]; 762 if (aCommand == cmd.mReverseMove) { 763 return (selectionController->*(cmd.move))(false, false); 764 } 765 if (aCommand == cmd.mForwardMove) { 766 return (selectionController->*(cmd.move))(true, false); 767 } 768 if (aCommand == cmd.mReverseSelect) { 769 return (selectionController->*(cmd.move))(false, true); 770 } 771 if (aCommand == cmd.mForwardSelect) { 772 return (selectionController->*(cmd.move))(true, true); 773 } 774 } 775 776 // physical-direction movement/selection 777 for (size_t i = 0; i < std::size(physicalCommands); i++) { 778 const PhysicalCommand& cmd = physicalCommands[i]; 779 if (aCommand == cmd.mMove) { 780 nsresult rv = 781 selectionController->PhysicalMove(cmd.direction, cmd.amount, false); 782 NS_WARNING_ASSERTION( 783 NS_SUCCEEDED(rv), 784 "nsISelectionController::PhysicalMove() failed to move caret"); 785 return rv; 786 } 787 if (aCommand == cmd.mSelect) { 788 nsresult rv = 789 selectionController->PhysicalMove(cmd.direction, cmd.amount, true); 790 NS_WARNING_ASSERTION( 791 NS_SUCCEEDED(rv), 792 "nsISelectionController::PhysicalMove() failed to select"); 793 return rv; 794 } 795 } 796 797 return NS_ERROR_FAILURE; 798 } 799 800 nsresult SelectionMoveCommands::GetCommandStateParams( 801 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 802 nsIEditingSession* aEditingSession) const { 803 return aParams.SetBool(STATE_ENABLED, 804 IsCommandEnabled(aCommand, aEditorBase)); 805 } 806 807 /****************************************************************************** 808 * mozilla::InsertPlaintextCommand 809 ******************************************************************************/ 810 811 StaticRefPtr<InsertPlaintextCommand> InsertPlaintextCommand::sInstance; 812 813 bool InsertPlaintextCommand::IsCommandEnabled(Command aCommand, 814 EditorBase* aEditorBase) const { 815 if (!aEditorBase) { 816 return false; 817 } 818 return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable(); 819 } 820 821 nsresult InsertPlaintextCommand::DoCommand(Command aCommand, 822 EditorBase& aEditorBase, 823 nsIPrincipal* aPrincipal) const { 824 // XXX InsertTextAsAction() is not same as OnInputText(). However, other 825 // commands to insert line break or paragraph separator use OnInput*(). 826 // According to the semantics of those methods, using *AsAction() is 827 // better, however, this may not cause two or more placeholder 828 // transactions to the top transaction since its name may not be 829 // nsGkAtoms::TypingTxnName. 830 DebugOnly<nsresult> rvIgnored = 831 aEditorBase.InsertTextAsAction(u""_ns, aPrincipal); 832 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 833 "EditorBase::InsertTextAsAction() failed, but ignored"); 834 return NS_OK; 835 } 836 837 nsresult InsertPlaintextCommand::DoCommandParam( 838 Command aCommand, const nsAString& aStringParam, EditorBase& aEditorBase, 839 nsIPrincipal* aPrincipal) const { 840 if (NS_WARN_IF(aStringParam.IsVoid())) { 841 return NS_ERROR_INVALID_ARG; 842 } 843 844 // XXX InsertTextAsAction() is not same as OnInputText(). However, other 845 // commands to insert line break or paragraph separator use OnInput*(). 846 // According to the semantics of those methods, using *AsAction() is 847 // better, however, this may not cause two or more placeholder 848 // transactions to the top transaction since its name may not be 849 // nsGkAtoms::TypingTxnName. 850 DebugOnly<nsresult> rvIgnored = 851 aEditorBase.InsertTextAsAction(aStringParam, aPrincipal); 852 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 853 "EditorBase::InsertTextAsAction() failed, but ignored"); 854 return NS_OK; 855 } 856 857 nsresult InsertPlaintextCommand::GetCommandStateParams( 858 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 859 nsIEditingSession* aEditingSession) const { 860 return aParams.SetBool(STATE_ENABLED, 861 IsCommandEnabled(aCommand, aEditorBase)); 862 } 863 864 /****************************************************************************** 865 * mozilla::InsertParagraphCommand 866 ******************************************************************************/ 867 868 StaticRefPtr<InsertParagraphCommand> InsertParagraphCommand::sInstance; 869 870 bool InsertParagraphCommand::IsCommandEnabled(Command aCommand, 871 EditorBase* aEditorBase) const { 872 if (!aEditorBase || aEditorBase->IsSingleLineEditor()) { 873 return false; 874 } 875 return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable(); 876 } 877 878 nsresult InsertParagraphCommand::DoCommand(Command aCommand, 879 EditorBase& aEditorBase, 880 nsIPrincipal* aPrincipal) const { 881 if (aEditorBase.IsSingleLineEditor()) { 882 return NS_ERROR_FAILURE; 883 } 884 if (aEditorBase.IsHTMLEditor()) { 885 nsresult rv = MOZ_KnownLive(aEditorBase.AsHTMLEditor()) 886 ->InsertParagraphSeparatorAsAction(aPrincipal); 887 NS_WARNING_ASSERTION( 888 NS_SUCCEEDED(rv), 889 "HTMLEditor::InsertParagraphSeparatorAsAction() failed"); 890 return rv; 891 } 892 nsresult rv = aEditorBase.InsertLineBreakAsAction(aPrincipal); 893 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 894 "EditorBase::InsertLineBreakAsAction() failed"); 895 return rv; 896 } 897 898 nsresult InsertParagraphCommand::GetCommandStateParams( 899 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 900 nsIEditingSession* aEditingSession) const { 901 return aParams.SetBool(STATE_ENABLED, 902 IsCommandEnabled(aCommand, aEditorBase)); 903 } 904 905 /****************************************************************************** 906 * mozilla::InsertLineBreakCommand 907 ******************************************************************************/ 908 909 StaticRefPtr<InsertLineBreakCommand> InsertLineBreakCommand::sInstance; 910 911 bool InsertLineBreakCommand::IsCommandEnabled(Command aCommand, 912 EditorBase* aEditorBase) const { 913 if (!aEditorBase || aEditorBase->IsSingleLineEditor()) { 914 return false; 915 } 916 return aEditorBase->IsModifiable() && aEditorBase->IsSelectionEditable(); 917 } 918 919 nsresult InsertLineBreakCommand::DoCommand(Command aCommand, 920 EditorBase& aEditorBase, 921 nsIPrincipal* aPrincipal) const { 922 if (aEditorBase.IsSingleLineEditor()) { 923 return NS_ERROR_FAILURE; 924 } 925 nsresult rv = aEditorBase.InsertLineBreakAsAction(aPrincipal); 926 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 927 "EditorBase::InsertLineBreakAsAction() failed"); 928 return rv; 929 } 930 931 nsresult InsertLineBreakCommand::GetCommandStateParams( 932 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 933 nsIEditingSession* aEditingSession) const { 934 return aParams.SetBool(STATE_ENABLED, 935 IsCommandEnabled(aCommand, aEditorBase)); 936 } 937 938 /****************************************************************************** 939 * mozilla::PasteQuotationCommand 940 ******************************************************************************/ 941 942 StaticRefPtr<PasteQuotationCommand> PasteQuotationCommand::sInstance; 943 944 bool PasteQuotationCommand::IsCommandEnabled(Command aCommand, 945 EditorBase* aEditorBase) const { 946 if (!aEditorBase) { 947 return false; 948 } 949 return !aEditorBase->IsSingleLineEditor() && 950 aEditorBase->CanPaste(nsIClipboard::kGlobalClipboard); 951 } 952 953 nsresult PasteQuotationCommand::DoCommand(Command aCommand, 954 EditorBase& aEditorBase, 955 nsIPrincipal* aPrincipal) const { 956 nsresult rv = aEditorBase.PasteAsQuotationAsAction( 957 nsIClipboard::kGlobalClipboard, EditorBase::DispatchPasteEvent::Yes, 958 nullptr, aPrincipal); 959 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 960 "EditorBase::PasteAsQuotationAsAction(nsIClipboard::" 961 "kGlobalClipboard, DispatchPasteEvent::Yes) failed"); 962 return rv; 963 } 964 965 nsresult PasteQuotationCommand::GetCommandStateParams( 966 Command aCommand, nsCommandParams& aParams, EditorBase* aEditorBase, 967 nsIEditingSession* aEditingSession) const { 968 if (!aEditorBase) { 969 return NS_OK; 970 } 971 aParams.SetBool(STATE_ENABLED, 972 aEditorBase->CanPaste(nsIClipboard::kGlobalClipboard)); 973 return NS_OK; 974 } 975 976 } // namespace mozilla