HTMLInlineTableEditor.cpp (19036B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "mozilla/HTMLEditor.h" 6 7 #include "EditorEventListener.h" 8 #include "HTMLEditUtils.h" 9 #include "mozilla/PresShell.h" 10 #include "mozilla/dom/Element.h" 11 #include "nsAString.h" 12 #include "nsCOMPtr.h" 13 #include "nsDebug.h" 14 #include "nsError.h" 15 #include "nsGenericHTMLElement.h" 16 #include "nsIContent.h" 17 #include "nsLiteralString.h" 18 #include "nsReadableUtils.h" 19 #include "nsString.h" 20 #include "nscore.h" 21 22 namespace mozilla { 23 24 NS_IMETHODIMP HTMLEditor::SetInlineTableEditingEnabled(bool aIsEnabled) { 25 EnableInlineTableEditor(aIsEnabled); 26 return NS_OK; 27 } 28 29 NS_IMETHODIMP HTMLEditor::GetInlineTableEditingEnabled(bool* aIsEnabled) { 30 *aIsEnabled = IsInlineTableEditorEnabled(); 31 return NS_OK; 32 } 33 34 NS_IMETHODIMP HTMLEditor::GetIsInlineTableEditingActive(bool* aIsActive) { 35 MOZ_ASSERT(aIsActive); 36 *aIsActive = !!mInlineEditedCell; 37 return NS_OK; 38 } 39 40 nsresult HTMLEditor::ShowInlineTableEditingUIInternal(Element& aCellElement) { 41 if (NS_WARN_IF(!HTMLEditUtils::IsTableCellElement(aCellElement))) { 42 return NS_OK; 43 } 44 45 const RefPtr<Element> editingHost = ComputeEditingHost(); 46 if (NS_WARN_IF(!editingHost) || 47 NS_WARN_IF(!aCellElement.IsInclusiveDescendantOf(editingHost))) { 48 return NS_ERROR_FAILURE; 49 } 50 51 if (NS_WARN_IF(mInlineEditedCell)) { 52 return NS_ERROR_FAILURE; 53 } 54 55 mInlineEditedCell = &aCellElement; 56 57 // the resizers and the shadow will be anonymous children of the body 58 RefPtr<Element> rootElement = GetRoot(); 59 if (NS_WARN_IF(!rootElement)) { 60 return NS_ERROR_FAILURE; 61 } 62 63 do { 64 // The buttons of inline table editor will be children of the <body> 65 // element. Creating the anonymous elements may cause calling 66 // HideInlineTableEditingUIInternal() via a mutation event listener. 67 // So, we should store new button to a local variable, then, check: 68 // - whether creating a button is already set to the member or not 69 // - whether already created buttons are changed to another set 70 // If creating the buttons are canceled, we hit the latter check. 71 // If buttons for another table are created during this, we hit the latter 72 // check too. 73 // If buttons are just created again for same element, we hit the former 74 // check. 75 ManualNACPtr addColumnBeforeButton = CreateAnonymousElement( 76 nsGkAtoms::a, *rootElement, u"mozTableAddColumnBefore"_ns, false); 77 if (NS_WARN_IF(!addColumnBeforeButton)) { 78 NS_WARNING( 79 "HTMLEditor::CreateAnonymousElement(nsGkAtoms::a, " 80 "mozTableAddColumnBefore) failed"); 81 break; // Hide unnecessary buttons created above. 82 } 83 if (NS_WARN_IF(mAddColumnBeforeButton) || 84 NS_WARN_IF(mInlineEditedCell != &aCellElement)) { 85 return NS_ERROR_FAILURE; // Don't hide another set of buttons. 86 } 87 mAddColumnBeforeButton = std::move(addColumnBeforeButton); 88 89 ManualNACPtr removeColumnButton = CreateAnonymousElement( 90 nsGkAtoms::a, *rootElement, u"mozTableRemoveColumn"_ns, false); 91 if (!removeColumnButton) { 92 NS_WARNING( 93 "HTMLEditor::CreateAnonymousElement(nsGkAtoms::a, " 94 "mozTableRemoveColumn) failed"); 95 break; 96 } 97 if (NS_WARN_IF(mRemoveColumnButton) || 98 NS_WARN_IF(mInlineEditedCell != &aCellElement)) { 99 return NS_ERROR_FAILURE; 100 } 101 mRemoveColumnButton = std::move(removeColumnButton); 102 103 ManualNACPtr addColumnAfterButton = CreateAnonymousElement( 104 nsGkAtoms::a, *rootElement, u"mozTableAddColumnAfter"_ns, false); 105 if (!addColumnAfterButton) { 106 NS_WARNING( 107 "HTMLEditor::CreateAnonymousElement(nsGkAtoms::a, " 108 "mozTableAddColumnAfter) failed"); 109 break; 110 } 111 if (NS_WARN_IF(mAddColumnAfterButton) || 112 NS_WARN_IF(mInlineEditedCell != &aCellElement)) { 113 return NS_ERROR_FAILURE; 114 } 115 mAddColumnAfterButton = std::move(addColumnAfterButton); 116 117 ManualNACPtr addRowBeforeButton = CreateAnonymousElement( 118 nsGkAtoms::a, *rootElement, u"mozTableAddRowBefore"_ns, false); 119 if (!addRowBeforeButton) { 120 NS_WARNING( 121 "HTMLEditor::CreateAnonymousElement(nsGkAtoms::a, " 122 "mozTableAddRowBefore) failed"); 123 break; 124 } 125 if (NS_WARN_IF(mAddRowBeforeButton) || 126 NS_WARN_IF(mInlineEditedCell != &aCellElement)) { 127 return NS_ERROR_FAILURE; 128 } 129 mAddRowBeforeButton = std::move(addRowBeforeButton); 130 131 ManualNACPtr removeRowButton = CreateAnonymousElement( 132 nsGkAtoms::a, *rootElement, u"mozTableRemoveRow"_ns, false); 133 if (!removeRowButton) { 134 NS_WARNING( 135 "HTMLEditor::CreateAnonymousElement(nsGkAtoms::a, " 136 "mozTableRemoveRow) failed"); 137 break; 138 } 139 if (NS_WARN_IF(mRemoveRowButton) || 140 NS_WARN_IF(mInlineEditedCell != &aCellElement)) { 141 return NS_ERROR_FAILURE; 142 } 143 mRemoveRowButton = std::move(removeRowButton); 144 145 ManualNACPtr addRowAfterButton = CreateAnonymousElement( 146 nsGkAtoms::a, *rootElement, u"mozTableAddRowAfter"_ns, false); 147 if (!addRowAfterButton) { 148 NS_WARNING( 149 "HTMLEditor::CreateAnonymousElement(nsGkAtoms::a, " 150 "mozTableAddRowAfter) failed"); 151 break; 152 } 153 if (NS_WARN_IF(mAddRowAfterButton) || 154 NS_WARN_IF(mInlineEditedCell != &aCellElement)) { 155 return NS_ERROR_FAILURE; 156 } 157 mAddRowAfterButton = std::move(addRowAfterButton); 158 159 AddPointerClickListener(mAddColumnBeforeButton); 160 AddPointerClickListener(mRemoveColumnButton); 161 AddPointerClickListener(mAddColumnAfterButton); 162 AddPointerClickListener(mAddRowBeforeButton); 163 AddPointerClickListener(mRemoveRowButton); 164 AddPointerClickListener(mAddRowAfterButton); 165 166 nsresult rv = RefreshInlineTableEditingUIInternal(); 167 NS_WARNING_ASSERTION( 168 NS_SUCCEEDED(rv), 169 "HTMLEditor::RefreshInlineTableEditingUIInternal() failed"); 170 return rv; 171 } while (true); 172 173 HideInlineTableEditingUIInternal(); 174 return NS_ERROR_FAILURE; 175 } 176 177 void HTMLEditor::HideInlineTableEditingUIInternal() { 178 mInlineEditedCell = nullptr; 179 180 RemovePointerClickListener(mAddColumnBeforeButton); 181 RemovePointerClickListener(mRemoveColumnButton); 182 RemovePointerClickListener(mAddColumnAfterButton); 183 RemovePointerClickListener(mAddRowBeforeButton); 184 RemovePointerClickListener(mRemoveRowButton); 185 RemovePointerClickListener(mAddRowAfterButton); 186 187 // get the presshell's document observer interface. 188 RefPtr<PresShell> presShell = GetPresShell(); 189 // We allow the pres shell to be null; when it is, we presume there 190 // are no document observers to notify, but we still want to 191 // UnbindFromTree. 192 193 // Calling DeleteRefToAnonymousNode() may cause showing the UI again. 194 // Therefore, we should forget all anonymous contents first. 195 // Otherwise, we could leak the old content because of overwritten by 196 // ShowInlineTableEditingUIInternal(). 197 ManualNACPtr addColumnBeforeButton(std::move(mAddColumnBeforeButton)); 198 ManualNACPtr removeColumnButton(std::move(mRemoveColumnButton)); 199 ManualNACPtr addColumnAfterButton(std::move(mAddColumnAfterButton)); 200 ManualNACPtr addRowBeforeButton(std::move(mAddRowBeforeButton)); 201 ManualNACPtr removeRowButton(std::move(mRemoveRowButton)); 202 ManualNACPtr addRowAfterButton(std::move(mAddRowAfterButton)); 203 204 DeleteRefToAnonymousNode(std::move(addColumnBeforeButton), presShell); 205 DeleteRefToAnonymousNode(std::move(removeColumnButton), presShell); 206 DeleteRefToAnonymousNode(std::move(addColumnAfterButton), presShell); 207 DeleteRefToAnonymousNode(std::move(addRowBeforeButton), presShell); 208 DeleteRefToAnonymousNode(std::move(removeRowButton), presShell); 209 DeleteRefToAnonymousNode(std::move(addRowAfterButton), presShell); 210 } 211 212 nsresult HTMLEditor::DoInlineTableEditingAction(const Element& aElement) { 213 nsAutoString classList; 214 aElement.GetAttr(nsGkAtoms::_class, classList); 215 216 if (!StringBeginsWith(classList, u"mozTable"_ns)) { 217 return NS_OK; 218 } 219 220 if (NS_WARN_IF(!mInlineEditedCell) || 221 NS_WARN_IF(!mInlineEditedCell->IsInComposedDoc()) || 222 NS_WARN_IF(!mInlineEditedCell->GetParent()) || 223 NS_WARN_IF( 224 !HTMLEditUtils::IsTableRowElement(*mInlineEditedCell->GetParent()))) { 225 return NS_ERROR_NOT_AVAILABLE; 226 } 227 228 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 229 if (NS_WARN_IF(!editActionData.CanHandle())) { 230 return NS_ERROR_NOT_INITIALIZED; 231 } 232 233 RefPtr<Element> tableElement = 234 HTMLEditUtils::GetClosestAncestorTableElement(*mInlineEditedCell); 235 if (!tableElement) { 236 NS_WARNING("HTMLEditor::GetClosestAncestorTableElement() returned nullptr"); 237 return NS_ERROR_FAILURE; 238 } 239 int32_t rowCount, colCount; 240 nsresult rv = GetTableSize(tableElement, &rowCount, &colCount); 241 if (NS_FAILED(rv)) { 242 NS_WARNING("HTMLEditor::GetTableSize() failed"); 243 return EditorBase::ToGenericNSResult(rv); 244 } 245 246 bool hideUI = false; 247 bool hideResizersWithInlineTableUI = (mResizedObject == tableElement); 248 249 if (classList.EqualsLiteral("mozTableAddColumnBefore")) { 250 AutoEditActionDataSetter editActionData(*this, 251 EditAction::eInsertTableColumn); 252 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 253 if (NS_FAILED(rv)) { 254 NS_WARNING_ASSERTION( 255 rv == NS_ERROR_EDITOR_ACTION_CANCELED, 256 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 257 return EditorBase::ToGenericNSResult(rv); 258 } 259 260 DebugOnly<nsresult> rvIgnored = 261 InsertTableColumnsWithTransaction(EditorDOMPoint(mInlineEditedCell), 1); 262 NS_WARNING_ASSERTION( 263 NS_SUCCEEDED(rvIgnored), 264 "HTMLEditor::InsertTableColumnsWithTransaction(" 265 "EditorDOMPoint(mInlineEditedCell), 1) failed, but ignored"); 266 } else if (classList.EqualsLiteral("mozTableAddColumnAfter")) { 267 AutoEditActionDataSetter editActionData(*this, 268 EditAction::eInsertTableColumn); 269 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 270 if (NS_FAILED(rv)) { 271 NS_WARNING_ASSERTION( 272 rv == NS_ERROR_EDITOR_ACTION_CANCELED, 273 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 274 return EditorBase::ToGenericNSResult(rv); 275 } 276 Element* nextCellElement = nullptr; 277 for (nsIContent* maybeNextCellElement = mInlineEditedCell->GetNextSibling(); 278 maybeNextCellElement; 279 maybeNextCellElement = maybeNextCellElement->GetNextSibling()) { 280 if (HTMLEditUtils::IsTableCellElement(*maybeNextCellElement)) { 281 nextCellElement = maybeNextCellElement->AsElement(); 282 break; 283 } 284 } 285 DebugOnly<nsresult> rvIgnored = InsertTableColumnsWithTransaction( 286 nextCellElement 287 ? EditorDOMPoint(nextCellElement) 288 : EditorDOMPoint::AtEndOf(*mInlineEditedCell->GetParentElement()), 289 1); 290 NS_WARNING_ASSERTION( 291 NS_SUCCEEDED(rvIgnored), 292 "HTMLEditor::InsertTableColumnsWithTransaction(" 293 "EditorDOMPoint(nextCellElement), 1) failed, but ignored"); 294 } else if (classList.EqualsLiteral("mozTableAddRowBefore")) { 295 AutoEditActionDataSetter editActionData(*this, 296 EditAction::eInsertTableRowElement); 297 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 298 if (NS_FAILED(rv)) { 299 NS_WARNING_ASSERTION( 300 rv == NS_ERROR_EDITOR_ACTION_CANCELED, 301 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 302 return EditorBase::ToGenericNSResult(rv); 303 } 304 OwningNonNull<Element> targetCellElement(*mInlineEditedCell); 305 DebugOnly<nsresult> rvIgnored = InsertTableRowsWithTransaction( 306 targetCellElement, 1, InsertPosition::eBeforeSelectedCell); 307 NS_WARNING_ASSERTION( 308 NS_SUCCEEDED(rvIgnored), 309 "HTMLEditor::InsertTableRowsWithTransaction(targetCellElement, 1, " 310 "InsertPosition::eBeforeSelectedCell) failed, but ignored"); 311 } else if (classList.EqualsLiteral("mozTableAddRowAfter")) { 312 AutoEditActionDataSetter editActionData(*this, 313 EditAction::eInsertTableRowElement); 314 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 315 if (NS_FAILED(rv)) { 316 NS_WARNING_ASSERTION( 317 rv == NS_ERROR_EDITOR_ACTION_CANCELED, 318 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 319 return EditorBase::ToGenericNSResult(rv); 320 } 321 OwningNonNull<Element> targetCellElement(*mInlineEditedCell); 322 DebugOnly<nsresult> rvIgnored = InsertTableRowsWithTransaction( 323 targetCellElement, 1, InsertPosition::eAfterSelectedCell); 324 NS_WARNING_ASSERTION( 325 NS_SUCCEEDED(rvIgnored), 326 "HTMLEditor::InsertTableRowsWithTransaction(targetCellElement, 1, " 327 "InsertPosition::eAfterSelectedCell) failed, but ignored"); 328 } else if (classList.EqualsLiteral("mozTableRemoveColumn")) { 329 AutoEditActionDataSetter editActionData(*this, 330 EditAction::eRemoveTableColumn); 331 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 332 if (NS_FAILED(rv)) { 333 NS_WARNING_ASSERTION( 334 rv == NS_ERROR_EDITOR_ACTION_CANCELED, 335 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 336 return EditorBase::ToGenericNSResult(rv); 337 } 338 DebugOnly<nsresult> rvIgnored = 339 DeleteSelectedTableColumnsWithTransaction(1); 340 NS_WARNING_ASSERTION( 341 NS_SUCCEEDED(rvIgnored), 342 "HTMLEditor::DeleteSelectedTableColumnsWithTransaction(1) failed, but " 343 "ignored"); 344 hideUI = (colCount == 1); 345 } else if (classList.EqualsLiteral("mozTableRemoveRow")) { 346 AutoEditActionDataSetter editActionData(*this, 347 EditAction::eRemoveTableRowElement); 348 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 349 if (NS_FAILED(rv)) { 350 NS_WARNING_ASSERTION( 351 rv == NS_ERROR_EDITOR_ACTION_CANCELED, 352 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 353 return EditorBase::ToGenericNSResult(rv); 354 } 355 DebugOnly<nsresult> rvIgnored = DeleteSelectedTableRowsWithTransaction(1); 356 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 357 "HTMLEditor::DeleteSelectedTableRowsWithTransaction(1)" 358 " failed, but ignored"); 359 hideUI = (rowCount == 1); 360 } else { 361 return NS_OK; 362 } 363 364 if (NS_WARN_IF(Destroyed())) { 365 return NS_OK; 366 } 367 368 if (hideUI) { 369 HideInlineTableEditingUIInternal(); 370 if (hideResizersWithInlineTableUI) { 371 DebugOnly<nsresult> rvIgnored = HideResizersInternal(); 372 NS_WARNING_ASSERTION( 373 NS_SUCCEEDED(rvIgnored), 374 "HTMLEditor::HideResizersInternal() failed, but ignored"); 375 } 376 } 377 378 return NS_OK; 379 } 380 381 void HTMLEditor::AddPointerClickListener(Element* aElement) { 382 if (NS_WARN_IF(!aElement)) { 383 return; 384 } 385 DebugOnly<nsresult> rvIgnored = 386 aElement->AddEventListener(u"click"_ns, mEventListener, true); 387 NS_WARNING_ASSERTION( 388 NS_SUCCEEDED(rvIgnored), 389 "EventTarget::AddEventListener(click) failed, but ignored"); 390 } 391 392 void HTMLEditor::RemovePointerClickListener(Element* aElement) { 393 if (NS_WARN_IF(!aElement)) { 394 return; 395 } 396 aElement->RemoveEventListener(u"click"_ns, mEventListener, true); 397 } 398 399 nsresult HTMLEditor::RefreshInlineTableEditingUIInternal() { 400 MOZ_ASSERT(IsEditActionDataAvailable()); 401 402 if (!mInlineEditedCell) { 403 return NS_OK; 404 } 405 406 RefPtr<nsGenericHTMLElement> inlineEditingCellElement = 407 nsGenericHTMLElement::FromNode(mInlineEditedCell); 408 if (NS_WARN_IF(!inlineEditingCellElement)) { 409 return NS_ERROR_FAILURE; 410 } 411 412 int32_t cellX = 0, cellY = 0; 413 DebugOnly<nsresult> rvIgnored = 414 GetElementOrigin(*mInlineEditedCell, cellX, cellY); 415 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 416 "HTMLEditor::GetElementOrigin() failed, but ignored"); 417 418 int32_t cellWidth = inlineEditingCellElement->OffsetWidth(); 419 int32_t cellHeight = inlineEditingCellElement->OffsetHeight(); 420 421 int32_t centerOfCellX = cellX + cellWidth / 2; 422 int32_t centerOfCellY = cellY + cellHeight / 2; 423 424 RefPtr<Element> tableElement = 425 HTMLEditUtils::GetClosestAncestorTableElement(*mInlineEditedCell); 426 if (!tableElement) { 427 NS_WARNING("HTMLEditor::GetClosestAncestorTableElement() returned nullptr"); 428 return NS_ERROR_FAILURE; 429 } 430 int32_t rowCount = 0, colCount = 0; 431 nsresult rv = GetTableSize(tableElement, &rowCount, &colCount); 432 if (NS_FAILED(rv)) { 433 NS_WARNING("HTMLEditor::GetTableSize() failed"); 434 return rv; 435 } 436 437 auto setInlineTableEditButtonPosition = 438 [this](ManualNACPtr& aButtonElement, int32_t aNewX, int32_t aNewY) 439 MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> nsresult { 440 RefPtr<nsStyledElement> buttonStyledElement = 441 nsStyledElement::FromNodeOrNull(aButtonElement.get()); 442 if (!buttonStyledElement) { 443 return NS_OK; 444 } 445 nsresult rv = SetAnonymousElementPositionWithoutTransaction( 446 *buttonStyledElement, aNewX, aNewY); 447 if (NS_FAILED(rv)) { 448 NS_WARNING( 449 "HTMLEditor::SetAnonymousElementPositionWithoutTransaction() " 450 "failed"); 451 return rv; 452 } 453 return NS_WARN_IF(buttonStyledElement != aButtonElement.get()) 454 ? NS_ERROR_FAILURE 455 : NS_OK; 456 }; 457 458 // clang-format off 459 rv = setInlineTableEditButtonPosition(mAddColumnBeforeButton, 460 centerOfCellX - 10, cellY - 7); 461 if (NS_FAILED(rv)) { 462 NS_WARNING("Failed to move button for add-column-before"); 463 return rv; 464 } 465 rv = setInlineTableEditButtonPosition(mRemoveColumnButton, 466 centerOfCellX - 4, cellY - 7); 467 if (NS_FAILED(rv)) { 468 NS_WARNING("Failed to move button for remove-column"); 469 return rv; 470 } 471 rv = setInlineTableEditButtonPosition(mAddColumnAfterButton, 472 centerOfCellX + 6, cellY - 7); 473 if (NS_FAILED(rv)) { 474 NS_WARNING("Failed to move button for add-column-after"); 475 return rv; 476 } 477 rv = setInlineTableEditButtonPosition(mAddRowBeforeButton, 478 cellX - 7, centerOfCellY - 10); 479 if (NS_FAILED(rv)) { 480 NS_WARNING("Failed to move button for add-row-before"); 481 return rv; 482 } 483 rv = setInlineTableEditButtonPosition(mRemoveRowButton, 484 cellX - 7, centerOfCellY - 4); 485 if (NS_FAILED(rv)) { 486 NS_WARNING("Failed to move button for remove-row"); 487 return rv; 488 } 489 rv = setInlineTableEditButtonPosition(mAddRowAfterButton, 490 cellX - 7, centerOfCellY + 6); 491 if (NS_FAILED(rv)) { 492 NS_WARNING("Failed to move button for add-row-after"); 493 return rv; 494 } 495 // clang-format on 496 497 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK; 498 } 499 500 } // namespace mozilla