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/. */
6 #include "EditorCommands.h"
8 #include "EditorBase.h" // for EditorBase
10 #include "HTMLEditor.h" // for HTMLEditor
12 #include "mozilla/BasePrincipal.h" // for nsIPrincipal::IsSystemPrincipal()
13 #include "mozilla/dom/Element.h" // for Element
14 #include "mozilla/dom/Document.h" // for Document
15 #include "mozilla/dom/HTMLInputElement.h" // for HTMLInputElement
16 #include "mozilla/dom/HTMLTextAreaElement.h" // for HTMLTextAreaElement
18 #include "nsCommandParams.h" // for nsCommandParams
19 #include "nsIEditingSession.h" // for nsIEditingSession, etc
20 #include "nsIPrincipal.h" // for nsIPrincipal
21 #include "nsISupportsImpl.h" // for nsPresContext::Release
22 #include "nsISupportsUtils.h" // for NS_IF_ADDREF
23 #include "nsIURI.h" // for nsIURI
24 #include "nsPresContext.h" // for nsPresContext
27 #define STATE_ENABLED "state_enabled"
28 #define STATE_ALL "state_all"
29 #define STATE_ATTRIBUTE "state_attribute"
30 #define STATE_DATA "state_data"
36 /*****************************************************************************
37 * mozilla::SetDocumentStateCommand
39 * Commands for document state that may be changed via doCommandParams
40 * As of 11/11/02, this is just "cmd_setDocumentModified"
41 * Note that you can use the same command class, SetDocumentStateCommand,
42 * for more than one of this type of command
43 * We check the input command param for different behavior
44 *****************************************************************************/
46 StaticRefPtr
<SetDocumentStateCommand
> SetDocumentStateCommand::sInstance
;
48 bool SetDocumentStateCommand::IsCommandEnabled(Command aCommand
,
49 EditorBase
* aEditorBase
) const {
51 case Command::SetDocumentReadOnly
:
54 // The other commands are always enabled if given editor is an HTMLEditor.
55 return aEditorBase
&& aEditorBase
->IsHTMLEditor();
59 nsresult
SetDocumentStateCommand::DoCommand(Command aCommand
,
60 EditorBase
& aEditorBase
,
61 nsIPrincipal
* aPrincipal
) const {
62 return NS_ERROR_NOT_IMPLEMENTED
;
65 nsresult
SetDocumentStateCommand::DoCommandParam(
66 Command aCommand
, const Maybe
<bool>& aBoolParam
, EditorBase
& aEditorBase
,
67 nsIPrincipal
* aPrincipal
) const {
68 if (NS_WARN_IF(aBoolParam
.isNothing())) {
69 return NS_ERROR_INVALID_ARG
;
72 if (aCommand
!= Command::SetDocumentReadOnly
&&
73 NS_WARN_IF(!aEditorBase
.IsHTMLEditor())) {
74 return NS_ERROR_FAILURE
;
78 case Command::SetDocumentModified
: {
79 if (aBoolParam
.value()) {
80 nsresult rv
= aEditorBase
.IncrementModificationCount(1);
81 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
82 "EditorBase::IncrementModificationCount() failed");
85 nsresult rv
= aEditorBase
.ResetModificationCount();
86 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
87 "EditorBase::ResetModificationCount() failed");
90 case Command::SetDocumentReadOnly
: {
91 if (aEditorBase
.IsTextEditor()) {
92 Element
* inputOrTextArea
= aEditorBase
.GetExposedRoot();
93 if (NS_WARN_IF(!inputOrTextArea
)) {
94 return NS_ERROR_FAILURE
;
96 // Perhaps, this legacy command shouldn't work with
97 // `<input type="file">` and `<input type="number">.
98 if (inputOrTextArea
->IsInNativeAnonymousSubtree()) {
99 return NS_ERROR_FAILURE
;
101 if (RefPtr
<HTMLInputElement
> inputElement
=
102 HTMLInputElement::FromNode(inputOrTextArea
)) {
103 if (inputElement
->ReadOnly() == aBoolParam
.value()) {
104 return NS_SUCCESS_DOM_NO_OPERATION
;
107 inputElement
->SetReadOnly(aBoolParam
.value(), error
);
108 return error
.StealNSResult();
110 if (RefPtr
<HTMLTextAreaElement
> textAreaElement
=
111 HTMLTextAreaElement::FromNode(inputOrTextArea
)) {
112 if (textAreaElement
->ReadOnly() == aBoolParam
.value()) {
113 return NS_SUCCESS_DOM_NO_OPERATION
;
116 textAreaElement
->SetReadOnly(aBoolParam
.value(), error
);
117 return error
.StealNSResult();
121 "Unexpected exposed root element, fallthrough to directly make the "
125 if (aBoolParam
.value()) {
126 nsresult rv
= aEditorBase
.AddFlags(nsIEditor::eEditorReadonlyMask
);
127 NS_WARNING_ASSERTION(
129 "EditorBase::AddFlags(nsIEditor::eEditorReadonlyMask) failed");
132 nsresult rv
= aEditorBase
.RemoveFlags(nsIEditor::eEditorReadonlyMask
);
133 NS_WARNING_ASSERTION(
135 "EditorBase::RemoveFlags(nsIEditor::eEditorReadonlyMask) failed");
138 case Command::SetDocumentUseCSS
: {
139 nsresult rv
= MOZ_KnownLive(aEditorBase
.AsHTMLEditor())
140 ->SetIsCSSEnabled(aBoolParam
.value());
141 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
142 "HTMLEditor::SetIsCSSEnabled() failed");
145 case Command::SetDocumentInsertBROnEnterKeyPress
: {
147 aEditorBase
.AsHTMLEditor()->SetReturnInParagraphCreatesNewParagraph(
148 !aBoolParam
.value());
149 NS_WARNING_ASSERTION(
151 "HTMLEditor::SetReturnInParagraphCreatesNewParagraph() failed");
154 case Command::ToggleObjectResizers
: {
155 MOZ_KnownLive(aEditorBase
.AsHTMLEditor())
156 ->EnableObjectResizer(aBoolParam
.value());
159 case Command::ToggleInlineTableEditor
: {
160 MOZ_KnownLive(aEditorBase
.AsHTMLEditor())
161 ->EnableInlineTableEditor(aBoolParam
.value());
164 case Command::ToggleAbsolutePositionEditor
: {
165 MOZ_KnownLive(aEditorBase
.AsHTMLEditor())
166 ->EnableAbsolutePositionEditor(aBoolParam
.value());
169 case Command::EnableCompatibleJoinSplitNodeDirection
:
170 // Now we don't support the legacy join/split node direction anymore, but
171 // this result may be used for the feature detection whether Gecko
172 // supports the new direction mode. Therefore, even though we do nothing,
173 // but we should return NS_OK to return `true` from
174 // `Document.execCommand()`.
177 return NS_ERROR_NOT_IMPLEMENTED
;
181 nsresult
SetDocumentStateCommand::DoCommandParam(
182 Command aCommand
, const nsACString
& aCStringParam
, EditorBase
& aEditorBase
,
183 nsIPrincipal
* aPrincipal
) const {
184 if (NS_WARN_IF(aCStringParam
.IsVoid())) {
185 return NS_ERROR_INVALID_ARG
;
188 if (NS_WARN_IF(!aEditorBase
.IsHTMLEditor())) {
189 return NS_ERROR_FAILURE
;
193 case Command::SetDocumentDefaultParagraphSeparator
: {
194 if (aCStringParam
.LowerCaseEqualsLiteral("div")) {
195 aEditorBase
.AsHTMLEditor()->SetDefaultParagraphSeparator(
196 ParagraphSeparator::div
);
199 if (aCStringParam
.LowerCaseEqualsLiteral("p")) {
200 aEditorBase
.AsHTMLEditor()->SetDefaultParagraphSeparator(
201 ParagraphSeparator::p
);
204 if (aCStringParam
.LowerCaseEqualsLiteral("br")) {
205 // Mozilla extension for backwards compatibility
206 aEditorBase
.AsHTMLEditor()->SetDefaultParagraphSeparator(
207 ParagraphSeparator::br
);
211 // This should not be reachable from nsHTMLDocument::ExecCommand
212 // XXX Shouldn't return error in this case because Chrome does not throw
213 // exception in this case.
214 NS_WARNING("Invalid default paragraph separator");
215 return NS_ERROR_UNEXPECTED
;
218 return NS_ERROR_NOT_IMPLEMENTED
;
222 nsresult
SetDocumentStateCommand::GetCommandStateParams(
223 Command aCommand
, nsCommandParams
& aParams
, EditorBase
* aEditorBase
,
224 nsIEditingSession
* aEditingSession
) const {
225 // If the result is set to STATE_ATTRIBUTE as CString value,
226 // queryCommandValue() returns the string value.
227 // Otherwise, ignored.
229 // The base editor owns most state info
230 if (NS_WARN_IF(!aEditorBase
)) {
231 return NS_ERROR_INVALID_ARG
;
234 if (NS_WARN_IF(!aEditorBase
->IsHTMLEditor())) {
235 return NS_ERROR_FAILURE
;
238 // Always get the enabled state
240 aParams
.SetBool(STATE_ENABLED
, IsCommandEnabled(aCommand
, aEditorBase
));
241 if (NS_WARN_IF(NS_FAILED(rv
))) {
246 case Command::SetDocumentModified
: {
248 rv
= aEditorBase
->GetDocumentModified(&modified
);
250 NS_WARNING("EditorBase::GetDocumentModified() failed");
253 // XXX Nobody refers this result due to wrong type.
254 rv
= aParams
.SetBool(STATE_ATTRIBUTE
, modified
);
255 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
256 "nsCommandParams::SetBool(STATE_ATTRIBUTE) failed");
259 case Command::SetDocumentReadOnly
: {
260 // XXX Nobody refers this result due to wrong type.
261 rv
= aParams
.SetBool(STATE_ATTRIBUTE
, aEditorBase
->IsReadonly());
262 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
263 "nsCommandParams::SetBool(STATE_ATTRIBUTE) failed");
266 case Command::SetDocumentUseCSS
: {
267 HTMLEditor
* htmlEditor
= aEditorBase
->GetAsHTMLEditor();
268 if (NS_WARN_IF(!htmlEditor
)) {
269 return NS_ERROR_INVALID_ARG
;
271 rv
= aParams
.SetBool(STATE_ALL
, htmlEditor
->IsCSSEnabled());
272 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
273 "nsCommandParams::SetBool(STATE_ALL) failed");
276 case Command::SetDocumentInsertBROnEnterKeyPress
: {
277 HTMLEditor
* htmlEditor
= aEditorBase
->GetAsHTMLEditor();
278 if (NS_WARN_IF(!htmlEditor
)) {
279 return NS_ERROR_INVALID_ARG
;
281 bool createPOnReturn
;
282 DebugOnly
<nsresult
> rvIgnored
=
283 htmlEditor
->GetReturnInParagraphCreatesNewParagraph(&createPOnReturn
);
284 NS_WARNING_ASSERTION(
285 NS_SUCCEEDED(rvIgnored
),
286 "HTMLEditor::GetReturnInParagraphCreatesNewParagraph() failed");
287 // XXX Nobody refers this result due to wrong type.
288 rv
= aParams
.SetBool(STATE_ATTRIBUTE
, !createPOnReturn
);
289 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
290 "nsCommandParams::SetBool(STATE_ATTRIBUTE) failed");
293 case Command::SetDocumentDefaultParagraphSeparator
: {
294 HTMLEditor
* htmlEditor
= aEditorBase
->GetAsHTMLEditor();
295 if (NS_WARN_IF(!htmlEditor
)) {
296 return NS_ERROR_INVALID_ARG
;
299 switch (htmlEditor
->GetDefaultParagraphSeparator()) {
300 case ParagraphSeparator::div
: {
301 DebugOnly
<nsresult
> rv
=
302 aParams
.SetCString(STATE_ATTRIBUTE
, "div"_ns
);
303 NS_WARNING_ASSERTION(
305 "Failed to set command params to return \"div\"");
308 case ParagraphSeparator::p
: {
309 DebugOnly
<nsresult
> rv
= aParams
.SetCString(STATE_ATTRIBUTE
, "p"_ns
);
310 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
311 "Failed to set command params to return \"p\"");
314 case ParagraphSeparator::br
: {
315 DebugOnly
<nsresult
> rv
= aParams
.SetCString(STATE_ATTRIBUTE
, "br"_ns
);
316 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
317 "Failed to set command params to return \"br\"");
321 MOZ_ASSERT_UNREACHABLE("Invalid paragraph separator value");
322 return NS_ERROR_UNEXPECTED
;
325 case Command::ToggleObjectResizers
: {
326 HTMLEditor
* htmlEditor
= aEditorBase
->GetAsHTMLEditor();
327 if (NS_WARN_IF(!htmlEditor
)) {
328 return NS_ERROR_INVALID_ARG
;
330 // We returned the result as STATE_ATTRIBUTE with bool value 60 or
331 // earlier. So, the result was ignored by both
332 // nsHTMLDocument::QueryCommandValue() and
333 // nsHTMLDocument::QueryCommandState().
334 rv
= aParams
.SetBool(STATE_ALL
, htmlEditor
->IsObjectResizerEnabled());
335 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
336 "nsCommandParams::SetBool(STATE_ALL) failed");
339 case Command::ToggleInlineTableEditor
: {
340 HTMLEditor
* htmlEditor
= aEditorBase
->GetAsHTMLEditor();
341 if (NS_WARN_IF(!htmlEditor
)) {
342 return NS_ERROR_INVALID_ARG
;
344 // We returned the result as STATE_ATTRIBUTE with bool value 60 or
345 // earlier. So, the result was ignored by both
346 // nsHTMLDocument::QueryCommandValue() and
347 // nsHTMLDocument::QueryCommandState().
348 rv
= aParams
.SetBool(STATE_ALL
, htmlEditor
->IsInlineTableEditorEnabled());
349 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
350 "nsCommandParams::SetBool(STATE_ALL) failed");
353 case Command::ToggleAbsolutePositionEditor
: {
354 HTMLEditor
* htmlEditor
= aEditorBase
->GetAsHTMLEditor();
355 if (NS_WARN_IF(!htmlEditor
)) {
356 return NS_ERROR_INVALID_ARG
;
358 return aParams
.SetBool(STATE_ALL
,
359 htmlEditor
->IsAbsolutePositionEditorEnabled());
361 case Command::EnableCompatibleJoinSplitNodeDirection
: {
362 HTMLEditor
* htmlEditor
= aEditorBase
->GetAsHTMLEditor();
363 if (NS_WARN_IF(!htmlEditor
)) {
364 return NS_ERROR_INVALID_ARG
;
366 // Now we don't support the legacy join/split node direction anymore, but
367 // this result may be used for the feature detection whether Gecko
368 // supports the new direction mode. Therefore, we should return `true`
369 // even though executing the command does nothing.
370 return aParams
.SetBool(STATE_ALL
, true);
373 return NS_ERROR_NOT_IMPLEMENTED
;
377 /*****************************************************************************
378 * mozilla::DocumentStateCommand
380 * Commands just for state notification
381 * As of 11/21/02, possible commands are:
382 * "obs_documentCreated"
383 * "obs_documentWillBeDestroyed"
384 * "obs_documentLocationChanged"
385 * Note that you can use the same command class, DocumentStateCommand
386 * for these or future observer commands.
387 * We check the input command param for different behavior
390 * 1. Get the nsCommandManager for the current editor
391 * 2. Implement an nsIObserve object, e.g:
394 * in nsISupports aSubject, // The nsCommandManager calling this
396 * in string aTopic, // command name, e.g.:"obs_documentCreated"
397 * // or "obs_documentWillBeDestroyed"
398 in wstring aData ); // ignored (set to "command_status_changed")
400 * 3. Add the observer by:
401 * commandManager.addObserver(observeobject, obs_documentCreated);
402 * 4. In the appropriate location in editorSession, editor, or commands code,
403 * trigger the notification of this observer by something like:
405 * RefPtr<nsCommandManager> commandManager = mDocShell->GetCommandManager();
406 * commandManager->CommandStatusChanged(obs_documentCreated);
408 * 5. Use GetCommandStateParams() to obtain state information
409 * e.g., any creation state codes when creating an editor are
410 * supplied for "obs_documentCreated" command in the
411 * "state_data" param's value
412 *****************************************************************************/
414 StaticRefPtr
<DocumentStateCommand
> DocumentStateCommand::sInstance
;
416 bool DocumentStateCommand::IsCommandEnabled(Command aCommand
,
417 EditorBase
* aEditorBase
) const {
418 // Always return false to discourage callers from using DoCommand()
422 nsresult
DocumentStateCommand::DoCommand(Command aCommand
,
423 EditorBase
& aEditorBase
,
424 nsIPrincipal
* aPrincipal
) const {
425 return NS_ERROR_NOT_IMPLEMENTED
;
428 nsresult
DocumentStateCommand::GetCommandStateParams(
429 Command aCommand
, nsCommandParams
& aParams
, EditorBase
* aEditorBase
,
430 nsIEditingSession
* aEditingSession
) const {
432 case Command::EditorObserverDocumentCreated
: {
433 uint32_t editorStatus
= nsIEditingSession::eEditorErrorUnknown
;
434 if (aEditingSession
) {
435 // Current context is initially set to nsIEditingSession until editor is
436 // successfully created and source doc is loaded. Embedder gets error
437 // status if this fails. If called before startup is finished,
438 // status will be eEditorCreationInProgress.
439 nsresult rv
= aEditingSession
->GetEditorStatus(&editorStatus
);
441 NS_WARNING("nsIEditingSession::GetEditorStatus() failed");
444 } else if (aEditorBase
) {
445 // If current context is an editor, then everything started up OK!
446 editorStatus
= nsIEditingSession::eEditorOK
;
449 // Note that if refCon is not-null, but is neither
450 // an nsIEditingSession or nsIEditor, we return "eEditorErrorUnknown"
451 DebugOnly
<nsresult
> rvIgnored
= aParams
.SetInt(STATE_DATA
, editorStatus
);
452 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
453 "Failed to set editor status");
456 case Command::EditorObserverDocumentLocationChanged
: {
460 Document
* document
= aEditorBase
->GetDocument();
461 if (NS_WARN_IF(!document
)) {
462 return NS_ERROR_FAILURE
;
464 nsIURI
* uri
= document
->GetDocumentURI();
465 if (NS_WARN_IF(!uri
)) {
466 return NS_ERROR_FAILURE
;
468 nsresult rv
= aParams
.SetISupports(STATE_DATA
, uri
);
469 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
470 "nsCOmmandParms::SetISupports(STATE_DATA) failed");
474 return NS_ERROR_NOT_IMPLEMENTED
;
478 } // namespace mozilla