HTMLEditorDocumentCommands.cpp (18754B)
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 "EditorBase.h" // for EditorBase 9 #include "ErrorList.h" 10 #include "HTMLEditor.h" // for HTMLEditor 11 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 17 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 25 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" 31 32 namespace mozilla { 33 34 using namespace dom; 35 36 /***************************************************************************** 37 * mozilla::SetDocumentStateCommand 38 * 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 *****************************************************************************/ 45 46 StaticRefPtr<SetDocumentStateCommand> SetDocumentStateCommand::sInstance; 47 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(); 56 } 57 } 58 59 nsresult SetDocumentStateCommand::DoCommand(Command aCommand, 60 EditorBase& aEditorBase, 61 nsIPrincipal* aPrincipal) const { 62 return NS_ERROR_NOT_IMPLEMENTED; 63 } 64 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; 70 } 71 72 if (aCommand != Command::SetDocumentReadOnly && 73 NS_WARN_IF(!aEditorBase.IsHTMLEditor())) { 74 return NS_ERROR_FAILURE; 75 } 76 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; 84 } 85 nsresult rv = aEditorBase.ResetModificationCount(); 86 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 87 "EditorBase::ResetModificationCount() failed"); 88 return rv; 89 } 90 case Command::SetDocumentReadOnly: { 91 if (aEditorBase.IsTextEditor()) { 92 Element* inputOrTextArea = aEditorBase.GetExposedRoot(); 93 if (NS_WARN_IF(!inputOrTextArea)) { 94 return NS_ERROR_FAILURE; 95 } 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; 100 } 101 if (RefPtr<HTMLInputElement> inputElement = 102 HTMLInputElement::FromNode(inputOrTextArea)) { 103 if (inputElement->ReadOnly() == aBoolParam.value()) { 104 return NS_SUCCESS_DOM_NO_OPERATION; 105 } 106 ErrorResult error; 107 inputElement->SetReadOnly(aBoolParam.value(), error); 108 return error.StealNSResult(); 109 } 110 if (RefPtr<HTMLTextAreaElement> textAreaElement = 111 HTMLTextAreaElement::FromNode(inputOrTextArea)) { 112 if (textAreaElement->ReadOnly() == aBoolParam.value()) { 113 return NS_SUCCESS_DOM_NO_OPERATION; 114 } 115 ErrorResult error; 116 textAreaElement->SetReadOnly(aBoolParam.value(), error); 117 return error.StealNSResult(); 118 } 119 NS_ASSERTION( 120 false, 121 "Unexpected exposed root element, fallthrough to directly make the " 122 "editor readonly"); 123 } 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; 131 } 132 nsresult rv = aEditorBase.RemoveFlags(nsIEditor::eEditorReadonlyMask); 133 NS_WARNING_ASSERTION( 134 NS_SUCCEEDED(rv), 135 "EditorBase::RemoveFlags(nsIEditor::eEditorReadonlyMask) failed"); 136 return rv; 137 } 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; 144 } 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; 153 } 154 case Command::ToggleObjectResizers: { 155 MOZ_KnownLive(aEditorBase.AsHTMLEditor()) 156 ->EnableObjectResizer(aBoolParam.value()); 157 return NS_OK; 158 } 159 case Command::ToggleInlineTableEditor: { 160 MOZ_KnownLive(aEditorBase.AsHTMLEditor()) 161 ->EnableInlineTableEditor(aBoolParam.value()); 162 return NS_OK; 163 } 164 case Command::ToggleAbsolutePositionEditor: { 165 MOZ_KnownLive(aEditorBase.AsHTMLEditor()) 166 ->EnableAbsolutePositionEditor(aBoolParam.value()); 167 return NS_OK; 168 } 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; 178 } 179 } 180 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; 186 } 187 188 if (NS_WARN_IF(!aEditorBase.IsHTMLEditor())) { 189 return NS_ERROR_FAILURE; 190 } 191 192 switch (aCommand) { 193 case Command::SetDocumentDefaultParagraphSeparator: { 194 if (aCStringParam.LowerCaseEqualsLiteral("div")) { 195 aEditorBase.AsHTMLEditor()->SetDefaultParagraphSeparator( 196 ParagraphSeparator::div); 197 return NS_OK; 198 } 199 if (aCStringParam.LowerCaseEqualsLiteral("p")) { 200 aEditorBase.AsHTMLEditor()->SetDefaultParagraphSeparator( 201 ParagraphSeparator::p); 202 return NS_OK; 203 } 204 if (aCStringParam.LowerCaseEqualsLiteral("br")) { 205 // Mozilla extension for backwards compatibility 206 aEditorBase.AsHTMLEditor()->SetDefaultParagraphSeparator( 207 ParagraphSeparator::br); 208 return NS_OK; 209 } 210 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; 216 } 217 default: 218 return NS_ERROR_NOT_IMPLEMENTED; 219 } 220 } 221 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. 228 229 // The base editor owns most state info 230 if (NS_WARN_IF(!aEditorBase)) { 231 return NS_ERROR_INVALID_ARG; 232 } 233 234 if (NS_WARN_IF(!aEditorBase->IsHTMLEditor())) { 235 return NS_ERROR_FAILURE; 236 } 237 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; 243 } 244 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; 252 } 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; 258 } 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; 265 } 266 case Command::SetDocumentUseCSS: { 267 HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); 268 if (NS_WARN_IF(!htmlEditor)) { 269 return NS_ERROR_INVALID_ARG; 270 } 271 rv = aParams.SetBool(STATE_ALL, htmlEditor->IsCSSEnabled()); 272 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 273 "nsCommandParams::SetBool(STATE_ALL) failed"); 274 return rv; 275 } 276 case Command::SetDocumentInsertBROnEnterKeyPress: { 277 HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); 278 if (NS_WARN_IF(!htmlEditor)) { 279 return NS_ERROR_INVALID_ARG; 280 } 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; 292 } 293 case Command::SetDocumentDefaultParagraphSeparator: { 294 HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); 295 if (NS_WARN_IF(!htmlEditor)) { 296 return NS_ERROR_INVALID_ARG; 297 } 298 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; 307 } 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; 313 } 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; 319 } 320 default: 321 MOZ_ASSERT_UNREACHABLE("Invalid paragraph separator value"); 322 return NS_ERROR_UNEXPECTED; 323 } 324 } 325 case Command::ToggleObjectResizers: { 326 HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); 327 if (NS_WARN_IF(!htmlEditor)) { 328 return NS_ERROR_INVALID_ARG; 329 } 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; 338 } 339 case Command::ToggleInlineTableEditor: { 340 HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); 341 if (NS_WARN_IF(!htmlEditor)) { 342 return NS_ERROR_INVALID_ARG; 343 } 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; 352 } 353 case Command::ToggleAbsolutePositionEditor: { 354 HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); 355 if (NS_WARN_IF(!htmlEditor)) { 356 return NS_ERROR_INVALID_ARG; 357 } 358 return aParams.SetBool(STATE_ALL, 359 htmlEditor->IsAbsolutePositionEditorEnabled()); 360 } 361 case Command::EnableCompatibleJoinSplitNodeDirection: { 362 HTMLEditor* htmlEditor = aEditorBase->GetAsHTMLEditor(); 363 if (NS_WARN_IF(!htmlEditor)) { 364 return NS_ERROR_INVALID_ARG; 365 } 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); 371 } 372 default: 373 return NS_ERROR_NOT_IMPLEMENTED; 374 } 375 } 376 377 /***************************************************************************** 378 * mozilla::DocumentStateCommand 379 * 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 388 * 389 * How to use: 390 * 1. Get the nsCommandManager for the current editor 391 * 2. Implement an nsIObserve object, e.g: 392 * 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") 399 * 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: 404 * 405 * RefPtr<nsCommandManager> commandManager = mDocShell->GetCommandManager(); 406 * commandManager->CommandStatusChanged(obs_documentCreated); 407 * 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 *****************************************************************************/ 413 414 StaticRefPtr<DocumentStateCommand> DocumentStateCommand::sInstance; 415 416 bool DocumentStateCommand::IsCommandEnabled(Command aCommand, 417 EditorBase* aEditorBase) const { 418 // Always return false to discourage callers from using DoCommand() 419 return false; 420 } 421 422 nsresult DocumentStateCommand::DoCommand(Command aCommand, 423 EditorBase& aEditorBase, 424 nsIPrincipal* aPrincipal) const { 425 return NS_ERROR_NOT_IMPLEMENTED; 426 } 427 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; 443 } 444 } else if (aEditorBase) { 445 // If current context is an editor, then everything started up OK! 446 editorStatus = nsIEditingSession::eEditorOK; 447 } 448 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; 455 } 456 case Command::EditorObserverDocumentLocationChanged: { 457 if (!aEditorBase) { 458 return NS_OK; 459 } 460 Document* document = aEditorBase->GetDocument(); 461 if (NS_WARN_IF(!document)) { 462 return NS_ERROR_FAILURE; 463 } 464 nsIURI* uri = document->GetDocumentURI(); 465 if (NS_WARN_IF(!uri)) { 466 return NS_ERROR_FAILURE; 467 } 468 nsresult rv = aParams.SetISupports(STATE_DATA, uri); 469 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 470 "nsCOmmandParms::SetISupports(STATE_DATA) failed"); 471 return rv; 472 } 473 default: 474 return NS_ERROR_NOT_IMPLEMENTED; 475 } 476 } 477 478 } // namespace mozilla