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