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