зеркало из https://github.com/mozilla/gecko-dev.git
397 строки
14 KiB
C++
397 строки
14 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "mozilla/HTMLEditor.h"
|
|
|
|
#include "HTMLEditUtils.h"
|
|
#include "mozilla/dom/Element.h"
|
|
#include "nsAString.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsDebug.h"
|
|
#include "nsError.h"
|
|
#include "nsGenericHTMLElement.h"
|
|
#include "nsIContent.h"
|
|
#include "nsIHTMLObjectResizer.h"
|
|
#include "nsIPresShell.h"
|
|
#include "nsLiteralString.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsString.h"
|
|
#include "nscore.h"
|
|
|
|
namespace mozilla {
|
|
|
|
NS_IMETHODIMP
|
|
HTMLEditor::SetInlineTableEditingEnabled(bool aIsEnabled) {
|
|
EnableInlineTableEditor(aIsEnabled);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HTMLEditor::GetInlineTableEditingEnabled(bool* aIsEnabled) {
|
|
*aIsEnabled = IsInlineTableEditorEnabled();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult HTMLEditor::ShowInlineTableEditingUIInternal(Element& aCellElement) {
|
|
if (NS_WARN_IF(!HTMLEditUtils::IsTableCell(&aCellElement))) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (NS_WARN_IF(!IsDescendantOfEditorRoot(&aCellElement))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (NS_WARN_IF(mInlineEditedCell)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mInlineEditedCell = &aCellElement;
|
|
|
|
// the resizers and the shadow will be anonymous children of the body
|
|
RefPtr<Element> bodyElement = GetRoot();
|
|
if (NS_WARN_IF(!bodyElement)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
do {
|
|
// The buttons of inline table editor will be children of the <body>
|
|
// element. Creating the anonymous elements may cause calling
|
|
// HideInlineTableEditingUIInternal() via a mutation event listener.
|
|
// So, we should store new button to a local variable, then, check:
|
|
// - whether creating a button is already set to the member or not
|
|
// - whether already created buttons are changed to another set
|
|
// If creating the buttons are canceled, we hit the latter check.
|
|
// If buttons for another table are created during this, we hit the latter
|
|
// check too.
|
|
// If buttons are just created again for same element, we hit the former
|
|
// check.
|
|
ManualNACPtr addColumnBeforeButton = CreateAnonymousElement(
|
|
nsGkAtoms::a, *bodyElement,
|
|
NS_LITERAL_STRING("mozTableAddColumnBefore"), false);
|
|
if (NS_WARN_IF(!addColumnBeforeButton)) {
|
|
break; // Hide unnecessary buttons created above.
|
|
}
|
|
if (NS_WARN_IF(mAddColumnBeforeButton) ||
|
|
NS_WARN_IF(mInlineEditedCell != &aCellElement)) {
|
|
return NS_ERROR_FAILURE; // Don't hide another set of buttons.
|
|
}
|
|
mAddColumnBeforeButton = std::move(addColumnBeforeButton);
|
|
|
|
ManualNACPtr removeColumnButton = CreateAnonymousElement(
|
|
nsGkAtoms::a, *bodyElement, NS_LITERAL_STRING("mozTableRemoveColumn"),
|
|
false);
|
|
if (NS_WARN_IF(!removeColumnButton)) {
|
|
break;
|
|
}
|
|
if (NS_WARN_IF(mRemoveColumnButton) ||
|
|
NS_WARN_IF(mInlineEditedCell != &aCellElement)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
mRemoveColumnButton = std::move(removeColumnButton);
|
|
|
|
ManualNACPtr addColumnAfterButton = CreateAnonymousElement(
|
|
nsGkAtoms::a, *bodyElement, NS_LITERAL_STRING("mozTableAddColumnAfter"),
|
|
false);
|
|
if (NS_WARN_IF(!addColumnAfterButton)) {
|
|
break;
|
|
}
|
|
if (NS_WARN_IF(mAddColumnAfterButton) ||
|
|
NS_WARN_IF(mInlineEditedCell != &aCellElement)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
mAddColumnAfterButton = std::move(addColumnAfterButton);
|
|
|
|
ManualNACPtr addRowBeforeButton = CreateAnonymousElement(
|
|
nsGkAtoms::a, *bodyElement, NS_LITERAL_STRING("mozTableAddRowBefore"),
|
|
false);
|
|
if (NS_WARN_IF(!addRowBeforeButton)) {
|
|
break;
|
|
}
|
|
if (NS_WARN_IF(mAddRowBeforeButton) ||
|
|
NS_WARN_IF(mInlineEditedCell != &aCellElement)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
mAddRowBeforeButton = std::move(addRowBeforeButton);
|
|
|
|
ManualNACPtr removeRowButton =
|
|
CreateAnonymousElement(nsGkAtoms::a, *bodyElement,
|
|
NS_LITERAL_STRING("mozTableRemoveRow"), false);
|
|
if (NS_WARN_IF(!removeRowButton)) {
|
|
break;
|
|
}
|
|
if (NS_WARN_IF(mRemoveRowButton) ||
|
|
NS_WARN_IF(mInlineEditedCell != &aCellElement)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
mRemoveRowButton = std::move(removeRowButton);
|
|
|
|
ManualNACPtr addRowAfterButton =
|
|
CreateAnonymousElement(nsGkAtoms::a, *bodyElement,
|
|
NS_LITERAL_STRING("mozTableAddRowAfter"), false);
|
|
if (NS_WARN_IF(!addRowAfterButton)) {
|
|
break;
|
|
}
|
|
if (NS_WARN_IF(mAddRowAfterButton) ||
|
|
NS_WARN_IF(mInlineEditedCell != &aCellElement)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
mAddRowAfterButton = std::move(addRowAfterButton);
|
|
|
|
AddMouseClickListener(mAddColumnBeforeButton);
|
|
AddMouseClickListener(mRemoveColumnButton);
|
|
AddMouseClickListener(mAddColumnAfterButton);
|
|
AddMouseClickListener(mAddRowBeforeButton);
|
|
AddMouseClickListener(mRemoveRowButton);
|
|
AddMouseClickListener(mAddRowAfterButton);
|
|
|
|
mHasShownInlineTableEditor = true;
|
|
|
|
nsresult rv = RefreshInlineTableEditingUIInternal();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
return NS_OK;
|
|
} while (true);
|
|
|
|
HideInlineTableEditingUIInternal();
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
void HTMLEditor::HideInlineTableEditingUIInternal() {
|
|
mInlineEditedCell = nullptr;
|
|
|
|
RemoveMouseClickListener(mAddColumnBeforeButton);
|
|
RemoveMouseClickListener(mRemoveColumnButton);
|
|
RemoveMouseClickListener(mAddColumnAfterButton);
|
|
RemoveMouseClickListener(mAddRowBeforeButton);
|
|
RemoveMouseClickListener(mRemoveRowButton);
|
|
RemoveMouseClickListener(mAddRowAfterButton);
|
|
|
|
// get the presshell's document observer interface.
|
|
nsCOMPtr<nsIPresShell> ps = GetPresShell();
|
|
// We allow the pres shell to be null; when it is, we presume there
|
|
// are no document observers to notify, but we still want to
|
|
// UnbindFromTree.
|
|
|
|
// Calling DeleteRefToAnonymousNode() may cause showing the UI again.
|
|
// Therefore, we should forget all anonymous contents first.
|
|
// Otherwise, we could leak the old content because of overwritten by
|
|
// ShowInlineTableEditingUIInternal().
|
|
ManualNACPtr addColumnBeforeButton(std::move(mAddColumnBeforeButton));
|
|
ManualNACPtr removeColumnButton(std::move(mRemoveColumnButton));
|
|
ManualNACPtr addColumnAfterButton(std::move(mAddColumnAfterButton));
|
|
ManualNACPtr addRowBeforeButton(std::move(mAddRowBeforeButton));
|
|
ManualNACPtr removeRowButton(std::move(mRemoveRowButton));
|
|
ManualNACPtr addRowAfterButton(std::move(mAddRowAfterButton));
|
|
|
|
DeleteRefToAnonymousNode(std::move(addColumnBeforeButton), ps);
|
|
DeleteRefToAnonymousNode(std::move(removeColumnButton), ps);
|
|
DeleteRefToAnonymousNode(std::move(addColumnAfterButton), ps);
|
|
DeleteRefToAnonymousNode(std::move(addRowBeforeButton), ps);
|
|
DeleteRefToAnonymousNode(std::move(removeRowButton), ps);
|
|
DeleteRefToAnonymousNode(std::move(addRowAfterButton), ps);
|
|
}
|
|
|
|
nsresult HTMLEditor::DoInlineTableEditingAction(const Element& aElement) {
|
|
nsAutoString anonclass;
|
|
aElement.GetAttr(kNameSpaceID_None, nsGkAtoms::_moz_anonclass, anonclass);
|
|
|
|
if (!StringBeginsWith(anonclass, NS_LITERAL_STRING("mozTable"))) {
|
|
return NS_OK;
|
|
}
|
|
|
|
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
|
|
if (NS_WARN_IF(!editActionData.CanHandle())) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
RefPtr<Element> tableElement = GetEnclosingTable(mInlineEditedCell);
|
|
int32_t rowCount, colCount;
|
|
nsresult rv = GetTableSize(tableElement, &rowCount, &colCount);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool hideUI = false;
|
|
bool hideResizersWithInlineTableUI = (mResizedObject == tableElement);
|
|
|
|
if (anonclass.EqualsLiteral("mozTableAddColumnBefore")) {
|
|
AutoEditActionDataSetter editActionData(*this,
|
|
EditAction::eInsertTableColumn);
|
|
if (NS_WARN_IF(!editActionData.CanHandle())) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
DebugOnly<nsresult> rv = InsertTableColumnsWithTransaction(
|
|
1, InsertPosition::eBeforeSelectedCell);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"Failed to insert a column before the selected cell");
|
|
} else if (anonclass.EqualsLiteral("mozTableAddColumnAfter")) {
|
|
AutoEditActionDataSetter editActionData(*this,
|
|
EditAction::eInsertTableColumn);
|
|
if (NS_WARN_IF(!editActionData.CanHandle())) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
DebugOnly<nsresult> rv = InsertTableColumnsWithTransaction(
|
|
1, InsertPosition::eAfterSelectedCell);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"Failed to insert a column after the selected cell");
|
|
} else if (anonclass.EqualsLiteral("mozTableAddRowBefore")) {
|
|
AutoEditActionDataSetter editActionData(*this,
|
|
EditAction::eInsertTableRowElement);
|
|
if (NS_WARN_IF(!editActionData.CanHandle())) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
DebugOnly<nsresult> rv =
|
|
InsertTableRowsWithTransaction(1, InsertPosition::eBeforeSelectedCell);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"Failed to insert a row before the selected cell");
|
|
} else if (anonclass.EqualsLiteral("mozTableAddRowAfter")) {
|
|
AutoEditActionDataSetter editActionData(*this,
|
|
EditAction::eInsertTableRowElement);
|
|
if (NS_WARN_IF(!editActionData.CanHandle())) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
DebugOnly<nsresult> rv =
|
|
InsertTableRowsWithTransaction(1, InsertPosition::eAfterSelectedCell);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"Failed to insert a row after the selected cell");
|
|
} else if (anonclass.EqualsLiteral("mozTableRemoveColumn")) {
|
|
AutoEditActionDataSetter editActionData(*this,
|
|
EditAction::eRemoveTableColumn);
|
|
if (NS_WARN_IF(!editActionData.CanHandle())) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
DebugOnly<nsresult> rv = DeleteSelectedTableColumnsWithTransaction(1);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"Failed to delete the selected table column");
|
|
hideUI = (colCount == 1);
|
|
} else if (anonclass.EqualsLiteral("mozTableRemoveRow")) {
|
|
AutoEditActionDataSetter editActionData(*this,
|
|
EditAction::eRemoveTableRowElement);
|
|
if (NS_WARN_IF(!editActionData.CanHandle())) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
DebugOnly<nsresult> rv = DeleteSelectedTableRowsWithTransaction(1);
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
|
"Failed to delete the selected table row");
|
|
hideUI = (rowCount == 1);
|
|
} else {
|
|
return NS_OK;
|
|
}
|
|
|
|
++mInlineTableEditorUsedCount;
|
|
|
|
// InsertTableRowsWithTransaction() might causes reframe.
|
|
if (Destroyed()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (hideUI) {
|
|
HideInlineTableEditingUIInternal();
|
|
if (hideResizersWithInlineTableUI) {
|
|
DebugOnly<nsresult> rv = HideResizersInternal();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to hide resizers");
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void HTMLEditor::AddMouseClickListener(Element* aElement) {
|
|
if (aElement) {
|
|
aElement->AddEventListener(NS_LITERAL_STRING("click"), mEventListener,
|
|
true);
|
|
}
|
|
}
|
|
|
|
void HTMLEditor::RemoveMouseClickListener(Element* aElement) {
|
|
if (aElement) {
|
|
aElement->RemoveEventListener(NS_LITERAL_STRING("click"), mEventListener,
|
|
true);
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HTMLEditor::RefreshInlineTableEditingUI() {
|
|
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
|
|
if (NS_WARN_IF(!editActionData.CanHandle())) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
nsresult rv = RefreshInlineTableEditingUIInternal();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult HTMLEditor::RefreshInlineTableEditingUIInternal() {
|
|
if (!mInlineEditedCell) {
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<nsGenericHTMLElement> inlineEditingCellElement =
|
|
nsGenericHTMLElement::FromNode(mInlineEditedCell);
|
|
if (NS_WARN_IF(!inlineEditingCellElement)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
int32_t cellX = 0, cellY = 0;
|
|
GetElementOrigin(*mInlineEditedCell, cellX, cellY);
|
|
|
|
int32_t cellWidth = inlineEditingCellElement->OffsetWidth();
|
|
int32_t cellHeight = inlineEditingCellElement->OffsetHeight();
|
|
|
|
int32_t centerOfCellX = cellX + cellWidth / 2;
|
|
int32_t centerOfCellY = cellY + cellHeight / 2;
|
|
|
|
RefPtr<Element> tableElement = GetEnclosingTable(mInlineEditedCell);
|
|
int32_t rowCount = 0, colCount = 0;
|
|
nsresult rv = GetTableSize(tableElement, &rowCount, &colCount);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
RefPtr<Element> addColumunBeforeButton = mAddColumnBeforeButton.get();
|
|
SetAnonymousElementPosition(centerOfCellX - 10, cellY - 7,
|
|
addColumunBeforeButton);
|
|
if (NS_WARN_IF(addColumunBeforeButton != mAddColumnBeforeButton.get())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RefPtr<Element> removeColumnButton = mRemoveColumnButton.get();
|
|
SetAnonymousElementPosition(centerOfCellX - 4, cellY - 7, removeColumnButton);
|
|
if (NS_WARN_IF(removeColumnButton != mRemoveColumnButton.get())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RefPtr<Element> addColumnAfterButton = mAddColumnAfterButton.get();
|
|
SetAnonymousElementPosition(centerOfCellX + 6, cellY - 7,
|
|
addColumnAfterButton);
|
|
if (NS_WARN_IF(addColumnAfterButton != mAddColumnAfterButton.get())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RefPtr<Element> addRowBeforeButton = mAddRowBeforeButton.get();
|
|
SetAnonymousElementPosition(cellX - 7, centerOfCellY - 10,
|
|
addRowBeforeButton);
|
|
if (NS_WARN_IF(addRowBeforeButton != mAddRowBeforeButton.get())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RefPtr<Element> removeRowButton = mRemoveRowButton.get();
|
|
SetAnonymousElementPosition(cellX - 7, centerOfCellY - 4, removeRowButton);
|
|
if (NS_WARN_IF(removeRowButton != mRemoveRowButton.get())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RefPtr<Element> addRowAfterButton = mAddRowAfterButton.get();
|
|
SetAnonymousElementPosition(cellX - 7, centerOfCellY + 6, addRowAfterButton);
|
|
if (NS_WARN_IF(addRowAfterButton != mAddRowAfterButton.get())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace mozilla
|