Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / editor / libeditor / HTMLEditorDocumentCommands.cpp
blobae1af5814b45839fa72d12252fed961109a71368
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
9 #include "ErrorList.h"
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
26 // defines
27 #define STATE_ENABLED "state_enabled"
28 #define STATE_ALL "state_all"
29 #define STATE_ATTRIBUTE "state_attribute"
30 #define STATE_DATA "state_data"
32 namespace mozilla {
34 using namespace dom;
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 {
50 switch (aCommand) {
51 case Command::SetDocumentReadOnly:
52 return !!aEditorBase;
53 default:
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;
77 switch (aCommand) {
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");
83 return rv;
85 nsresult rv = aEditorBase.ResetModificationCount();
86 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
87 "EditorBase::ResetModificationCount() failed");
88 return rv;
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;
106 ErrorResult error;
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;
115 ErrorResult error;
116 textAreaElement->SetReadOnly(aBoolParam.value(), error);
117 return error.StealNSResult();
119 NS_ASSERTION(
120 false,
121 "Unexpected exposed root element, fallthrough to directly make the "
122 "editor readonly");
124 ErrorResult error;
125 if (aBoolParam.value()) {
126 nsresult rv = aEditorBase.AddFlags(nsIEditor::eEditorReadonlyMask);
127 NS_WARNING_ASSERTION(
128 NS_SUCCEEDED(rv),
129 "EditorBase::AddFlags(nsIEditor::eEditorReadonlyMask) failed");
130 return rv;
132 nsresult rv = aEditorBase.RemoveFlags(nsIEditor::eEditorReadonlyMask);
133 NS_WARNING_ASSERTION(
134 NS_SUCCEEDED(rv),
135 "EditorBase::RemoveFlags(nsIEditor::eEditorReadonlyMask) failed");
136 return rv;
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");
143 return rv;
145 case Command::SetDocumentInsertBROnEnterKeyPress: {
146 nsresult rv =
147 aEditorBase.AsHTMLEditor()->SetReturnInParagraphCreatesNewParagraph(
148 !aBoolParam.value());
149 NS_WARNING_ASSERTION(
150 NS_SUCCEEDED(rv),
151 "HTMLEditor::SetReturnInParagraphCreatesNewParagraph() failed");
152 return rv;
154 case Command::ToggleObjectResizers: {
155 MOZ_KnownLive(aEditorBase.AsHTMLEditor())
156 ->EnableObjectResizer(aBoolParam.value());
157 return NS_OK;
159 case Command::ToggleInlineTableEditor: {
160 MOZ_KnownLive(aEditorBase.AsHTMLEditor())
161 ->EnableInlineTableEditor(aBoolParam.value());
162 return NS_OK;
164 case Command::ToggleAbsolutePositionEditor: {
165 MOZ_KnownLive(aEditorBase.AsHTMLEditor())
166 ->EnableAbsolutePositionEditor(aBoolParam.value());
167 return NS_OK;
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()`.
175 return NS_OK;
176 default:
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;
192 switch (aCommand) {
193 case Command::SetDocumentDefaultParagraphSeparator: {
194 if (aCStringParam.LowerCaseEqualsLiteral("div")) {
195 aEditorBase.AsHTMLEditor()->SetDefaultParagraphSeparator(
196 ParagraphSeparator::div);
197 return NS_OK;
199 if (aCStringParam.LowerCaseEqualsLiteral("p")) {
200 aEditorBase.AsHTMLEditor()->SetDefaultParagraphSeparator(
201 ParagraphSeparator::p);
202 return NS_OK;
204 if (aCStringParam.LowerCaseEqualsLiteral("br")) {
205 // Mozilla extension for backwards compatibility
206 aEditorBase.AsHTMLEditor()->SetDefaultParagraphSeparator(
207 ParagraphSeparator::br);
208 return NS_OK;
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;
217 default:
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
239 nsresult rv =
240 aParams.SetBool(STATE_ENABLED, IsCommandEnabled(aCommand, aEditorBase));
241 if (NS_WARN_IF(NS_FAILED(rv))) {
242 return rv;
245 switch (aCommand) {
246 case Command::SetDocumentModified: {
247 bool modified;
248 rv = aEditorBase->GetDocumentModified(&modified);
249 if (NS_FAILED(rv)) {
250 NS_WARNING("EditorBase::GetDocumentModified() failed");
251 return rv;
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");
257 return rv;
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");
264 return rv;
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");
274 return rv;
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");
291 return rv;
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(
304 NS_SUCCEEDED(rv),
305 "Failed to set command params to return \"div\"");
306 return NS_OK;
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\"");
312 return NS_OK;
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\"");
318 return NS_OK;
320 default:
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");
337 return rv;
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");
351 return rv;
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);
372 default:
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
389 * How to use:
390 * 1. Get the nsCommandManager for the current editor
391 * 2. Implement an nsIObserve object, e.g:
393 * void Observe(
394 * in nsISupports aSubject, // The nsCommandManager calling this
395 * // Observer
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()
419 return false;
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 {
431 switch (aCommand) {
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);
440 if (NS_FAILED(rv)) {
441 NS_WARNING("nsIEditingSession::GetEditorStatus() failed");
442 return rv;
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");
454 return NS_OK;
456 case Command::EditorObserverDocumentLocationChanged: {
457 if (!aEditorBase) {
458 return NS_OK;
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");
471 return rv;
473 default:
474 return NS_ERROR_NOT_IMPLEMENTED;
478 } // namespace mozilla