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