editor/libeditor/html/nsHTMLEditRules.cpp

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 sw=2 et tw=79: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include <stdlib.h>
michael@0 8
michael@0 9 #include "mozilla/Assertions.h"
michael@0 10 #include "mozilla/MathAlgorithms.h"
michael@0 11 #include "mozilla/Preferences.h"
michael@0 12 #include "mozilla/dom/Selection.h"
michael@0 13 #include "mozilla/dom/Element.h"
michael@0 14 #include "mozilla/mozalloc.h"
michael@0 15 #include "nsAString.h"
michael@0 16 #include "nsAlgorithm.h"
michael@0 17 #include "nsCOMArray.h"
michael@0 18 #include "nsCRT.h"
michael@0 19 #include "nsCRTGlue.h"
michael@0 20 #include "nsComponentManagerUtils.h"
michael@0 21 #include "nsContentUtils.h"
michael@0 22 #include "nsDebug.h"
michael@0 23 #include "nsEditProperty.h"
michael@0 24 #include "nsEditor.h"
michael@0 25 #include "nsEditorUtils.h"
michael@0 26 #include "nsError.h"
michael@0 27 #include "nsGkAtoms.h"
michael@0 28 #include "nsHTMLCSSUtils.h"
michael@0 29 #include "nsHTMLEditRules.h"
michael@0 30 #include "nsHTMLEditUtils.h"
michael@0 31 #include "nsHTMLEditor.h"
michael@0 32 #include "nsIAtom.h"
michael@0 33 #include "nsIContent.h"
michael@0 34 #include "nsIContentIterator.h"
michael@0 35 #include "nsID.h"
michael@0 36 #include "nsIDOMCharacterData.h"
michael@0 37 #include "nsIDOMDocument.h"
michael@0 38 #include "nsIDOMElement.h"
michael@0 39 #include "nsIDOMNode.h"
michael@0 40 #include "nsIDOMRange.h"
michael@0 41 #include "nsIDOMText.h"
michael@0 42 #include "nsIHTMLAbsPosEditor.h"
michael@0 43 #include "nsIHTMLDocument.h"
michael@0 44 #include "nsINode.h"
michael@0 45 #include "nsISelection.h"
michael@0 46 #include "nsISelectionPrivate.h"
michael@0 47 #include "nsLiteralString.h"
michael@0 48 #include "nsPlaintextEditor.h"
michael@0 49 #include "nsRange.h"
michael@0 50 #include "nsReadableUtils.h"
michael@0 51 #include "nsString.h"
michael@0 52 #include "nsStringFwd.h"
michael@0 53 #include "nsTArray.h"
michael@0 54 #include "nsTextEditUtils.h"
michael@0 55 #include "nsThreadUtils.h"
michael@0 56 #include "nsUnicharUtils.h"
michael@0 57 #include "nsWSRunObject.h"
michael@0 58 #include <algorithm>
michael@0 59
michael@0 60 class nsISupports;
michael@0 61 class nsRulesInfo;
michael@0 62
michael@0 63 using namespace mozilla;
michael@0 64 using namespace mozilla::dom;
michael@0 65
michael@0 66 //const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE";
michael@0 67 //const static char* kMOZEditorBogusNodeValue="TRUE";
michael@0 68
michael@0 69 enum
michael@0 70 {
michael@0 71 kLonely = 0,
michael@0 72 kPrevSib = 1,
michael@0 73 kNextSib = 2,
michael@0 74 kBothSibs = 3
michael@0 75 };
michael@0 76
michael@0 77 /********************************************************
michael@0 78 * first some helpful functors we will use
michael@0 79 ********************************************************/
michael@0 80
michael@0 81 static bool IsBlockNode(nsIDOMNode* node)
michael@0 82 {
michael@0 83 bool isBlock (false);
michael@0 84 nsHTMLEditor::NodeIsBlockStatic(node, &isBlock);
michael@0 85 return isBlock;
michael@0 86 }
michael@0 87
michael@0 88 static bool IsInlineNode(nsIDOMNode* node)
michael@0 89 {
michael@0 90 return !IsBlockNode(node);
michael@0 91 }
michael@0 92
michael@0 93 static bool
michael@0 94 IsStyleCachePreservingAction(EditAction action)
michael@0 95 {
michael@0 96 return action == EditAction::deleteSelection ||
michael@0 97 action == EditAction::insertBreak ||
michael@0 98 action == EditAction::makeList ||
michael@0 99 action == EditAction::indent ||
michael@0 100 action == EditAction::outdent ||
michael@0 101 action == EditAction::align ||
michael@0 102 action == EditAction::makeBasicBlock ||
michael@0 103 action == EditAction::removeList ||
michael@0 104 action == EditAction::makeDefListItem ||
michael@0 105 action == EditAction::insertElement ||
michael@0 106 action == EditAction::insertQuotation;
michael@0 107 }
michael@0 108
michael@0 109 class nsTableCellAndListItemFunctor : public nsBoolDomIterFunctor
michael@0 110 {
michael@0 111 public:
michael@0 112 virtual bool operator()(nsIDOMNode* aNode) // used to build list of all li's, td's & th's iterator covers
michael@0 113 {
michael@0 114 if (nsHTMLEditUtils::IsTableCell(aNode)) return true;
michael@0 115 if (nsHTMLEditUtils::IsListItem(aNode)) return true;
michael@0 116 return false;
michael@0 117 }
michael@0 118 };
michael@0 119
michael@0 120 class nsBRNodeFunctor : public nsBoolDomIterFunctor
michael@0 121 {
michael@0 122 public:
michael@0 123 virtual bool operator()(nsIDOMNode* aNode)
michael@0 124 {
michael@0 125 if (nsTextEditUtils::IsBreak(aNode)) return true;
michael@0 126 return false;
michael@0 127 }
michael@0 128 };
michael@0 129
michael@0 130 class nsEmptyEditableFunctor : public nsBoolDomIterFunctor
michael@0 131 {
michael@0 132 public:
michael@0 133 nsEmptyEditableFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {}
michael@0 134 virtual bool operator()(nsIDOMNode* aNode)
michael@0 135 {
michael@0 136 if (mHTMLEditor->IsEditable(aNode) &&
michael@0 137 (nsHTMLEditUtils::IsListItem(aNode) ||
michael@0 138 nsHTMLEditUtils::IsTableCellOrCaption(aNode)))
michael@0 139 {
michael@0 140 bool bIsEmptyNode;
michael@0 141 nsresult res = mHTMLEditor->IsEmptyNode(aNode, &bIsEmptyNode, false, false);
michael@0 142 NS_ENSURE_SUCCESS(res, false);
michael@0 143 if (bIsEmptyNode)
michael@0 144 return true;
michael@0 145 }
michael@0 146 return false;
michael@0 147 }
michael@0 148 protected:
michael@0 149 nsHTMLEditor* mHTMLEditor;
michael@0 150 };
michael@0 151
michael@0 152 class nsEditableTextFunctor : public nsBoolDomIterFunctor
michael@0 153 {
michael@0 154 public:
michael@0 155 nsEditableTextFunctor(nsHTMLEditor* editor) : mHTMLEditor(editor) {}
michael@0 156 virtual bool operator()(nsIDOMNode* aNode)
michael@0 157 {
michael@0 158 if (nsEditor::IsTextNode(aNode) && mHTMLEditor->IsEditable(aNode))
michael@0 159 {
michael@0 160 return true;
michael@0 161 }
michael@0 162 return false;
michael@0 163 }
michael@0 164 protected:
michael@0 165 nsHTMLEditor* mHTMLEditor;
michael@0 166 };
michael@0 167
michael@0 168
michael@0 169 /********************************************************
michael@0 170 * Constructor/Destructor
michael@0 171 ********************************************************/
michael@0 172
michael@0 173 nsHTMLEditRules::nsHTMLEditRules()
michael@0 174 {
michael@0 175 InitFields();
michael@0 176 }
michael@0 177
michael@0 178 void
michael@0 179 nsHTMLEditRules::InitFields()
michael@0 180 {
michael@0 181 mHTMLEditor = nullptr;
michael@0 182 mDocChangeRange = nullptr;
michael@0 183 mListenerEnabled = true;
michael@0 184 mReturnInEmptyLIKillsList = true;
michael@0 185 mDidDeleteSelection = false;
michael@0 186 mDidRangedDelete = false;
michael@0 187 mRestoreContentEditableCount = false;
michael@0 188 mUtilRange = nullptr;
michael@0 189 mJoinOffset = 0;
michael@0 190 mNewBlock = nullptr;
michael@0 191 mRangeItem = new nsRangeStore();
michael@0 192 // populate mCachedStyles
michael@0 193 mCachedStyles[0] = StyleCache(nsEditProperty::b, EmptyString(), EmptyString());
michael@0 194 mCachedStyles[1] = StyleCache(nsEditProperty::i, EmptyString(), EmptyString());
michael@0 195 mCachedStyles[2] = StyleCache(nsEditProperty::u, EmptyString(), EmptyString());
michael@0 196 mCachedStyles[3] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("face"), EmptyString());
michael@0 197 mCachedStyles[4] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("size"), EmptyString());
michael@0 198 mCachedStyles[5] = StyleCache(nsEditProperty::font, NS_LITERAL_STRING("color"), EmptyString());
michael@0 199 mCachedStyles[6] = StyleCache(nsEditProperty::tt, EmptyString(), EmptyString());
michael@0 200 mCachedStyles[7] = StyleCache(nsEditProperty::em, EmptyString(), EmptyString());
michael@0 201 mCachedStyles[8] = StyleCache(nsEditProperty::strong, EmptyString(), EmptyString());
michael@0 202 mCachedStyles[9] = StyleCache(nsEditProperty::dfn, EmptyString(), EmptyString());
michael@0 203 mCachedStyles[10] = StyleCache(nsEditProperty::code, EmptyString(), EmptyString());
michael@0 204 mCachedStyles[11] = StyleCache(nsEditProperty::samp, EmptyString(), EmptyString());
michael@0 205 mCachedStyles[12] = StyleCache(nsEditProperty::var, EmptyString(), EmptyString());
michael@0 206 mCachedStyles[13] = StyleCache(nsEditProperty::cite, EmptyString(), EmptyString());
michael@0 207 mCachedStyles[14] = StyleCache(nsEditProperty::abbr, EmptyString(), EmptyString());
michael@0 208 mCachedStyles[15] = StyleCache(nsEditProperty::acronym, EmptyString(), EmptyString());
michael@0 209 mCachedStyles[16] = StyleCache(nsEditProperty::cssBackgroundColor, EmptyString(), EmptyString());
michael@0 210 mCachedStyles[17] = StyleCache(nsEditProperty::sub, EmptyString(), EmptyString());
michael@0 211 mCachedStyles[18] = StyleCache(nsEditProperty::sup, EmptyString(), EmptyString());
michael@0 212 }
michael@0 213
michael@0 214 nsHTMLEditRules::~nsHTMLEditRules()
michael@0 215 {
michael@0 216 // remove ourselves as a listener to edit actions
michael@0 217 // In some cases, we have already been removed by
michael@0 218 // ~nsHTMLEditor, in which case we will get a null pointer here
michael@0 219 // which we ignore. But this allows us to add the ability to
michael@0 220 // switch rule sets on the fly if we want.
michael@0 221 if (mHTMLEditor)
michael@0 222 mHTMLEditor->RemoveEditActionListener(this);
michael@0 223 }
michael@0 224
michael@0 225 /********************************************************
michael@0 226 * XPCOM Cruft
michael@0 227 ********************************************************/
michael@0 228
michael@0 229 NS_IMPL_ADDREF_INHERITED(nsHTMLEditRules, nsTextEditRules)
michael@0 230 NS_IMPL_RELEASE_INHERITED(nsHTMLEditRules, nsTextEditRules)
michael@0 231 NS_IMPL_QUERY_INTERFACE_INHERITED(nsHTMLEditRules, nsTextEditRules, nsIEditActionListener)
michael@0 232
michael@0 233
michael@0 234 /********************************************************
michael@0 235 * Public methods
michael@0 236 ********************************************************/
michael@0 237
michael@0 238 NS_IMETHODIMP
michael@0 239 nsHTMLEditRules::Init(nsPlaintextEditor *aEditor)
michael@0 240 {
michael@0 241 InitFields();
michael@0 242
michael@0 243 mHTMLEditor = static_cast<nsHTMLEditor*>(aEditor);
michael@0 244 nsresult res;
michael@0 245
michael@0 246 // call through to base class Init
michael@0 247 res = nsTextEditRules::Init(aEditor);
michael@0 248 NS_ENSURE_SUCCESS(res, res);
michael@0 249
michael@0 250 // cache any prefs we care about
michael@0 251 static const char kPrefName[] =
michael@0 252 "editor.html.typing.returnInEmptyListItemClosesList";
michael@0 253 nsAdoptingCString returnInEmptyLIKillsList =
michael@0 254 Preferences::GetCString(kPrefName);
michael@0 255
michael@0 256 // only when "false", becomes FALSE. Otherwise (including empty), TRUE.
michael@0 257 // XXX Why was this pref designed as a string and not bool?
michael@0 258 mReturnInEmptyLIKillsList = !returnInEmptyLIKillsList.EqualsLiteral("false");
michael@0 259
michael@0 260 // make a utility range for use by the listenter
michael@0 261 nsCOMPtr<nsINode> node = mHTMLEditor->GetRoot();
michael@0 262 if (!node) {
michael@0 263 node = mHTMLEditor->GetDocument();
michael@0 264 }
michael@0 265
michael@0 266 NS_ENSURE_STATE(node);
michael@0 267
michael@0 268 mUtilRange = new nsRange(node);
michael@0 269
michael@0 270 // set up mDocChangeRange to be whole doc
michael@0 271 // temporarily turn off rules sniffing
michael@0 272 nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this);
michael@0 273 if (!mDocChangeRange) {
michael@0 274 mDocChangeRange = new nsRange(node);
michael@0 275 }
michael@0 276
michael@0 277 if (node->IsElement()) {
michael@0 278 ErrorResult rv;
michael@0 279 mDocChangeRange->SelectNode(*node, rv);
michael@0 280 res = AdjustSpecialBreaks(node);
michael@0 281 NS_ENSURE_SUCCESS(res, res);
michael@0 282 }
michael@0 283
michael@0 284 // add ourselves as a listener to edit actions
michael@0 285 res = mHTMLEditor->AddEditActionListener(this);
michael@0 286
michael@0 287 return res;
michael@0 288 }
michael@0 289
michael@0 290 NS_IMETHODIMP
michael@0 291 nsHTMLEditRules::DetachEditor()
michael@0 292 {
michael@0 293 if (mHTMLEditor) {
michael@0 294 mHTMLEditor->RemoveEditActionListener(this);
michael@0 295 }
michael@0 296 mHTMLEditor = nullptr;
michael@0 297 return nsTextEditRules::DetachEditor();
michael@0 298 }
michael@0 299
michael@0 300 NS_IMETHODIMP
michael@0 301 nsHTMLEditRules::BeforeEdit(EditAction action,
michael@0 302 nsIEditor::EDirection aDirection)
michael@0 303 {
michael@0 304 if (mLockRulesSniffing) return NS_OK;
michael@0 305
michael@0 306 nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this);
michael@0 307 mDidExplicitlySetInterline = false;
michael@0 308
michael@0 309 if (!mActionNesting++)
michael@0 310 {
michael@0 311 // clear our flag about if just deleted a range
michael@0 312 mDidRangedDelete = false;
michael@0 313
michael@0 314 // remember where our selection was before edit action took place:
michael@0 315
michael@0 316 // get selection
michael@0 317 nsCOMPtr<nsISelection> selection;
michael@0 318 NS_ENSURE_STATE(mHTMLEditor);
michael@0 319 nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
michael@0 320 NS_ENSURE_SUCCESS(res, res);
michael@0 321
michael@0 322 // get the selection start location
michael@0 323 nsCOMPtr<nsIDOMNode> selStartNode, selEndNode;
michael@0 324 int32_t selOffset;
michael@0 325 NS_ENSURE_STATE(mHTMLEditor);
michael@0 326 res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selStartNode), &selOffset);
michael@0 327 NS_ENSURE_SUCCESS(res, res);
michael@0 328 mRangeItem->startNode = selStartNode;
michael@0 329 mRangeItem->startOffset = selOffset;
michael@0 330
michael@0 331 // get the selection end location
michael@0 332 NS_ENSURE_STATE(mHTMLEditor);
michael@0 333 res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(selEndNode), &selOffset);
michael@0 334 NS_ENSURE_SUCCESS(res, res);
michael@0 335 mRangeItem->endNode = selEndNode;
michael@0 336 mRangeItem->endOffset = selOffset;
michael@0 337
michael@0 338 // register this range with range updater to track this as we perturb the doc
michael@0 339 NS_ENSURE_STATE(mHTMLEditor);
michael@0 340 (mHTMLEditor->mRangeUpdater).RegisterRangeItem(mRangeItem);
michael@0 341
michael@0 342 // clear deletion state bool
michael@0 343 mDidDeleteSelection = false;
michael@0 344
michael@0 345 // clear out mDocChangeRange and mUtilRange
michael@0 346 if(mDocChangeRange)
michael@0 347 {
michael@0 348 // clear out our accounting of what changed
michael@0 349 mDocChangeRange->Reset();
michael@0 350 }
michael@0 351 if(mUtilRange)
michael@0 352 {
michael@0 353 // ditto for mUtilRange.
michael@0 354 mUtilRange->Reset();
michael@0 355 }
michael@0 356
michael@0 357 // remember current inline styles for deletion and normal insertion operations
michael@0 358 if (action == EditAction::insertText ||
michael@0 359 action == EditAction::insertIMEText ||
michael@0 360 action == EditAction::deleteSelection ||
michael@0 361 IsStyleCachePreservingAction(action)) {
michael@0 362 nsCOMPtr<nsIDOMNode> selNode = selStartNode;
michael@0 363 if (aDirection == nsIEditor::eNext)
michael@0 364 selNode = selEndNode;
michael@0 365 res = CacheInlineStyles(selNode);
michael@0 366 NS_ENSURE_SUCCESS(res, res);
michael@0 367 }
michael@0 368
michael@0 369 // Stabilize the document against contenteditable count changes
michael@0 370 NS_ENSURE_STATE(mHTMLEditor);
michael@0 371 nsCOMPtr<nsIDOMDocument> doc = mHTMLEditor->GetDOMDocument();
michael@0 372 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
michael@0 373 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
michael@0 374 NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE);
michael@0 375 if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) {
michael@0 376 htmlDoc->ChangeContentEditableCount(nullptr, +1);
michael@0 377 mRestoreContentEditableCount = true;
michael@0 378 }
michael@0 379
michael@0 380 // check that selection is in subtree defined by body node
michael@0 381 ConfirmSelectionInBody();
michael@0 382 // let rules remember the top level action
michael@0 383 mTheAction = action;
michael@0 384 }
michael@0 385 return NS_OK;
michael@0 386 }
michael@0 387
michael@0 388
michael@0 389 NS_IMETHODIMP
michael@0 390 nsHTMLEditRules::AfterEdit(EditAction action,
michael@0 391 nsIEditor::EDirection aDirection)
michael@0 392 {
michael@0 393 if (mLockRulesSniffing) return NS_OK;
michael@0 394
michael@0 395 nsAutoLockRulesSniffing lockIt(this);
michael@0 396
michael@0 397 NS_PRECONDITION(mActionNesting>0, "bad action nesting!");
michael@0 398 nsresult res = NS_OK;
michael@0 399 if (!--mActionNesting)
michael@0 400 {
michael@0 401 // do all the tricky stuff
michael@0 402 res = AfterEditInner(action, aDirection);
michael@0 403
michael@0 404 // free up selectionState range item
michael@0 405 NS_ENSURE_STATE(mHTMLEditor);
michael@0 406 (mHTMLEditor->mRangeUpdater).DropRangeItem(mRangeItem);
michael@0 407
michael@0 408 // Reset the contenteditable count to its previous value
michael@0 409 if (mRestoreContentEditableCount) {
michael@0 410 NS_ENSURE_STATE(mHTMLEditor);
michael@0 411 nsCOMPtr<nsIDOMDocument> doc = mHTMLEditor->GetDOMDocument();
michael@0 412 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
michael@0 413 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
michael@0 414 NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE);
michael@0 415 if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) {
michael@0 416 htmlDoc->ChangeContentEditableCount(nullptr, -1);
michael@0 417 }
michael@0 418 mRestoreContentEditableCount = false;
michael@0 419 }
michael@0 420 }
michael@0 421
michael@0 422 return res;
michael@0 423 }
michael@0 424
michael@0 425
michael@0 426 nsresult
michael@0 427 nsHTMLEditRules::AfterEditInner(EditAction action,
michael@0 428 nsIEditor::EDirection aDirection)
michael@0 429 {
michael@0 430 ConfirmSelectionInBody();
michael@0 431 if (action == EditAction::ignore) return NS_OK;
michael@0 432
michael@0 433 nsCOMPtr<nsISelection>selection;
michael@0 434 NS_ENSURE_STATE(mHTMLEditor);
michael@0 435 nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
michael@0 436 NS_ENSURE_SUCCESS(res, res);
michael@0 437
michael@0 438 nsCOMPtr<nsIDOMNode> rangeStartParent, rangeEndParent;
michael@0 439 int32_t rangeStartOffset = 0, rangeEndOffset = 0;
michael@0 440 // do we have a real range to act on?
michael@0 441 bool bDamagedRange = false;
michael@0 442 if (mDocChangeRange)
michael@0 443 {
michael@0 444 mDocChangeRange->GetStartContainer(getter_AddRefs(rangeStartParent));
michael@0 445 mDocChangeRange->GetEndContainer(getter_AddRefs(rangeEndParent));
michael@0 446 mDocChangeRange->GetStartOffset(&rangeStartOffset);
michael@0 447 mDocChangeRange->GetEndOffset(&rangeEndOffset);
michael@0 448 if (rangeStartParent && rangeEndParent)
michael@0 449 bDamagedRange = true;
michael@0 450 }
michael@0 451
michael@0 452 if (bDamagedRange && !((action == EditAction::undo) || (action == EditAction::redo)))
michael@0 453 {
michael@0 454 // don't let any txns in here move the selection around behind our back.
michael@0 455 // Note that this won't prevent explicit selection setting from working.
michael@0 456 NS_ENSURE_STATE(mHTMLEditor);
michael@0 457 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
michael@0 458
michael@0 459 // expand the "changed doc range" as needed
michael@0 460 res = PromoteRange(mDocChangeRange, action);
michael@0 461 NS_ENSURE_SUCCESS(res, res);
michael@0 462
michael@0 463 // if we did a ranged deletion, make sure we have a place to put caret.
michael@0 464 // Note we only want to do this if the overall operation was deletion,
michael@0 465 // not if deletion was done along the way for EditAction::loadHTML, EditAction::insertText, etc.
michael@0 466 // That's why this is here rather than DidDeleteSelection().
michael@0 467 if ((action == EditAction::deleteSelection) && mDidRangedDelete)
michael@0 468 {
michael@0 469 res = InsertBRIfNeeded(selection);
michael@0 470 NS_ENSURE_SUCCESS(res, res);
michael@0 471 }
michael@0 472
michael@0 473 // add in any needed <br>s, and remove any unneeded ones.
michael@0 474 res = AdjustSpecialBreaks();
michael@0 475 NS_ENSURE_SUCCESS(res, res);
michael@0 476
michael@0 477 // merge any adjacent text nodes
michael@0 478 if ( (action != EditAction::insertText &&
michael@0 479 action != EditAction::insertIMEText) )
michael@0 480 {
michael@0 481 NS_ENSURE_STATE(mHTMLEditor);
michael@0 482 res = mHTMLEditor->CollapseAdjacentTextNodes(mDocChangeRange);
michael@0 483 NS_ENSURE_SUCCESS(res, res);
michael@0 484 }
michael@0 485
michael@0 486 // clean up any empty nodes in the selection
michael@0 487 res = RemoveEmptyNodes();
michael@0 488 NS_ENSURE_SUCCESS(res, res);
michael@0 489
michael@0 490 // attempt to transform any unneeded nbsp's into spaces after doing various operations
michael@0 491 if ((action == EditAction::insertText) ||
michael@0 492 (action == EditAction::insertIMEText) ||
michael@0 493 (action == EditAction::deleteSelection) ||
michael@0 494 (action == EditAction::insertBreak) ||
michael@0 495 (action == EditAction::htmlPaste ||
michael@0 496 (action == EditAction::loadHTML)))
michael@0 497 {
michael@0 498 res = AdjustWhitespace(selection);
michael@0 499 NS_ENSURE_SUCCESS(res, res);
michael@0 500
michael@0 501 // also do this for original selection endpoints.
michael@0 502 NS_ENSURE_STATE(mHTMLEditor);
michael@0 503 nsWSRunObject(mHTMLEditor, mRangeItem->startNode,
michael@0 504 mRangeItem->startOffset).AdjustWhitespace();
michael@0 505 // we only need to handle old selection endpoint if it was different from start
michael@0 506 if (mRangeItem->startNode != mRangeItem->endNode ||
michael@0 507 mRangeItem->startOffset != mRangeItem->endOffset) {
michael@0 508 NS_ENSURE_STATE(mHTMLEditor);
michael@0 509 nsWSRunObject(mHTMLEditor, mRangeItem->endNode,
michael@0 510 mRangeItem->endOffset).AdjustWhitespace();
michael@0 511 }
michael@0 512 }
michael@0 513
michael@0 514 // if we created a new block, make sure selection lands in it
michael@0 515 if (mNewBlock)
michael@0 516 {
michael@0 517 res = PinSelectionToNewBlock(selection);
michael@0 518 mNewBlock = 0;
michael@0 519 }
michael@0 520
michael@0 521 // adjust selection for insert text, html paste, and delete actions
michael@0 522 if ((action == EditAction::insertText) ||
michael@0 523 (action == EditAction::insertIMEText) ||
michael@0 524 (action == EditAction::deleteSelection) ||
michael@0 525 (action == EditAction::insertBreak) ||
michael@0 526 (action == EditAction::htmlPaste ||
michael@0 527 (action == EditAction::loadHTML)))
michael@0 528 {
michael@0 529 res = AdjustSelection(selection, aDirection);
michael@0 530 NS_ENSURE_SUCCESS(res, res);
michael@0 531 }
michael@0 532
michael@0 533 // check for any styles which were removed inappropriately
michael@0 534 if (action == EditAction::insertText ||
michael@0 535 action == EditAction::insertIMEText ||
michael@0 536 action == EditAction::deleteSelection ||
michael@0 537 IsStyleCachePreservingAction(action)) {
michael@0 538 NS_ENSURE_STATE(mHTMLEditor);
michael@0 539 mHTMLEditor->mTypeInState->UpdateSelState(selection);
michael@0 540 res = ReapplyCachedStyles();
michael@0 541 NS_ENSURE_SUCCESS(res, res);
michael@0 542 ClearCachedStyles();
michael@0 543 }
michael@0 544 }
michael@0 545
michael@0 546 NS_ENSURE_STATE(mHTMLEditor);
michael@0 547
michael@0 548 res = mHTMLEditor->HandleInlineSpellCheck(action, selection,
michael@0 549 mRangeItem->startNode,
michael@0 550 mRangeItem->startOffset,
michael@0 551 rangeStartParent, rangeStartOffset,
michael@0 552 rangeEndParent, rangeEndOffset);
michael@0 553 NS_ENSURE_SUCCESS(res, res);
michael@0 554
michael@0 555 // detect empty doc
michael@0 556 res = CreateBogusNodeIfNeeded(selection);
michael@0 557
michael@0 558 // adjust selection HINT if needed
michael@0 559 NS_ENSURE_SUCCESS(res, res);
michael@0 560
michael@0 561 if (!mDidExplicitlySetInterline)
michael@0 562 {
michael@0 563 res = CheckInterlinePosition(selection);
michael@0 564 }
michael@0 565
michael@0 566 return res;
michael@0 567 }
michael@0 568
michael@0 569
michael@0 570 NS_IMETHODIMP
michael@0 571 nsHTMLEditRules::WillDoAction(Selection* aSelection,
michael@0 572 nsRulesInfo* aInfo,
michael@0 573 bool* aCancel,
michael@0 574 bool* aHandled)
michael@0 575 {
michael@0 576 MOZ_ASSERT(aInfo && aCancel && aHandled);
michael@0 577
michael@0 578 *aCancel = false;
michael@0 579 *aHandled = false;
michael@0 580
michael@0 581 // my kingdom for dynamic cast
michael@0 582 nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
michael@0 583
michael@0 584 // Deal with actions for which we don't need to check whether the selection is
michael@0 585 // editable.
michael@0 586 if (info->action == EditAction::outputText ||
michael@0 587 info->action == EditAction::undo ||
michael@0 588 info->action == EditAction::redo) {
michael@0 589 return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled);
michael@0 590 }
michael@0 591
michael@0 592 // Nothing to do if there's no selection to act on
michael@0 593 if (!aSelection) {
michael@0 594 return NS_OK;
michael@0 595 }
michael@0 596 NS_ENSURE_TRUE(aSelection->GetRangeCount(), NS_OK);
michael@0 597
michael@0 598 nsRefPtr<nsRange> range = aSelection->GetRangeAt(0);
michael@0 599 nsCOMPtr<nsINode> selStartNode = range->GetStartParent();
michael@0 600
michael@0 601 NS_ENSURE_STATE(mHTMLEditor);
michael@0 602 if (!mHTMLEditor->IsModifiableNode(selStartNode)) {
michael@0 603 *aCancel = true;
michael@0 604 return NS_OK;
michael@0 605 }
michael@0 606
michael@0 607 nsCOMPtr<nsINode> selEndNode = range->GetEndParent();
michael@0 608
michael@0 609 if (selStartNode != selEndNode) {
michael@0 610 NS_ENSURE_STATE(mHTMLEditor);
michael@0 611 if (!mHTMLEditor->IsModifiableNode(selEndNode)) {
michael@0 612 *aCancel = true;
michael@0 613 return NS_OK;
michael@0 614 }
michael@0 615
michael@0 616 NS_ENSURE_STATE(mHTMLEditor);
michael@0 617 if (!mHTMLEditor->IsModifiableNode(range->GetCommonAncestor())) {
michael@0 618 *aCancel = true;
michael@0 619 return NS_OK;
michael@0 620 }
michael@0 621 }
michael@0 622
michael@0 623 switch (info->action) {
michael@0 624 case EditAction::insertText:
michael@0 625 case EditAction::insertIMEText:
michael@0 626 return WillInsertText(info->action, aSelection, aCancel, aHandled,
michael@0 627 info->inString, info->outString, info->maxLength);
michael@0 628 case EditAction::loadHTML:
michael@0 629 return WillLoadHTML(aSelection, aCancel);
michael@0 630 case EditAction::insertBreak:
michael@0 631 return WillInsertBreak(aSelection, aCancel, aHandled);
michael@0 632 case EditAction::deleteSelection:
michael@0 633 return WillDeleteSelection(aSelection, info->collapsedAction,
michael@0 634 info->stripWrappers, aCancel, aHandled);
michael@0 635 case EditAction::makeList:
michael@0 636 return WillMakeList(aSelection, info->blockType, info->entireList,
michael@0 637 info->bulletType, aCancel, aHandled);
michael@0 638 case EditAction::indent:
michael@0 639 return WillIndent(aSelection, aCancel, aHandled);
michael@0 640 case EditAction::outdent:
michael@0 641 return WillOutdent(aSelection, aCancel, aHandled);
michael@0 642 case EditAction::setAbsolutePosition:
michael@0 643 return WillAbsolutePosition(aSelection, aCancel, aHandled);
michael@0 644 case EditAction::removeAbsolutePosition:
michael@0 645 return WillRemoveAbsolutePosition(aSelection, aCancel, aHandled);
michael@0 646 case EditAction::align:
michael@0 647 return WillAlign(aSelection, info->alignType, aCancel, aHandled);
michael@0 648 case EditAction::makeBasicBlock:
michael@0 649 return WillMakeBasicBlock(aSelection, info->blockType, aCancel, aHandled);
michael@0 650 case EditAction::removeList:
michael@0 651 return WillRemoveList(aSelection, info->bOrdered, aCancel, aHandled);
michael@0 652 case EditAction::makeDefListItem:
michael@0 653 return WillMakeDefListItem(aSelection, info->blockType, info->entireList,
michael@0 654 aCancel, aHandled);
michael@0 655 case EditAction::insertElement:
michael@0 656 return WillInsert(aSelection, aCancel);
michael@0 657 case EditAction::decreaseZIndex:
michael@0 658 return WillRelativeChangeZIndex(aSelection, -1, aCancel, aHandled);
michael@0 659 case EditAction::increaseZIndex:
michael@0 660 return WillRelativeChangeZIndex(aSelection, 1, aCancel, aHandled);
michael@0 661 default:
michael@0 662 return nsTextEditRules::WillDoAction(aSelection, aInfo,
michael@0 663 aCancel, aHandled);
michael@0 664 }
michael@0 665 }
michael@0 666
michael@0 667
michael@0 668 NS_IMETHODIMP
michael@0 669 nsHTMLEditRules::DidDoAction(nsISelection *aSelection,
michael@0 670 nsRulesInfo *aInfo, nsresult aResult)
michael@0 671 {
michael@0 672 nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
michael@0 673 switch (info->action)
michael@0 674 {
michael@0 675 case EditAction::insertBreak:
michael@0 676 return DidInsertBreak(aSelection, aResult);
michael@0 677 case EditAction::deleteSelection:
michael@0 678 return DidDeleteSelection(aSelection, info->collapsedAction, aResult);
michael@0 679 case EditAction::makeBasicBlock:
michael@0 680 case EditAction::indent:
michael@0 681 case EditAction::outdent:
michael@0 682 case EditAction::align:
michael@0 683 return DidMakeBasicBlock(aSelection, aInfo, aResult);
michael@0 684 case EditAction::setAbsolutePosition: {
michael@0 685 nsresult rv = DidMakeBasicBlock(aSelection, aInfo, aResult);
michael@0 686 NS_ENSURE_SUCCESS(rv, rv);
michael@0 687 return DidAbsolutePosition();
michael@0 688 }
michael@0 689 default:
michael@0 690 // pass thru to nsTextEditRules
michael@0 691 return nsTextEditRules::DidDoAction(aSelection, aInfo, aResult);
michael@0 692 }
michael@0 693 }
michael@0 694
michael@0 695 nsresult
michael@0 696 nsHTMLEditRules::GetListState(bool *aMixed, bool *aOL, bool *aUL, bool *aDL)
michael@0 697 {
michael@0 698 NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER);
michael@0 699 *aMixed = false;
michael@0 700 *aOL = false;
michael@0 701 *aUL = false;
michael@0 702 *aDL = false;
michael@0 703 bool bNonList = false;
michael@0 704
michael@0 705 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 706 nsresult res = GetListActionNodes(arrayOfNodes, false, true);
michael@0 707 NS_ENSURE_SUCCESS(res, res);
michael@0 708
michael@0 709 // Examine list type for nodes in selection.
michael@0 710 int32_t listCount = arrayOfNodes.Count();
michael@0 711 for (int32_t i = listCount - 1; i >= 0; --i) {
michael@0 712 nsIDOMNode* curDOMNode = arrayOfNodes[i];
michael@0 713 nsCOMPtr<dom::Element> curElement = do_QueryInterface(curDOMNode);
michael@0 714
michael@0 715 if (!curElement) {
michael@0 716 bNonList = true;
michael@0 717 } else if (curElement->IsHTML(nsGkAtoms::ul)) {
michael@0 718 *aUL = true;
michael@0 719 } else if (curElement->IsHTML(nsGkAtoms::ol)) {
michael@0 720 *aOL = true;
michael@0 721 } else if (curElement->IsHTML(nsGkAtoms::li)) {
michael@0 722 if (dom::Element* parent = curElement->GetParentElement()) {
michael@0 723 if (parent->IsHTML(nsGkAtoms::ul)) {
michael@0 724 *aUL = true;
michael@0 725 } else if (parent->IsHTML(nsGkAtoms::ol)) {
michael@0 726 *aOL = true;
michael@0 727 }
michael@0 728 }
michael@0 729 } else if (curElement->IsHTML(nsGkAtoms::dl) ||
michael@0 730 curElement->IsHTML(nsGkAtoms::dt) ||
michael@0 731 curElement->IsHTML(nsGkAtoms::dd)) {
michael@0 732 *aDL = true;
michael@0 733 } else {
michael@0 734 bNonList = true;
michael@0 735 }
michael@0 736 }
michael@0 737
michael@0 738 // hokey arithmetic with booleans
michael@0 739 if ((*aUL + *aOL + *aDL + bNonList) > 1) {
michael@0 740 *aMixed = true;
michael@0 741 }
michael@0 742
michael@0 743 return NS_OK;
michael@0 744 }
michael@0 745
michael@0 746 nsresult
michael@0 747 nsHTMLEditRules::GetListItemState(bool *aMixed, bool *aLI, bool *aDT, bool *aDD)
michael@0 748 {
michael@0 749 NS_ENSURE_TRUE(aMixed && aLI && aDT && aDD, NS_ERROR_NULL_POINTER);
michael@0 750 *aMixed = false;
michael@0 751 *aLI = false;
michael@0 752 *aDT = false;
michael@0 753 *aDD = false;
michael@0 754 bool bNonList = false;
michael@0 755
michael@0 756 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 757 nsresult res = GetListActionNodes(arrayOfNodes, false, true);
michael@0 758 NS_ENSURE_SUCCESS(res, res);
michael@0 759
michael@0 760 // examine list type for nodes in selection
michael@0 761 int32_t listCount = arrayOfNodes.Count();
michael@0 762 for (int32_t i = listCount - 1; i >= 0; --i) {
michael@0 763 nsIDOMNode* curNode = arrayOfNodes[i];
michael@0 764 nsCOMPtr<dom::Element> element = do_QueryInterface(curNode);
michael@0 765 if (!element) {
michael@0 766 bNonList = true;
michael@0 767 } else if (element->IsHTML(nsGkAtoms::ul) ||
michael@0 768 element->IsHTML(nsGkAtoms::ol) ||
michael@0 769 element->IsHTML(nsGkAtoms::li)) {
michael@0 770 *aLI = true;
michael@0 771 } else if (element->IsHTML(nsGkAtoms::dt)) {
michael@0 772 *aDT = true;
michael@0 773 } else if (element->IsHTML(nsGkAtoms::dd)) {
michael@0 774 *aDD = true;
michael@0 775 } else if (element->IsHTML(nsGkAtoms::dl)) {
michael@0 776 // need to look inside dl and see which types of items it has
michael@0 777 bool bDT, bDD;
michael@0 778 GetDefinitionListItemTypes(element, &bDT, &bDD);
michael@0 779 *aDT |= bDT;
michael@0 780 *aDD |= bDD;
michael@0 781 } else {
michael@0 782 bNonList = true;
michael@0 783 }
michael@0 784 }
michael@0 785
michael@0 786 // hokey arithmetic with booleans
michael@0 787 if ( (*aDT + *aDD + bNonList) > 1) *aMixed = true;
michael@0 788
michael@0 789 return NS_OK;
michael@0 790 }
michael@0 791
michael@0 792 nsresult
michael@0 793 nsHTMLEditRules::GetAlignment(bool *aMixed, nsIHTMLEditor::EAlignment *aAlign)
michael@0 794 {
michael@0 795 // for now, just return first alignment. we'll lie about
michael@0 796 // if it's mixed. This is for efficiency
michael@0 797 // given that our current ui doesn't care if it's mixed.
michael@0 798 // cmanske: NOT TRUE! We would like to pay attention to mixed state
michael@0 799 // in Format | Align submenu!
michael@0 800
michael@0 801 // this routine assumes that alignment is done ONLY via divs
michael@0 802
michael@0 803 // default alignment is left
michael@0 804 NS_ENSURE_TRUE(aMixed && aAlign, NS_ERROR_NULL_POINTER);
michael@0 805 *aMixed = false;
michael@0 806 *aAlign = nsIHTMLEditor::eLeft;
michael@0 807
michael@0 808 // get selection
michael@0 809 nsCOMPtr<nsISelection>selection;
michael@0 810 NS_ENSURE_STATE(mHTMLEditor);
michael@0 811 nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
michael@0 812 NS_ENSURE_SUCCESS(res, res);
michael@0 813
michael@0 814 // get selection location
michael@0 815 NS_ENSURE_STATE(mHTMLEditor);
michael@0 816 nsCOMPtr<nsIDOMElement> rootElem = do_QueryInterface(mHTMLEditor->GetRoot());
michael@0 817 NS_ENSURE_TRUE(rootElem, NS_ERROR_FAILURE);
michael@0 818
michael@0 819 int32_t offset, rootOffset;
michael@0 820 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(rootElem, &rootOffset);
michael@0 821 NS_ENSURE_STATE(mHTMLEditor);
michael@0 822 res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(parent), &offset);
michael@0 823 NS_ENSURE_SUCCESS(res, res);
michael@0 824
michael@0 825 // is the selection collapsed?
michael@0 826 nsCOMPtr<nsIDOMNode> nodeToExamine;
michael@0 827 if (selection->Collapsed()) {
michael@0 828 // if it is, we want to look at 'parent' and its ancestors
michael@0 829 // for divs with alignment on them
michael@0 830 nodeToExamine = parent;
michael@0 831 }
michael@0 832 else if (!mHTMLEditor) {
michael@0 833 return NS_ERROR_UNEXPECTED;
michael@0 834 }
michael@0 835 else if (mHTMLEditor->IsTextNode(parent))
michael@0 836 {
michael@0 837 // if we are in a text node, then that is the node of interest
michael@0 838 nodeToExamine = parent;
michael@0 839 }
michael@0 840 else if (nsEditor::NodeIsType(parent, nsEditProperty::html) &&
michael@0 841 offset == rootOffset)
michael@0 842 {
michael@0 843 // if we have selected the body, let's look at the first editable node
michael@0 844 NS_ENSURE_STATE(mHTMLEditor);
michael@0 845 mHTMLEditor->GetNextNode(parent, offset, true, address_of(nodeToExamine));
michael@0 846 }
michael@0 847 else
michael@0 848 {
michael@0 849 nsCOMArray<nsIDOMRange> arrayOfRanges;
michael@0 850 res = GetPromotedRanges(selection, arrayOfRanges, EditAction::align);
michael@0 851 NS_ENSURE_SUCCESS(res, res);
michael@0 852
michael@0 853 // use these ranges to construct a list of nodes to act on.
michael@0 854 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 855 res = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
michael@0 856 EditAction::align, true);
michael@0 857 NS_ENSURE_SUCCESS(res, res);
michael@0 858 nodeToExamine = arrayOfNodes.SafeObjectAt(0);
michael@0 859 }
michael@0 860
michael@0 861 NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER);
michael@0 862
michael@0 863 NS_NAMED_LITERAL_STRING(typeAttrName, "align");
michael@0 864 nsIAtom *dummyProperty = nullptr;
michael@0 865 nsCOMPtr<nsIDOMNode> blockParent;
michael@0 866 NS_ENSURE_STATE(mHTMLEditor);
michael@0 867 if (mHTMLEditor->IsBlockNode(nodeToExamine))
michael@0 868 blockParent = nodeToExamine;
michael@0 869 else {
michael@0 870 NS_ENSURE_STATE(mHTMLEditor);
michael@0 871 blockParent = mHTMLEditor->GetBlockNodeParent(nodeToExamine);
michael@0 872 }
michael@0 873
michael@0 874 NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);
michael@0 875
michael@0 876 NS_ENSURE_STATE(mHTMLEditor);
michael@0 877 if (mHTMLEditor->IsCSSEnabled())
michael@0 878 {
michael@0 879 nsCOMPtr<nsIContent> blockParentContent = do_QueryInterface(blockParent);
michael@0 880 NS_ENSURE_STATE(mHTMLEditor);
michael@0 881 if (blockParentContent &&
michael@0 882 mHTMLEditor->mHTMLCSSUtils->IsCSSEditableProperty(blockParentContent, dummyProperty, &typeAttrName))
michael@0 883 {
michael@0 884 // we are in CSS mode and we know how to align this element with CSS
michael@0 885 nsAutoString value;
michael@0 886 // let's get the value(s) of text-align or margin-left/margin-right
michael@0 887 NS_ENSURE_STATE(mHTMLEditor);
michael@0 888 mHTMLEditor->mHTMLCSSUtils->GetCSSEquivalentToHTMLInlineStyleSet(
michael@0 889 blockParentContent, dummyProperty, &typeAttrName, value,
michael@0 890 nsHTMLCSSUtils::eComputed);
michael@0 891 if (value.EqualsLiteral("center") ||
michael@0 892 value.EqualsLiteral("-moz-center") ||
michael@0 893 value.EqualsLiteral("auto auto"))
michael@0 894 {
michael@0 895 *aAlign = nsIHTMLEditor::eCenter;
michael@0 896 return NS_OK;
michael@0 897 }
michael@0 898 if (value.EqualsLiteral("right") ||
michael@0 899 value.EqualsLiteral("-moz-right") ||
michael@0 900 value.EqualsLiteral("auto 0px"))
michael@0 901 {
michael@0 902 *aAlign = nsIHTMLEditor::eRight;
michael@0 903 return NS_OK;
michael@0 904 }
michael@0 905 if (value.EqualsLiteral("justify"))
michael@0 906 {
michael@0 907 *aAlign = nsIHTMLEditor::eJustify;
michael@0 908 return NS_OK;
michael@0 909 }
michael@0 910 *aAlign = nsIHTMLEditor::eLeft;
michael@0 911 return NS_OK;
michael@0 912 }
michael@0 913 }
michael@0 914
michael@0 915 // check up the ladder for divs with alignment
michael@0 916 nsCOMPtr<nsIDOMNode> temp = nodeToExamine;
michael@0 917 bool isFirstNodeToExamine = true;
michael@0 918 while (nodeToExamine)
michael@0 919 {
michael@0 920 if (!isFirstNodeToExamine && nsHTMLEditUtils::IsTable(nodeToExamine))
michael@0 921 {
michael@0 922 // the node to examine is a table and this is not the first node
michael@0 923 // we examine; let's break here to materialize the 'inline-block'
michael@0 924 // behaviour of html tables regarding to text alignment
michael@0 925 return NS_OK;
michael@0 926 }
michael@0 927 if (nsHTMLEditUtils::SupportsAlignAttr(nodeToExamine))
michael@0 928 {
michael@0 929 // check for alignment
michael@0 930 nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(nodeToExamine);
michael@0 931 if (elem)
michael@0 932 {
michael@0 933 nsAutoString typeAttrVal;
michael@0 934 res = elem->GetAttribute(NS_LITERAL_STRING("align"), typeAttrVal);
michael@0 935 ToLowerCase(typeAttrVal);
michael@0 936 if (NS_SUCCEEDED(res) && typeAttrVal.Length())
michael@0 937 {
michael@0 938 if (typeAttrVal.EqualsLiteral("center"))
michael@0 939 *aAlign = nsIHTMLEditor::eCenter;
michael@0 940 else if (typeAttrVal.EqualsLiteral("right"))
michael@0 941 *aAlign = nsIHTMLEditor::eRight;
michael@0 942 else if (typeAttrVal.EqualsLiteral("justify"))
michael@0 943 *aAlign = nsIHTMLEditor::eJustify;
michael@0 944 else
michael@0 945 *aAlign = nsIHTMLEditor::eLeft;
michael@0 946 return res;
michael@0 947 }
michael@0 948 }
michael@0 949 }
michael@0 950 isFirstNodeToExamine = false;
michael@0 951 res = nodeToExamine->GetParentNode(getter_AddRefs(temp));
michael@0 952 if (NS_FAILED(res)) temp = nullptr;
michael@0 953 nodeToExamine = temp;
michael@0 954 }
michael@0 955 return NS_OK;
michael@0 956 }
michael@0 957
michael@0 958 nsIAtom* MarginPropertyAtomForIndent(nsHTMLCSSUtils* aHTMLCSSUtils, nsIDOMNode* aNode) {
michael@0 959 nsAutoString direction;
michael@0 960 aHTMLCSSUtils->GetComputedProperty(aNode, nsEditProperty::cssDirection, direction);
michael@0 961 return direction.EqualsLiteral("rtl") ?
michael@0 962 nsEditProperty::cssMarginRight : nsEditProperty::cssMarginLeft;
michael@0 963 }
michael@0 964
michael@0 965 nsresult
michael@0 966 nsHTMLEditRules::GetIndentState(bool *aCanIndent, bool *aCanOutdent)
michael@0 967 {
michael@0 968 NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_FAILURE);
michael@0 969 *aCanIndent = true;
michael@0 970 *aCanOutdent = false;
michael@0 971
michael@0 972 // get selection
michael@0 973 nsCOMPtr<nsISelection>selection;
michael@0 974 NS_ENSURE_STATE(mHTMLEditor);
michael@0 975 nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
michael@0 976 NS_ENSURE_SUCCESS(res, res);
michael@0 977 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
michael@0 978 NS_ENSURE_TRUE(selPriv, NS_ERROR_FAILURE);
michael@0 979
michael@0 980 // contruct a list of nodes to act on.
michael@0 981 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 982 res = GetNodesFromSelection(selection, EditAction::indent,
michael@0 983 arrayOfNodes, true);
michael@0 984 NS_ENSURE_SUCCESS(res, res);
michael@0 985
michael@0 986 // examine nodes in selection for blockquotes or list elements;
michael@0 987 // these we can outdent. Note that we return true for canOutdent
michael@0 988 // if *any* of the selection is outdentable, rather than all of it.
michael@0 989 int32_t listCount = arrayOfNodes.Count();
michael@0 990 int32_t i;
michael@0 991 NS_ENSURE_STATE(mHTMLEditor);
michael@0 992 bool useCSS = mHTMLEditor->IsCSSEnabled();
michael@0 993 for (i=listCount-1; i>=0; i--)
michael@0 994 {
michael@0 995 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
michael@0 996
michael@0 997 if (nsHTMLEditUtils::IsNodeThatCanOutdent(curNode))
michael@0 998 {
michael@0 999 *aCanOutdent = true;
michael@0 1000 break;
michael@0 1001 }
michael@0 1002 else if (useCSS) {
michael@0 1003 // we are in CSS mode, indentation is done using the margin-left (or margin-right) property
michael@0 1004 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1005 nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode);
michael@0 1006 nsAutoString value;
michael@0 1007 // retrieve its specified value
michael@0 1008 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1009 mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(curNode, marginProperty, value);
michael@0 1010 float f;
michael@0 1011 nsCOMPtr<nsIAtom> unit;
michael@0 1012 // get its number part and its unit
michael@0 1013 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1014 mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit));
michael@0 1015 // if the number part is strictly positive, outdent is possible
michael@0 1016 if (0 < f) {
michael@0 1017 *aCanOutdent = true;
michael@0 1018 break;
michael@0 1019 }
michael@0 1020 }
michael@0 1021 }
michael@0 1022
michael@0 1023 if (!*aCanOutdent)
michael@0 1024 {
michael@0 1025 // if we haven't found something to outdent yet, also check the parents
michael@0 1026 // of selection endpoints. We might have a blockquote or list item
michael@0 1027 // in the parent hierarchy.
michael@0 1028
michael@0 1029 // gather up info we need for test
michael@0 1030 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1031 nsCOMPtr<nsIDOMNode> parent, tmp, root = do_QueryInterface(mHTMLEditor->GetRoot());
michael@0 1032 NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER);
michael@0 1033 nsCOMPtr<nsISelection> selection;
michael@0 1034 int32_t selOffset;
michael@0 1035 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1036 res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
michael@0 1037 NS_ENSURE_SUCCESS(res, res);
michael@0 1038 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
michael@0 1039
michael@0 1040 // test start parent hierarchy
michael@0 1041 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1042 res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(parent), &selOffset);
michael@0 1043 NS_ENSURE_SUCCESS(res, res);
michael@0 1044 while (parent && (parent!=root))
michael@0 1045 {
michael@0 1046 if (nsHTMLEditUtils::IsNodeThatCanOutdent(parent))
michael@0 1047 {
michael@0 1048 *aCanOutdent = true;
michael@0 1049 break;
michael@0 1050 }
michael@0 1051 tmp=parent;
michael@0 1052 tmp->GetParentNode(getter_AddRefs(parent));
michael@0 1053 }
michael@0 1054
michael@0 1055 // test end parent hierarchy
michael@0 1056 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1057 res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(parent), &selOffset);
michael@0 1058 NS_ENSURE_SUCCESS(res, res);
michael@0 1059 while (parent && (parent!=root))
michael@0 1060 {
michael@0 1061 if (nsHTMLEditUtils::IsNodeThatCanOutdent(parent))
michael@0 1062 {
michael@0 1063 *aCanOutdent = true;
michael@0 1064 break;
michael@0 1065 }
michael@0 1066 tmp=parent;
michael@0 1067 tmp->GetParentNode(getter_AddRefs(parent));
michael@0 1068 }
michael@0 1069 }
michael@0 1070 return res;
michael@0 1071 }
michael@0 1072
michael@0 1073
michael@0 1074 nsresult
michael@0 1075 nsHTMLEditRules::GetParagraphState(bool *aMixed, nsAString &outFormat)
michael@0 1076 {
michael@0 1077 // This routine is *heavily* tied to our ui choices in the paragraph
michael@0 1078 // style popup. I can't see a way around that.
michael@0 1079 NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
michael@0 1080 *aMixed = true;
michael@0 1081 outFormat.Truncate(0);
michael@0 1082
michael@0 1083 bool bMixed = false;
michael@0 1084 // using "x" as an uninitialized value, since "" is meaningful
michael@0 1085 nsAutoString formatStr(NS_LITERAL_STRING("x"));
michael@0 1086
michael@0 1087 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 1088 nsresult res = GetParagraphFormatNodes(arrayOfNodes, true);
michael@0 1089 NS_ENSURE_SUCCESS(res, res);
michael@0 1090
michael@0 1091 // post process list. We need to replace any block nodes that are not format
michael@0 1092 // nodes with their content. This is so we only have to look "up" the hierarchy
michael@0 1093 // to find format nodes, instead of both up and down.
michael@0 1094 int32_t listCount = arrayOfNodes.Count();
michael@0 1095 int32_t i;
michael@0 1096 for (i=listCount-1; i>=0; i--)
michael@0 1097 {
michael@0 1098 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
michael@0 1099 nsAutoString format;
michael@0 1100 // if it is a known format node we have it easy
michael@0 1101 if (IsBlockNode(curNode) && !nsHTMLEditUtils::IsFormatNode(curNode))
michael@0 1102 {
michael@0 1103 // arrayOfNodes.RemoveObject(curNode);
michael@0 1104 res = AppendInnerFormatNodes(arrayOfNodes, curNode);
michael@0 1105 NS_ENSURE_SUCCESS(res, res);
michael@0 1106 }
michael@0 1107 }
michael@0 1108
michael@0 1109 // we might have an empty node list. if so, find selection parent
michael@0 1110 // and put that on the list
michael@0 1111 listCount = arrayOfNodes.Count();
michael@0 1112 if (!listCount)
michael@0 1113 {
michael@0 1114 nsCOMPtr<nsIDOMNode> selNode;
michael@0 1115 int32_t selOffset;
michael@0 1116 nsCOMPtr<nsISelection>selection;
michael@0 1117 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1118 res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
michael@0 1119 NS_ENSURE_SUCCESS(res, res);
michael@0 1120 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1121 res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
michael@0 1122 NS_ENSURE_SUCCESS(res, res);
michael@0 1123 NS_ENSURE_TRUE(selNode, NS_ERROR_NULL_POINTER);
michael@0 1124 arrayOfNodes.AppendObject(selNode);
michael@0 1125 listCount = 1;
michael@0 1126 }
michael@0 1127
michael@0 1128 // remember root node
michael@0 1129 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1130 nsCOMPtr<nsIDOMElement> rootElem = do_QueryInterface(mHTMLEditor->GetRoot());
michael@0 1131 NS_ENSURE_TRUE(rootElem, NS_ERROR_NULL_POINTER);
michael@0 1132
michael@0 1133 // loop through the nodes in selection and examine their paragraph format
michael@0 1134 for (i=listCount-1; i>=0; i--)
michael@0 1135 {
michael@0 1136 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
michael@0 1137 nsAutoString format;
michael@0 1138 // if it is a known format node we have it easy
michael@0 1139 if (nsHTMLEditUtils::IsFormatNode(curNode))
michael@0 1140 GetFormatString(curNode, format);
michael@0 1141 else if (IsBlockNode(curNode))
michael@0 1142 {
michael@0 1143 // this is a div or some other non-format block.
michael@0 1144 // we should ignore it. Its children were appended to this list
michael@0 1145 // by AppendInnerFormatNodes() call above. We will get needed
michael@0 1146 // info when we examine them instead.
michael@0 1147 continue;
michael@0 1148 }
michael@0 1149 else
michael@0 1150 {
michael@0 1151 nsCOMPtr<nsIDOMNode> node, tmp = curNode;
michael@0 1152 tmp->GetParentNode(getter_AddRefs(node));
michael@0 1153 while (node)
michael@0 1154 {
michael@0 1155 if (node == rootElem)
michael@0 1156 {
michael@0 1157 format.Truncate(0);
michael@0 1158 break;
michael@0 1159 }
michael@0 1160 else if (nsHTMLEditUtils::IsFormatNode(node))
michael@0 1161 {
michael@0 1162 GetFormatString(node, format);
michael@0 1163 break;
michael@0 1164 }
michael@0 1165 // else keep looking up
michael@0 1166 tmp = node;
michael@0 1167 tmp->GetParentNode(getter_AddRefs(node));
michael@0 1168 }
michael@0 1169 }
michael@0 1170
michael@0 1171 // if this is the first node, we've found, remember it as the format
michael@0 1172 if (formatStr.EqualsLiteral("x"))
michael@0 1173 formatStr = format;
michael@0 1174 // else make sure it matches previously found format
michael@0 1175 else if (format != formatStr)
michael@0 1176 {
michael@0 1177 bMixed = true;
michael@0 1178 break;
michael@0 1179 }
michael@0 1180 }
michael@0 1181
michael@0 1182 *aMixed = bMixed;
michael@0 1183 outFormat = formatStr;
michael@0 1184 return res;
michael@0 1185 }
michael@0 1186
michael@0 1187 nsresult
michael@0 1188 nsHTMLEditRules::AppendInnerFormatNodes(nsCOMArray<nsIDOMNode>& aArray,
michael@0 1189 nsIDOMNode *aNode)
michael@0 1190 {
michael@0 1191 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
michael@0 1192 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
michael@0 1193
michael@0 1194 return AppendInnerFormatNodes(aArray, node);
michael@0 1195 }
michael@0 1196
michael@0 1197 nsresult
michael@0 1198 nsHTMLEditRules::AppendInnerFormatNodes(nsCOMArray<nsIDOMNode>& aArray,
michael@0 1199 nsINode* aNode)
michael@0 1200 {
michael@0 1201 MOZ_ASSERT(aNode);
michael@0 1202
michael@0 1203 // we only need to place any one inline inside this node onto
michael@0 1204 // the list. They are all the same for purposes of determining
michael@0 1205 // paragraph style. We use foundInline to track this as we are
michael@0 1206 // going through the children in the loop below.
michael@0 1207 bool foundInline = false;
michael@0 1208 for (nsIContent* child = aNode->GetFirstChild();
michael@0 1209 child;
michael@0 1210 child = child->GetNextSibling()) {
michael@0 1211 bool isBlock = IsBlockNode(child->AsDOMNode());
michael@0 1212 bool isFormat = nsHTMLEditUtils::IsFormatNode(child);
michael@0 1213 if (isBlock && !isFormat) {
michael@0 1214 // if it's a div, etc, recurse
michael@0 1215 AppendInnerFormatNodes(aArray, child);
michael@0 1216 } else if (isFormat) {
michael@0 1217 aArray.AppendObject(child->AsDOMNode());
michael@0 1218 } else if (!foundInline) {
michael@0 1219 // if this is the first inline we've found, use it
michael@0 1220 foundInline = true;
michael@0 1221 aArray.AppendObject(child->AsDOMNode());
michael@0 1222 }
michael@0 1223 }
michael@0 1224 return NS_OK;
michael@0 1225 }
michael@0 1226
michael@0 1227 nsresult
michael@0 1228 nsHTMLEditRules::GetFormatString(nsIDOMNode *aNode, nsAString &outFormat)
michael@0 1229 {
michael@0 1230 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
michael@0 1231
michael@0 1232 if (nsHTMLEditUtils::IsFormatNode(aNode))
michael@0 1233 {
michael@0 1234 nsCOMPtr<nsIAtom> atom = nsEditor::GetTag(aNode);
michael@0 1235 atom->ToString(outFormat);
michael@0 1236 }
michael@0 1237 else
michael@0 1238 outFormat.Truncate();
michael@0 1239
michael@0 1240 return NS_OK;
michael@0 1241 }
michael@0 1242
michael@0 1243 /********************************************************
michael@0 1244 * Protected rules methods
michael@0 1245 ********************************************************/
michael@0 1246
michael@0 1247 nsresult
michael@0 1248 nsHTMLEditRules::WillInsert(nsISelection *aSelection, bool *aCancel)
michael@0 1249 {
michael@0 1250 nsresult res = nsTextEditRules::WillInsert(aSelection, aCancel);
michael@0 1251 NS_ENSURE_SUCCESS(res, res);
michael@0 1252
michael@0 1253 // Adjust selection to prevent insertion after a moz-BR.
michael@0 1254 // this next only works for collapsed selections right now,
michael@0 1255 // because selection is a pain to work with when not collapsed.
michael@0 1256 // (no good way to extend start or end of selection), so we ignore
michael@0 1257 // those types of selections.
michael@0 1258 if (!aSelection->Collapsed()) {
michael@0 1259 return NS_OK;
michael@0 1260 }
michael@0 1261
michael@0 1262 // if we are after a mozBR in the same block, then move selection
michael@0 1263 // to be before it
michael@0 1264 nsCOMPtr<nsIDOMNode> selNode, priorNode;
michael@0 1265 int32_t selOffset;
michael@0 1266 // get the (collapsed) selection location
michael@0 1267 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1268 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode),
michael@0 1269 &selOffset);
michael@0 1270 NS_ENSURE_SUCCESS(res, res);
michael@0 1271 // get prior node
michael@0 1272 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1273 res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset,
michael@0 1274 address_of(priorNode));
michael@0 1275 if (NS_SUCCEEDED(res) && priorNode && nsTextEditUtils::IsMozBR(priorNode))
michael@0 1276 {
michael@0 1277 nsCOMPtr<nsIDOMNode> block1, block2;
michael@0 1278 if (IsBlockNode(selNode)) {
michael@0 1279 block1 = selNode;
michael@0 1280 }
michael@0 1281 else {
michael@0 1282 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1283 block1 = mHTMLEditor->GetBlockNodeParent(selNode);
michael@0 1284 }
michael@0 1285 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1286 block2 = mHTMLEditor->GetBlockNodeParent(priorNode);
michael@0 1287
michael@0 1288 if (block1 == block2)
michael@0 1289 {
michael@0 1290 // if we are here then the selection is right after a mozBR
michael@0 1291 // that is in the same block as the selection. We need to move
michael@0 1292 // the selection start to be before the mozBR.
michael@0 1293 selNode = nsEditor::GetNodeLocation(priorNode, &selOffset);
michael@0 1294 res = aSelection->Collapse(selNode,selOffset);
michael@0 1295 NS_ENSURE_SUCCESS(res, res);
michael@0 1296 }
michael@0 1297 }
michael@0 1298
michael@0 1299 if (mDidDeleteSelection &&
michael@0 1300 (mTheAction == EditAction::insertText ||
michael@0 1301 mTheAction == EditAction::insertIMEText ||
michael@0 1302 mTheAction == EditAction::deleteSelection)) {
michael@0 1303 res = ReapplyCachedStyles();
michael@0 1304 NS_ENSURE_SUCCESS(res, res);
michael@0 1305 }
michael@0 1306 // For most actions we want to clear the cached styles, but there are
michael@0 1307 // exceptions
michael@0 1308 if (!IsStyleCachePreservingAction(mTheAction)) {
michael@0 1309 ClearCachedStyles();
michael@0 1310 }
michael@0 1311
michael@0 1312 return NS_OK;
michael@0 1313 }
michael@0 1314
michael@0 1315 nsresult
michael@0 1316 nsHTMLEditRules::WillInsertText(EditAction aAction,
michael@0 1317 Selection* aSelection,
michael@0 1318 bool *aCancel,
michael@0 1319 bool *aHandled,
michael@0 1320 const nsAString *inString,
michael@0 1321 nsAString *outString,
michael@0 1322 int32_t aMaxLength)
michael@0 1323 {
michael@0 1324 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 1325
michael@0 1326 if (inString->IsEmpty() && aAction != EditAction::insertIMEText) {
michael@0 1327 // HACK: this is a fix for bug 19395
michael@0 1328 // I can't outlaw all empty insertions
michael@0 1329 // because IME transaction depend on them
michael@0 1330 // There is more work to do to make the
michael@0 1331 // world safe for IME.
michael@0 1332 *aCancel = true;
michael@0 1333 *aHandled = false;
michael@0 1334 return NS_OK;
michael@0 1335 }
michael@0 1336
michael@0 1337 // initialize out param
michael@0 1338 *aCancel = false;
michael@0 1339 *aHandled = true;
michael@0 1340 nsresult res;
michael@0 1341 nsCOMPtr<nsIDOMNode> selNode;
michael@0 1342 int32_t selOffset;
michael@0 1343
michael@0 1344 // If the selection isn't collapsed, delete it. Don't delete existing inline
michael@0 1345 // tags, because we're hopefully going to insert text (bug 787432).
michael@0 1346 if (!aSelection->Collapsed()) {
michael@0 1347 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1348 res = mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip);
michael@0 1349 NS_ENSURE_SUCCESS(res, res);
michael@0 1350 }
michael@0 1351
michael@0 1352 res = WillInsert(aSelection, aCancel);
michael@0 1353 NS_ENSURE_SUCCESS(res, res);
michael@0 1354 // initialize out param
michael@0 1355 // we want to ignore result of WillInsert()
michael@0 1356 *aCancel = false;
michael@0 1357
michael@0 1358 // we need to get the doc
michael@0 1359 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1360 nsCOMPtr<nsIDOMDocument> doc = mHTMLEditor->GetDOMDocument();
michael@0 1361 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
michael@0 1362
michael@0 1363 // for every property that is set, insert a new inline style node
michael@0 1364 res = CreateStyleForInsertText(aSelection, doc);
michael@0 1365 NS_ENSURE_SUCCESS(res, res);
michael@0 1366
michael@0 1367 // get the (collapsed) selection location
michael@0 1368 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1369 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
michael@0 1370 NS_ENSURE_SUCCESS(res, res);
michael@0 1371
michael@0 1372 // dont put text in places that can't have it
michael@0 1373 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1374 if (!mHTMLEditor->IsTextNode(selNode) &&
michael@0 1375 (!mHTMLEditor ||
michael@0 1376 !mHTMLEditor->CanContainTag(selNode, nsGkAtoms::textTagName))) {
michael@0 1377 return NS_ERROR_FAILURE;
michael@0 1378 }
michael@0 1379
michael@0 1380 if (aAction == EditAction::insertIMEText) {
michael@0 1381 // Right now the nsWSRunObject code bails on empty strings, but IME needs
michael@0 1382 // the InsertTextImpl() call to still happen since empty strings are meaningful there.
michael@0 1383 if (inString->IsEmpty())
michael@0 1384 {
michael@0 1385 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1386 res = mHTMLEditor->InsertTextImpl(*inString, address_of(selNode), &selOffset, doc);
michael@0 1387 }
michael@0 1388 else
michael@0 1389 {
michael@0 1390 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1391 nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset);
michael@0 1392 res = wsObj.InsertText(*inString, address_of(selNode), &selOffset, doc);
michael@0 1393 }
michael@0 1394 NS_ENSURE_SUCCESS(res, res);
michael@0 1395 }
michael@0 1396 else // aAction == kInsertText
michael@0 1397 {
michael@0 1398 // find where we are
michael@0 1399 nsCOMPtr<nsIDOMNode> curNode = selNode;
michael@0 1400 int32_t curOffset = selOffset;
michael@0 1401
michael@0 1402 // is our text going to be PREformatted?
michael@0 1403 // We remember this so that we know how to handle tabs.
michael@0 1404 bool isPRE;
michael@0 1405 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1406 res = mHTMLEditor->IsPreformatted(selNode, &isPRE);
michael@0 1407 NS_ENSURE_SUCCESS(res, res);
michael@0 1408
michael@0 1409 // turn off the edit listener: we know how to
michael@0 1410 // build the "doc changed range" ourselves, and it's
michael@0 1411 // must faster to do it once here than to track all
michael@0 1412 // the changes one at a time.
michael@0 1413 nsAutoLockListener lockit(&mListenerEnabled);
michael@0 1414
michael@0 1415 // don't spaz my selection in subtransactions
michael@0 1416 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1417 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
michael@0 1418 nsAutoString tString(*inString);
michael@0 1419 const char16_t *unicodeBuf = tString.get();
michael@0 1420 nsCOMPtr<nsIDOMNode> unused;
michael@0 1421 int32_t pos = 0;
michael@0 1422 NS_NAMED_LITERAL_STRING(newlineStr, LFSTR);
michael@0 1423
michael@0 1424 // for efficiency, break out the pre case separately. This is because
michael@0 1425 // its a lot cheaper to search the input string for only newlines than
michael@0 1426 // it is to search for both tabs and newlines.
michael@0 1427 if (isPRE || IsPlaintextEditor())
michael@0 1428 {
michael@0 1429 while (unicodeBuf && (pos != -1) && (pos < (int32_t)(*inString).Length()))
michael@0 1430 {
michael@0 1431 int32_t oldPos = pos;
michael@0 1432 int32_t subStrLen;
michael@0 1433 pos = tString.FindChar(nsCRT::LF, oldPos);
michael@0 1434
michael@0 1435 if (pos != -1)
michael@0 1436 {
michael@0 1437 subStrLen = pos - oldPos;
michael@0 1438 // if first char is newline, then use just it
michael@0 1439 if (subStrLen == 0)
michael@0 1440 subStrLen = 1;
michael@0 1441 }
michael@0 1442 else
michael@0 1443 {
michael@0 1444 subStrLen = tString.Length() - oldPos;
michael@0 1445 pos = tString.Length();
michael@0 1446 }
michael@0 1447
michael@0 1448 nsDependentSubstring subStr(tString, oldPos, subStrLen);
michael@0 1449
michael@0 1450 // is it a return?
michael@0 1451 if (subStr.Equals(newlineStr))
michael@0 1452 {
michael@0 1453 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1454 res = mHTMLEditor->CreateBRImpl(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
michael@0 1455 pos++;
michael@0 1456 }
michael@0 1457 else
michael@0 1458 {
michael@0 1459 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1460 res = mHTMLEditor->InsertTextImpl(subStr, address_of(curNode), &curOffset, doc);
michael@0 1461 }
michael@0 1462 NS_ENSURE_SUCCESS(res, res);
michael@0 1463 }
michael@0 1464 }
michael@0 1465 else
michael@0 1466 {
michael@0 1467 NS_NAMED_LITERAL_STRING(tabStr, "\t");
michael@0 1468 NS_NAMED_LITERAL_STRING(spacesStr, " ");
michael@0 1469 char specialChars[] = {TAB, nsCRT::LF, 0};
michael@0 1470 while (unicodeBuf && (pos != -1) && (pos < (int32_t)inString->Length()))
michael@0 1471 {
michael@0 1472 int32_t oldPos = pos;
michael@0 1473 int32_t subStrLen;
michael@0 1474 pos = tString.FindCharInSet(specialChars, oldPos);
michael@0 1475
michael@0 1476 if (pos != -1)
michael@0 1477 {
michael@0 1478 subStrLen = pos - oldPos;
michael@0 1479 // if first char is newline, then use just it
michael@0 1480 if (subStrLen == 0)
michael@0 1481 subStrLen = 1;
michael@0 1482 }
michael@0 1483 else
michael@0 1484 {
michael@0 1485 subStrLen = tString.Length() - oldPos;
michael@0 1486 pos = tString.Length();
michael@0 1487 }
michael@0 1488
michael@0 1489 nsDependentSubstring subStr(tString, oldPos, subStrLen);
michael@0 1490 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1491 nsWSRunObject wsObj(mHTMLEditor, curNode, curOffset);
michael@0 1492
michael@0 1493 // is it a tab?
michael@0 1494 if (subStr.Equals(tabStr))
michael@0 1495 {
michael@0 1496 res = wsObj.InsertText(spacesStr, address_of(curNode), &curOffset, doc);
michael@0 1497 NS_ENSURE_SUCCESS(res, res);
michael@0 1498 pos++;
michael@0 1499 }
michael@0 1500 // is it a return?
michael@0 1501 else if (subStr.Equals(newlineStr))
michael@0 1502 {
michael@0 1503 res = wsObj.InsertBreak(address_of(curNode), &curOffset, address_of(unused), nsIEditor::eNone);
michael@0 1504 NS_ENSURE_SUCCESS(res, res);
michael@0 1505 pos++;
michael@0 1506 }
michael@0 1507 else
michael@0 1508 {
michael@0 1509 res = wsObj.InsertText(subStr, address_of(curNode), &curOffset, doc);
michael@0 1510 NS_ENSURE_SUCCESS(res, res);
michael@0 1511 }
michael@0 1512 NS_ENSURE_SUCCESS(res, res);
michael@0 1513 }
michael@0 1514 }
michael@0 1515 nsCOMPtr<nsISelection> selection(aSelection);
michael@0 1516 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
michael@0 1517 selPriv->SetInterlinePosition(false);
michael@0 1518 if (curNode) aSelection->Collapse(curNode, curOffset);
michael@0 1519 // manually update the doc changed range so that AfterEdit will clean up
michael@0 1520 // the correct portion of the document.
michael@0 1521 if (!mDocChangeRange)
michael@0 1522 {
michael@0 1523 nsCOMPtr<nsINode> node = do_QueryInterface(selNode);
michael@0 1524 NS_ENSURE_STATE(node);
michael@0 1525 mDocChangeRange = new nsRange(node);
michael@0 1526 }
michael@0 1527 res = mDocChangeRange->SetStart(selNode, selOffset);
michael@0 1528 NS_ENSURE_SUCCESS(res, res);
michael@0 1529 if (curNode)
michael@0 1530 res = mDocChangeRange->SetEnd(curNode, curOffset);
michael@0 1531 else
michael@0 1532 res = mDocChangeRange->SetEnd(selNode, selOffset);
michael@0 1533 NS_ENSURE_SUCCESS(res, res);
michael@0 1534 }
michael@0 1535 return res;
michael@0 1536 }
michael@0 1537
michael@0 1538 nsresult
michael@0 1539 nsHTMLEditRules::WillLoadHTML(nsISelection *aSelection, bool *aCancel)
michael@0 1540 {
michael@0 1541 NS_ENSURE_TRUE(aSelection && aCancel, NS_ERROR_NULL_POINTER);
michael@0 1542
michael@0 1543 *aCancel = false;
michael@0 1544
michael@0 1545 // Delete mBogusNode if it exists. If we really need one,
michael@0 1546 // it will be added during post-processing in AfterEditInner().
michael@0 1547
michael@0 1548 if (mBogusNode)
michael@0 1549 {
michael@0 1550 mEditor->DeleteNode(mBogusNode);
michael@0 1551 mBogusNode = nullptr;
michael@0 1552 }
michael@0 1553
michael@0 1554 return NS_OK;
michael@0 1555 }
michael@0 1556
michael@0 1557 nsresult
michael@0 1558 nsHTMLEditRules::WillInsertBreak(Selection* aSelection,
michael@0 1559 bool* aCancel, bool* aHandled)
michael@0 1560 {
michael@0 1561 if (!aSelection || !aCancel || !aHandled) {
michael@0 1562 return NS_ERROR_NULL_POINTER;
michael@0 1563 }
michael@0 1564 // initialize out params
michael@0 1565 *aCancel = false;
michael@0 1566 *aHandled = false;
michael@0 1567
michael@0 1568 // if the selection isn't collapsed, delete it.
michael@0 1569 nsresult res = NS_OK;
michael@0 1570 if (!aSelection->Collapsed()) {
michael@0 1571 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1572 res = mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
michael@0 1573 NS_ENSURE_SUCCESS(res, res);
michael@0 1574 }
michael@0 1575
michael@0 1576 res = WillInsert(aSelection, aCancel);
michael@0 1577 NS_ENSURE_SUCCESS(res, res);
michael@0 1578
michael@0 1579 // initialize out param
michael@0 1580 // we want to ignore result of WillInsert()
michael@0 1581 *aCancel = false;
michael@0 1582
michael@0 1583 // split any mailcites in the way.
michael@0 1584 // should we abort this if we encounter table cell boundaries?
michael@0 1585 if (IsMailEditor()) {
michael@0 1586 res = SplitMailCites(aSelection, IsPlaintextEditor(), aHandled);
michael@0 1587 NS_ENSURE_SUCCESS(res, res);
michael@0 1588 if (*aHandled) {
michael@0 1589 return NS_OK;
michael@0 1590 }
michael@0 1591 }
michael@0 1592
michael@0 1593 // smart splitting rules
michael@0 1594 nsCOMPtr<nsIDOMNode> node;
michael@0 1595 int32_t offset;
michael@0 1596
michael@0 1597 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1598 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node),
michael@0 1599 &offset);
michael@0 1600 NS_ENSURE_SUCCESS(res, res);
michael@0 1601 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
michael@0 1602
michael@0 1603 // do nothing if the node is read-only
michael@0 1604 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1605 if (!mHTMLEditor->IsModifiableNode(node)) {
michael@0 1606 *aCancel = true;
michael@0 1607 return NS_OK;
michael@0 1608 }
michael@0 1609
michael@0 1610 // identify the block
michael@0 1611 nsCOMPtr<nsIDOMNode> blockParent;
michael@0 1612 if (IsBlockNode(node)) {
michael@0 1613 blockParent = node;
michael@0 1614 } else {
michael@0 1615 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1616 blockParent = mHTMLEditor->GetBlockNodeParent(node);
michael@0 1617 }
michael@0 1618 NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);
michael@0 1619
michael@0 1620 // if the active editing host is an inline element, or if the active editing
michael@0 1621 // host is the block parent itself, just append a br.
michael@0 1622 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1623 nsCOMPtr<nsIContent> hostContent = mHTMLEditor->GetActiveEditingHost();
michael@0 1624 nsCOMPtr<nsIDOMNode> hostNode = do_QueryInterface(hostContent);
michael@0 1625 if (!nsEditorUtils::IsDescendantOf(blockParent, hostNode)) {
michael@0 1626 res = StandardBreakImpl(node, offset, aSelection);
michael@0 1627 NS_ENSURE_SUCCESS(res, res);
michael@0 1628 *aHandled = true;
michael@0 1629 return NS_OK;
michael@0 1630 }
michael@0 1631
michael@0 1632 // if block is empty, populate with br. (for example, imagine a div that
michael@0 1633 // contains the word "text". the user selects "text" and types return.
michael@0 1634 // "text" is deleted leaving an empty block. we want to put in one br to
michael@0 1635 // make block have a line. then code further below will put in a second br.)
michael@0 1636 bool isEmpty;
michael@0 1637 IsEmptyBlock(blockParent, &isEmpty);
michael@0 1638 if (isEmpty) {
michael@0 1639 uint32_t blockLen;
michael@0 1640 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1641 res = mHTMLEditor->GetLengthOfDOMNode(blockParent, blockLen);
michael@0 1642 NS_ENSURE_SUCCESS(res, res);
michael@0 1643 nsCOMPtr<nsIDOMNode> brNode;
michael@0 1644 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1645 res = mHTMLEditor->CreateBR(blockParent, blockLen, address_of(brNode));
michael@0 1646 NS_ENSURE_SUCCESS(res, res);
michael@0 1647 }
michael@0 1648
michael@0 1649 nsCOMPtr<nsIDOMNode> listItem = IsInListItem(blockParent);
michael@0 1650 if (listItem && listItem != hostNode) {
michael@0 1651 ReturnInListItem(aSelection, listItem, node, offset);
michael@0 1652 *aHandled = true;
michael@0 1653 return NS_OK;
michael@0 1654 } else if (nsHTMLEditUtils::IsHeader(blockParent)) {
michael@0 1655 // headers: close (or split) header
michael@0 1656 ReturnInHeader(aSelection, blockParent, node, offset);
michael@0 1657 *aHandled = true;
michael@0 1658 return NS_OK;
michael@0 1659 } else if (nsHTMLEditUtils::IsParagraph(blockParent)) {
michael@0 1660 // paragraphs: special rules to look for <br>s
michael@0 1661 res = ReturnInParagraph(aSelection, blockParent, node, offset,
michael@0 1662 aCancel, aHandled);
michael@0 1663 NS_ENSURE_SUCCESS(res, res);
michael@0 1664 // fall through, we may not have handled it in ReturnInParagraph()
michael@0 1665 }
michael@0 1666
michael@0 1667 // if not already handled then do the standard thing
michael@0 1668 if (!(*aHandled)) {
michael@0 1669 *aHandled = true;
michael@0 1670 return StandardBreakImpl(node, offset, aSelection);
michael@0 1671 }
michael@0 1672 return NS_OK;
michael@0 1673 }
michael@0 1674
michael@0 1675 nsresult
michael@0 1676 nsHTMLEditRules::StandardBreakImpl(nsIDOMNode* aNode, int32_t aOffset,
michael@0 1677 nsISelection* aSelection)
michael@0 1678 {
michael@0 1679 nsCOMPtr<nsIDOMNode> brNode;
michael@0 1680 bool bAfterBlock = false;
michael@0 1681 bool bBeforeBlock = false;
michael@0 1682 nsresult res = NS_OK;
michael@0 1683 nsCOMPtr<nsIDOMNode> node(aNode);
michael@0 1684 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(aSelection));
michael@0 1685
michael@0 1686 if (IsPlaintextEditor()) {
michael@0 1687 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1688 res = mHTMLEditor->CreateBR(node, aOffset, address_of(brNode));
michael@0 1689 } else {
michael@0 1690 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1691 nsWSRunObject wsObj(mHTMLEditor, node, aOffset);
michael@0 1692 nsCOMPtr<nsIDOMNode> visNode, linkNode;
michael@0 1693 int32_t visOffset = 0, newOffset;
michael@0 1694 WSType wsType;
michael@0 1695 wsObj.PriorVisibleNode(node, aOffset, address_of(visNode),
michael@0 1696 &visOffset, &wsType);
michael@0 1697 if (wsType & WSType::block) {
michael@0 1698 bAfterBlock = true;
michael@0 1699 }
michael@0 1700 wsObj.NextVisibleNode(node, aOffset, address_of(visNode),
michael@0 1701 &visOffset, &wsType);
michael@0 1702 if (wsType & WSType::block) {
michael@0 1703 bBeforeBlock = true;
michael@0 1704 }
michael@0 1705 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1706 if (mHTMLEditor->IsInLink(node, address_of(linkNode))) {
michael@0 1707 // split the link
michael@0 1708 nsCOMPtr<nsIDOMNode> linkParent;
michael@0 1709 res = linkNode->GetParentNode(getter_AddRefs(linkParent));
michael@0 1710 NS_ENSURE_SUCCESS(res, res);
michael@0 1711 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1712 res = mHTMLEditor->SplitNodeDeep(linkNode, node, aOffset,
michael@0 1713 &newOffset, true);
michael@0 1714 NS_ENSURE_SUCCESS(res, res);
michael@0 1715 // reset {node,aOffset} to the point where link was split
michael@0 1716 node = linkParent;
michael@0 1717 aOffset = newOffset;
michael@0 1718 }
michael@0 1719 res = wsObj.InsertBreak(address_of(node), &aOffset,
michael@0 1720 address_of(brNode), nsIEditor::eNone);
michael@0 1721 }
michael@0 1722 NS_ENSURE_SUCCESS(res, res);
michael@0 1723 node = nsEditor::GetNodeLocation(brNode, &aOffset);
michael@0 1724 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
michael@0 1725 if (bAfterBlock && bBeforeBlock) {
michael@0 1726 // we just placed a br between block boundaries. This is the one case
michael@0 1727 // where we want the selection to be before the br we just placed, as the
michael@0 1728 // br will be on a new line, rather than at end of prior line.
michael@0 1729 selPriv->SetInterlinePosition(true);
michael@0 1730 res = aSelection->Collapse(node, aOffset);
michael@0 1731 } else {
michael@0 1732 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1733 nsWSRunObject wsObj(mHTMLEditor, node, aOffset+1);
michael@0 1734 nsCOMPtr<nsIDOMNode> secondBR;
michael@0 1735 int32_t visOffset = 0;
michael@0 1736 WSType wsType;
michael@0 1737 wsObj.NextVisibleNode(node, aOffset+1, address_of(secondBR),
michael@0 1738 &visOffset, &wsType);
michael@0 1739 if (wsType == WSType::br) {
michael@0 1740 // the next thing after the break we inserted is another break. Move
michael@0 1741 // the 2nd break to be the first breaks sibling. This will prevent them
michael@0 1742 // from being in different inline nodes, which would break
michael@0 1743 // SetInterlinePosition(). It will also assure that if the user clicks
michael@0 1744 // away and then clicks back on their new blank line, they will still
michael@0 1745 // get the style from the line above.
michael@0 1746 int32_t brOffset;
michael@0 1747 nsCOMPtr<nsIDOMNode> brParent = nsEditor::GetNodeLocation(secondBR, &brOffset);
michael@0 1748 if (brParent != node || brOffset != aOffset + 1) {
michael@0 1749 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1750 res = mHTMLEditor->MoveNode(secondBR, node, aOffset+1);
michael@0 1751 NS_ENSURE_SUCCESS(res, res);
michael@0 1752 }
michael@0 1753 }
michael@0 1754 // SetInterlinePosition(true) means we want the caret to stick to the
michael@0 1755 // content on the "right". We want the caret to stick to whatever is past
michael@0 1756 // the break. This is because the break is on the same line we were on,
michael@0 1757 // but the next content will be on the following line.
michael@0 1758
michael@0 1759 // An exception to this is if the break has a next sibling that is a block
michael@0 1760 // node. Then we stick to the left to avoid an uber caret.
michael@0 1761 nsCOMPtr<nsIDOMNode> siblingNode;
michael@0 1762 brNode->GetNextSibling(getter_AddRefs(siblingNode));
michael@0 1763 if (siblingNode && IsBlockNode(siblingNode)) {
michael@0 1764 selPriv->SetInterlinePosition(false);
michael@0 1765 } else {
michael@0 1766 selPriv->SetInterlinePosition(true);
michael@0 1767 }
michael@0 1768 res = aSelection->Collapse(node, aOffset+1);
michael@0 1769 }
michael@0 1770 return res;
michael@0 1771 }
michael@0 1772
michael@0 1773 nsresult
michael@0 1774 nsHTMLEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult)
michael@0 1775 {
michael@0 1776 return NS_OK;
michael@0 1777 }
michael@0 1778
michael@0 1779
michael@0 1780 nsresult
michael@0 1781 nsHTMLEditRules::SplitMailCites(nsISelection *aSelection, bool aPlaintext, bool *aHandled)
michael@0 1782 {
michael@0 1783 NS_ENSURE_TRUE(aSelection && aHandled, NS_ERROR_NULL_POINTER);
michael@0 1784 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(aSelection));
michael@0 1785 nsCOMPtr<nsIDOMNode> citeNode, selNode, leftCite, rightCite;
michael@0 1786 int32_t selOffset, newOffset;
michael@0 1787 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1788 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
michael@0 1789 NS_ENSURE_SUCCESS(res, res);
michael@0 1790 res = GetTopEnclosingMailCite(selNode, address_of(citeNode), aPlaintext);
michael@0 1791 NS_ENSURE_SUCCESS(res, res);
michael@0 1792 if (citeNode)
michael@0 1793 {
michael@0 1794 // If our selection is just before a break, nudge it to be
michael@0 1795 // just after it. This does two things for us. It saves us the trouble of having to add
michael@0 1796 // a break here ourselves to preserve the "blockness" of the inline span mailquote
michael@0 1797 // (in the inline case), and :
michael@0 1798 // it means the break won't end up making an empty line that happens to be inside a
michael@0 1799 // mailquote (in either inline or block case).
michael@0 1800 // The latter can confuse a user if they click there and start typing,
michael@0 1801 // because being in the mailquote may affect wrapping behavior, or font color, etc.
michael@0 1802 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1803 nsWSRunObject wsObj(mHTMLEditor, selNode, selOffset);
michael@0 1804 nsCOMPtr<nsIDOMNode> visNode;
michael@0 1805 int32_t visOffset=0;
michael@0 1806 WSType wsType;
michael@0 1807 wsObj.NextVisibleNode(selNode, selOffset, address_of(visNode),
michael@0 1808 &visOffset, &wsType);
michael@0 1809 if (wsType == WSType::br) {
michael@0 1810 // ok, we are just before a break. is it inside the mailquote?
michael@0 1811 int32_t unused;
michael@0 1812 if (nsEditorUtils::IsDescendantOf(visNode, citeNode, &unused))
michael@0 1813 {
michael@0 1814 // it is. so lets reset our selection to be just after it.
michael@0 1815 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1816 selNode = mHTMLEditor->GetNodeLocation(visNode, &selOffset);
michael@0 1817 ++selOffset;
michael@0 1818 }
michael@0 1819 }
michael@0 1820
michael@0 1821 nsCOMPtr<nsIDOMNode> brNode;
michael@0 1822 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1823 res = mHTMLEditor->SplitNodeDeep(citeNode, selNode, selOffset, &newOffset,
michael@0 1824 true, address_of(leftCite), address_of(rightCite));
michael@0 1825 NS_ENSURE_SUCCESS(res, res);
michael@0 1826 res = citeNode->GetParentNode(getter_AddRefs(selNode));
michael@0 1827 NS_ENSURE_SUCCESS(res, res);
michael@0 1828 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1829 res = mHTMLEditor->CreateBR(selNode, newOffset, address_of(brNode));
michael@0 1830 NS_ENSURE_SUCCESS(res, res);
michael@0 1831 // want selection before the break, and on same line
michael@0 1832 selPriv->SetInterlinePosition(true);
michael@0 1833 res = aSelection->Collapse(selNode, newOffset);
michael@0 1834 NS_ENSURE_SUCCESS(res, res);
michael@0 1835 // if citeNode wasn't a block, we might also want another break before it.
michael@0 1836 // We need to examine the content both before the br we just added and also
michael@0 1837 // just after it. If we don't have another br or block boundary adjacent,
michael@0 1838 // then we will need a 2nd br added to achieve blank line that user expects.
michael@0 1839 if (IsInlineNode(citeNode))
michael@0 1840 {
michael@0 1841 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1842 nsWSRunObject wsObj(mHTMLEditor, selNode, newOffset);
michael@0 1843 nsCOMPtr<nsIDOMNode> visNode;
michael@0 1844 int32_t visOffset=0;
michael@0 1845 WSType wsType;
michael@0 1846 wsObj.PriorVisibleNode(selNode, newOffset, address_of(visNode),
michael@0 1847 &visOffset, &wsType);
michael@0 1848 if (wsType == WSType::normalWS || wsType == WSType::text ||
michael@0 1849 wsType == WSType::special) {
michael@0 1850 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1851 nsWSRunObject wsObjAfterBR(mHTMLEditor, selNode, newOffset+1);
michael@0 1852 wsObjAfterBR.NextVisibleNode(selNode, newOffset+1, address_of(visNode),
michael@0 1853 &visOffset, &wsType);
michael@0 1854 if (wsType == WSType::normalWS || wsType == WSType::text ||
michael@0 1855 wsType == WSType::special) {
michael@0 1856 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1857 res = mHTMLEditor->CreateBR(selNode, newOffset, address_of(brNode));
michael@0 1858 NS_ENSURE_SUCCESS(res, res);
michael@0 1859 }
michael@0 1860 }
michael@0 1861 }
michael@0 1862 // delete any empty cites
michael@0 1863 bool bEmptyCite = false;
michael@0 1864 if (leftCite)
michael@0 1865 {
michael@0 1866 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1867 res = mHTMLEditor->IsEmptyNode(leftCite, &bEmptyCite, true, false);
michael@0 1868 if (NS_SUCCEEDED(res) && bEmptyCite) {
michael@0 1869 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1870 res = mHTMLEditor->DeleteNode(leftCite);
michael@0 1871 }
michael@0 1872 NS_ENSURE_SUCCESS(res, res);
michael@0 1873 }
michael@0 1874 if (rightCite)
michael@0 1875 {
michael@0 1876 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1877 res = mHTMLEditor->IsEmptyNode(rightCite, &bEmptyCite, true, false);
michael@0 1878 if (NS_SUCCEEDED(res) && bEmptyCite) {
michael@0 1879 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1880 res = mHTMLEditor->DeleteNode(rightCite);
michael@0 1881 }
michael@0 1882 NS_ENSURE_SUCCESS(res, res);
michael@0 1883 }
michael@0 1884 *aHandled = true;
michael@0 1885 }
michael@0 1886 return NS_OK;
michael@0 1887 }
michael@0 1888
michael@0 1889
michael@0 1890 nsresult
michael@0 1891 nsHTMLEditRules::WillDeleteSelection(Selection* aSelection,
michael@0 1892 nsIEditor::EDirection aAction,
michael@0 1893 nsIEditor::EStripWrappers aStripWrappers,
michael@0 1894 bool* aCancel,
michael@0 1895 bool* aHandled)
michael@0 1896 {
michael@0 1897 MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip ||
michael@0 1898 aStripWrappers == nsIEditor::eNoStrip);
michael@0 1899
michael@0 1900 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 1901 // initialize out param
michael@0 1902 *aCancel = false;
michael@0 1903 *aHandled = false;
michael@0 1904
michael@0 1905 // remember that we did a selection deletion. Used by CreateStyleForInsertText()
michael@0 1906 mDidDeleteSelection = true;
michael@0 1907
michael@0 1908 // if there is only bogus content, cancel the operation
michael@0 1909 if (mBogusNode)
michael@0 1910 {
michael@0 1911 *aCancel = true;
michael@0 1912 return NS_OK;
michael@0 1913 }
michael@0 1914
michael@0 1915 bool bCollapsed = aSelection->Collapsed(), join = false;
michael@0 1916
michael@0 1917 // origCollapsed is used later to determine whether we should join
michael@0 1918 // blocks. We don't really care about bCollapsed because it will be
michael@0 1919 // modified by ExtendSelectionForDelete later. JoinBlocks should
michael@0 1920 // happen if the original selection is collapsed and the cursor is
michael@0 1921 // at the end of a block element, in which case ExtendSelectionForDelete
michael@0 1922 // would always make the selection not collapsed.
michael@0 1923 bool origCollapsed = bCollapsed;
michael@0 1924 nsCOMPtr<nsIDOMNode> startNode, selNode;
michael@0 1925 int32_t startOffset, selOffset;
michael@0 1926
michael@0 1927 // first check for table selection mode. If so,
michael@0 1928 // hand off to table editor.
michael@0 1929 nsCOMPtr<nsIDOMElement> cell;
michael@0 1930 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1931 nsresult res = mHTMLEditor->GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
michael@0 1932 if (NS_SUCCEEDED(res) && cell) {
michael@0 1933 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1934 res = mHTMLEditor->DeleteTableCellContents();
michael@0 1935 *aHandled = true;
michael@0 1936 return res;
michael@0 1937 }
michael@0 1938 cell = nullptr;
michael@0 1939
michael@0 1940 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1941 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
michael@0 1942 NS_ENSURE_SUCCESS(res, res);
michael@0 1943 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
michael@0 1944
michael@0 1945 if (bCollapsed)
michael@0 1946 {
michael@0 1947 // if we are inside an empty block, delete it.
michael@0 1948 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1949 nsCOMPtr<nsIContent> hostContent = mHTMLEditor->GetActiveEditingHost();
michael@0 1950 nsCOMPtr<nsIDOMNode> hostNode = do_QueryInterface(hostContent);
michael@0 1951 NS_ENSURE_TRUE(hostNode, NS_ERROR_FAILURE);
michael@0 1952 res = CheckForEmptyBlock(startNode, hostNode, aSelection, aHandled);
michael@0 1953 NS_ENSURE_SUCCESS(res, res);
michael@0 1954 if (*aHandled) return NS_OK;
michael@0 1955
michael@0 1956 // Test for distance between caret and text that will be deleted
michael@0 1957 res = CheckBidiLevelForDeletion(aSelection, startNode, startOffset, aAction, aCancel);
michael@0 1958 NS_ENSURE_SUCCESS(res, res);
michael@0 1959 if (*aCancel) return NS_OK;
michael@0 1960
michael@0 1961 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1962 res = mHTMLEditor->ExtendSelectionForDelete(aSelection, &aAction);
michael@0 1963 NS_ENSURE_SUCCESS(res, res);
michael@0 1964
michael@0 1965 // We should delete nothing.
michael@0 1966 if (aAction == nsIEditor::eNone)
michael@0 1967 return NS_OK;
michael@0 1968
michael@0 1969 // ExtendSelectionForDelete() may have changed the selection, update it
michael@0 1970 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1971 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
michael@0 1972 NS_ENSURE_SUCCESS(res, res);
michael@0 1973 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
michael@0 1974
michael@0 1975 bCollapsed = aSelection->Collapsed();
michael@0 1976 }
michael@0 1977
michael@0 1978 if (bCollapsed)
michael@0 1979 {
michael@0 1980 // what's in the direction we are deleting?
michael@0 1981 NS_ENSURE_STATE(mHTMLEditor);
michael@0 1982 nsWSRunObject wsObj(mHTMLEditor, startNode, startOffset);
michael@0 1983 nsCOMPtr<nsIDOMNode> visNode;
michael@0 1984 int32_t visOffset;
michael@0 1985 WSType wsType;
michael@0 1986
michael@0 1987 // find next visible node
michael@0 1988 if (aAction == nsIEditor::eNext)
michael@0 1989 wsObj.NextVisibleNode(startNode, startOffset, address_of(visNode),
michael@0 1990 &visOffset, &wsType);
michael@0 1991 else
michael@0 1992 wsObj.PriorVisibleNode(startNode, startOffset, address_of(visNode),
michael@0 1993 &visOffset, &wsType);
michael@0 1994
michael@0 1995 if (!visNode) // can't find anything to delete!
michael@0 1996 {
michael@0 1997 *aCancel = true;
michael@0 1998 return res;
michael@0 1999 }
michael@0 2000
michael@0 2001 if (wsType == WSType::normalWS) {
michael@0 2002 // we found some visible ws to delete. Let ws code handle it.
michael@0 2003 if (aAction == nsIEditor::eNext)
michael@0 2004 res = wsObj.DeleteWSForward();
michael@0 2005 else
michael@0 2006 res = wsObj.DeleteWSBackward();
michael@0 2007 *aHandled = true;
michael@0 2008 NS_ENSURE_SUCCESS(res, res);
michael@0 2009 res = InsertBRIfNeeded(aSelection);
michael@0 2010 return res;
michael@0 2011 } else if (wsType == WSType::text) {
michael@0 2012 // found normal text to delete.
michael@0 2013 int32_t so = visOffset;
michael@0 2014 int32_t eo = visOffset+1;
michael@0 2015 if (aAction == nsIEditor::ePrevious)
michael@0 2016 {
michael@0 2017 if (so == 0) return NS_ERROR_UNEXPECTED;
michael@0 2018 so--;
michael@0 2019 eo--;
michael@0 2020 }
michael@0 2021 else
michael@0 2022 {
michael@0 2023 nsCOMPtr<nsIDOMRange> range;
michael@0 2024 res = aSelection->GetRangeAt(0, getter_AddRefs(range));
michael@0 2025 NS_ENSURE_SUCCESS(res, res);
michael@0 2026
michael@0 2027 #ifdef DEBUG
michael@0 2028 nsIDOMNode *container;
michael@0 2029
michael@0 2030 res = range->GetStartContainer(&container);
michael@0 2031 NS_ENSURE_SUCCESS(res, res);
michael@0 2032 NS_ASSERTION(container == visNode, "selection start not in visNode");
michael@0 2033
michael@0 2034 res = range->GetEndContainer(&container);
michael@0 2035 NS_ENSURE_SUCCESS(res, res);
michael@0 2036 NS_ASSERTION(container == visNode, "selection end not in visNode");
michael@0 2037 #endif
michael@0 2038
michael@0 2039 res = range->GetStartOffset(&so);
michael@0 2040 NS_ENSURE_SUCCESS(res, res);
michael@0 2041 res = range->GetEndOffset(&eo);
michael@0 2042 NS_ENSURE_SUCCESS(res, res);
michael@0 2043 }
michael@0 2044 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2045 res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(visNode), &so, address_of(visNode), &eo);
michael@0 2046 NS_ENSURE_SUCCESS(res, res);
michael@0 2047 nsCOMPtr<nsIDOMCharacterData> nodeAsText(do_QueryInterface(visNode));
michael@0 2048 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2049 res = mHTMLEditor->DeleteText(nodeAsText, std::min(so, eo), DeprecatedAbs(eo - so));
michael@0 2050 *aHandled = true;
michael@0 2051 NS_ENSURE_SUCCESS(res, res);
michael@0 2052 res = InsertBRIfNeeded(aSelection);
michael@0 2053 return res;
michael@0 2054 } else if (wsType == WSType::special || wsType == WSType::br ||
michael@0 2055 nsHTMLEditUtils::IsHR(visNode)) {
michael@0 2056 // short circuit for invisible breaks. delete them and recurse.
michael@0 2057 if (nsTextEditUtils::IsBreak(visNode) &&
michael@0 2058 (!mHTMLEditor || !mHTMLEditor->IsVisBreak(visNode)))
michael@0 2059 {
michael@0 2060 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2061 res = mHTMLEditor->DeleteNode(visNode);
michael@0 2062 NS_ENSURE_SUCCESS(res, res);
michael@0 2063 return WillDeleteSelection(aSelection, aAction, aStripWrappers,
michael@0 2064 aCancel, aHandled);
michael@0 2065 }
michael@0 2066
michael@0 2067 // special handling for backspace when positioned after <hr>
michael@0 2068 if (aAction == nsIEditor::ePrevious && nsHTMLEditUtils::IsHR(visNode))
michael@0 2069 {
michael@0 2070 /*
michael@0 2071 Only if the caret is positioned at the end-of-hr-line position,
michael@0 2072 we want to delete the <hr>.
michael@0 2073
michael@0 2074 In other words, we only want to delete, if
michael@0 2075 our selection position (indicated by startNode and startOffset)
michael@0 2076 is the position directly after the <hr>,
michael@0 2077 on the same line as the <hr>.
michael@0 2078
michael@0 2079 To detect this case we check:
michael@0 2080 startNode == parentOfVisNode
michael@0 2081 and
michael@0 2082 startOffset -1 == visNodeOffsetToVisNodeParent
michael@0 2083 and
michael@0 2084 interline position is false (left)
michael@0 2085
michael@0 2086 In any other case we set the position to
michael@0 2087 startnode -1 and interlineposition to false,
michael@0 2088 only moving the caret to the end-of-hr-line position.
michael@0 2089 */
michael@0 2090
michael@0 2091 bool moveOnly = true;
michael@0 2092
michael@0 2093 selNode = nsEditor::GetNodeLocation(visNode, &selOffset);
michael@0 2094
michael@0 2095 bool interLineIsRight;
michael@0 2096 res = aSelection->GetInterlinePosition(&interLineIsRight);
michael@0 2097 NS_ENSURE_SUCCESS(res, res);
michael@0 2098
michael@0 2099 if (startNode == selNode &&
michael@0 2100 startOffset -1 == selOffset &&
michael@0 2101 !interLineIsRight)
michael@0 2102 {
michael@0 2103 moveOnly = false;
michael@0 2104 }
michael@0 2105
michael@0 2106 if (moveOnly)
michael@0 2107 {
michael@0 2108 // Go to the position after the <hr>, but to the end of the <hr> line
michael@0 2109 // by setting the interline position to left.
michael@0 2110 ++selOffset;
michael@0 2111 res = aSelection->Collapse(selNode, selOffset);
michael@0 2112 aSelection->SetInterlinePosition(false);
michael@0 2113 mDidExplicitlySetInterline = true;
michael@0 2114 *aHandled = true;
michael@0 2115
michael@0 2116 // There is one exception to the move only case.
michael@0 2117 // If the <hr> is followed by a <br> we want to delete the <br>.
michael@0 2118
michael@0 2119 WSType otherWSType;
michael@0 2120 nsCOMPtr<nsIDOMNode> otherNode;
michael@0 2121 int32_t otherOffset;
michael@0 2122
michael@0 2123 wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode),
michael@0 2124 &otherOffset, &otherWSType);
michael@0 2125
michael@0 2126 if (otherWSType == WSType::br) {
michael@0 2127 // Delete the <br>
michael@0 2128
michael@0 2129 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2130 res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, otherNode);
michael@0 2131 NS_ENSURE_SUCCESS(res, res);
michael@0 2132 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2133 res = mHTMLEditor->DeleteNode(otherNode);
michael@0 2134 NS_ENSURE_SUCCESS(res, res);
michael@0 2135 }
michael@0 2136
michael@0 2137 return NS_OK;
michael@0 2138 }
michael@0 2139 // else continue with normal delete code
michael@0 2140 }
michael@0 2141
michael@0 2142 // found break or image, or hr.
michael@0 2143 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2144 res = nsWSRunObject::PrepareToDeleteNode(mHTMLEditor, visNode);
michael@0 2145 NS_ENSURE_SUCCESS(res, res);
michael@0 2146 // remember sibling to visnode, if any
michael@0 2147 nsCOMPtr<nsIDOMNode> sibling, stepbrother;
michael@0 2148 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2149 mHTMLEditor->GetPriorHTMLSibling(visNode, address_of(sibling));
michael@0 2150 // delete the node, and join like nodes if appropriate
michael@0 2151 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2152 res = mHTMLEditor->DeleteNode(visNode);
michael@0 2153 NS_ENSURE_SUCCESS(res, res);
michael@0 2154 // we did something, so lets say so.
michael@0 2155 *aHandled = true;
michael@0 2156 // is there a prior node and are they siblings?
michael@0 2157 if (sibling) {
michael@0 2158 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2159 mHTMLEditor->GetNextHTMLSibling(sibling, address_of(stepbrother));
michael@0 2160 }
michael@0 2161 if (startNode == stepbrother)
michael@0 2162 {
michael@0 2163 // are they both text nodes?
michael@0 2164 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2165 if (mHTMLEditor->IsTextNode(startNode) &&
michael@0 2166 (!mHTMLEditor || mHTMLEditor->IsTextNode(sibling)))
michael@0 2167 {
michael@0 2168 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2169 // if so, join them!
michael@0 2170 res = JoinNodesSmart(sibling, startNode, address_of(selNode), &selOffset);
michael@0 2171 NS_ENSURE_SUCCESS(res, res);
michael@0 2172 // fix up selection
michael@0 2173 res = aSelection->Collapse(selNode, selOffset);
michael@0 2174 }
michael@0 2175 }
michael@0 2176 NS_ENSURE_SUCCESS(res, res);
michael@0 2177 res = InsertBRIfNeeded(aSelection);
michael@0 2178 return res;
michael@0 2179 } else if (wsType == WSType::otherBlock) {
michael@0 2180 // make sure it's not a table element. If so, cancel the operation
michael@0 2181 // (translation: users cannot backspace or delete across table cells)
michael@0 2182 if (nsHTMLEditUtils::IsTableElement(visNode))
michael@0 2183 {
michael@0 2184 *aCancel = true;
michael@0 2185 return NS_OK;
michael@0 2186 }
michael@0 2187
michael@0 2188 // next to a block. See if we are between a block and a br. If so, we really
michael@0 2189 // want to delete the br. Else join content at selection to the block.
michael@0 2190
michael@0 2191 bool bDeletedBR = false;
michael@0 2192 WSType otherWSType;
michael@0 2193 nsCOMPtr<nsIDOMNode> otherNode;
michael@0 2194 int32_t otherOffset;
michael@0 2195
michael@0 2196 // find node in other direction
michael@0 2197 if (aAction == nsIEditor::eNext)
michael@0 2198 wsObj.PriorVisibleNode(startNode, startOffset, address_of(otherNode),
michael@0 2199 &otherOffset, &otherWSType);
michael@0 2200 else
michael@0 2201 wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode),
michael@0 2202 &otherOffset, &otherWSType);
michael@0 2203
michael@0 2204 // first find the adjacent node in the block
michael@0 2205 nsCOMPtr<nsIDOMNode> leafNode, leftNode, rightNode;
michael@0 2206 if (aAction == nsIEditor::ePrevious)
michael@0 2207 {
michael@0 2208 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2209 res = mHTMLEditor->GetLastEditableLeaf( visNode, address_of(leafNode));
michael@0 2210 NS_ENSURE_SUCCESS(res, res);
michael@0 2211 leftNode = leafNode;
michael@0 2212 rightNode = startNode;
michael@0 2213 }
michael@0 2214 else
michael@0 2215 {
michael@0 2216 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2217 res = mHTMLEditor->GetFirstEditableLeaf( visNode, address_of(leafNode));
michael@0 2218 NS_ENSURE_SUCCESS(res, res);
michael@0 2219 leftNode = startNode;
michael@0 2220 rightNode = leafNode;
michael@0 2221 }
michael@0 2222
michael@0 2223 if (nsTextEditUtils::IsBreak(otherNode))
michael@0 2224 {
michael@0 2225 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2226 res = mHTMLEditor->DeleteNode(otherNode);
michael@0 2227 NS_ENSURE_SUCCESS(res, res);
michael@0 2228 *aHandled = true;
michael@0 2229 bDeletedBR = true;
michael@0 2230 }
michael@0 2231
michael@0 2232 // don't cross table boundaries
michael@0 2233 if (leftNode && rightNode && InDifferentTableElements(leftNode, rightNode)) {
michael@0 2234 return NS_OK;
michael@0 2235 }
michael@0 2236
michael@0 2237 if (bDeletedBR)
michael@0 2238 {
michael@0 2239 // put selection at edge of block and we are done.
michael@0 2240 nsCOMPtr<nsIDOMNode> newSelNode;
michael@0 2241 int32_t newSelOffset;
michael@0 2242 res = GetGoodSelPointForNode(leafNode, aAction, address_of(newSelNode), &newSelOffset);
michael@0 2243 NS_ENSURE_SUCCESS(res, res);
michael@0 2244 aSelection->Collapse(newSelNode, newSelOffset);
michael@0 2245 return res;
michael@0 2246 }
michael@0 2247
michael@0 2248 // else we are joining content to block
michael@0 2249
michael@0 2250 nsCOMPtr<nsIDOMNode> selPointNode = startNode;
michael@0 2251 int32_t selPointOffset = startOffset;
michael@0 2252 {
michael@0 2253 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2254 nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset);
michael@0 2255 res = JoinBlocks(leftNode, rightNode, aCancel);
michael@0 2256 *aHandled = true;
michael@0 2257 NS_ENSURE_SUCCESS(res, res);
michael@0 2258 }
michael@0 2259 aSelection->Collapse(selPointNode, selPointOffset);
michael@0 2260 return res;
michael@0 2261 } else if (wsType == WSType::thisBlock) {
michael@0 2262 // at edge of our block. Look beside it and see if we can join to an adjacent block
michael@0 2263
michael@0 2264 // make sure it's not a table element. If so, cancel the operation
michael@0 2265 // (translation: users cannot backspace or delete across table cells)
michael@0 2266 if (nsHTMLEditUtils::IsTableElement(visNode))
michael@0 2267 {
michael@0 2268 *aCancel = true;
michael@0 2269 return NS_OK;
michael@0 2270 }
michael@0 2271
michael@0 2272 // first find the relavent nodes
michael@0 2273 nsCOMPtr<nsIDOMNode> leftNode, rightNode;
michael@0 2274 if (aAction == nsIEditor::ePrevious)
michael@0 2275 {
michael@0 2276 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2277 res = mHTMLEditor->GetPriorHTMLNode(visNode, address_of(leftNode));
michael@0 2278 NS_ENSURE_SUCCESS(res, res);
michael@0 2279 rightNode = startNode;
michael@0 2280 }
michael@0 2281 else
michael@0 2282 {
michael@0 2283 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2284 res = mHTMLEditor->GetNextHTMLNode( visNode, address_of(rightNode));
michael@0 2285 NS_ENSURE_SUCCESS(res, res);
michael@0 2286 leftNode = startNode;
michael@0 2287 }
michael@0 2288
michael@0 2289 // nothing to join
michael@0 2290 if (!leftNode || !rightNode)
michael@0 2291 {
michael@0 2292 *aCancel = true;
michael@0 2293 return NS_OK;
michael@0 2294 }
michael@0 2295
michael@0 2296 // don't cross table boundaries -- cancel it
michael@0 2297 if (InDifferentTableElements(leftNode, rightNode)) {
michael@0 2298 *aCancel = true;
michael@0 2299 return NS_OK;
michael@0 2300 }
michael@0 2301
michael@0 2302 nsCOMPtr<nsIDOMNode> selPointNode = startNode;
michael@0 2303 int32_t selPointOffset = startOffset;
michael@0 2304 {
michael@0 2305 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2306 nsAutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, address_of(selPointNode), &selPointOffset);
michael@0 2307 res = JoinBlocks(leftNode, rightNode, aCancel);
michael@0 2308 *aHandled = true;
michael@0 2309 NS_ENSURE_SUCCESS(res, res);
michael@0 2310 }
michael@0 2311 aSelection->Collapse(selPointNode, selPointOffset);
michael@0 2312 return res;
michael@0 2313 }
michael@0 2314 }
michael@0 2315
michael@0 2316
michael@0 2317 // else we have a non collapsed selection
michael@0 2318 // first adjust the selection
michael@0 2319 res = ExpandSelectionForDeletion(aSelection);
michael@0 2320 NS_ENSURE_SUCCESS(res, res);
michael@0 2321
michael@0 2322 // remember that we did a ranged delete for the benefit of AfterEditInner().
michael@0 2323 mDidRangedDelete = true;
michael@0 2324
michael@0 2325 // refresh start and end points
michael@0 2326 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2327 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
michael@0 2328 NS_ENSURE_SUCCESS(res, res);
michael@0 2329 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
michael@0 2330 nsCOMPtr<nsIDOMNode> endNode;
michael@0 2331 int32_t endOffset;
michael@0 2332 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2333 res = mHTMLEditor->GetEndNodeAndOffset(aSelection, getter_AddRefs(endNode), &endOffset);
michael@0 2334 NS_ENSURE_SUCCESS(res, res);
michael@0 2335 NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE);
michael@0 2336
michael@0 2337 // figure out if the endpoints are in nodes that can be merged
michael@0 2338 // adjust surrounding whitespace in preperation to delete selection
michael@0 2339 if (!IsPlaintextEditor())
michael@0 2340 {
michael@0 2341 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2342 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
michael@0 2343 res = nsWSRunObject::PrepareToDeleteRange(mHTMLEditor,
michael@0 2344 address_of(startNode), &startOffset,
michael@0 2345 address_of(endNode), &endOffset);
michael@0 2346 NS_ENSURE_SUCCESS(res, res);
michael@0 2347 }
michael@0 2348
michael@0 2349 {
michael@0 2350 // track location of where we are deleting
michael@0 2351 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2352 nsAutoTrackDOMPoint startTracker(mHTMLEditor->mRangeUpdater,
michael@0 2353 address_of(startNode), &startOffset);
michael@0 2354 nsAutoTrackDOMPoint endTracker(mHTMLEditor->mRangeUpdater,
michael@0 2355 address_of(endNode), &endOffset);
michael@0 2356 // we are handling all ranged deletions directly now.
michael@0 2357 *aHandled = true;
michael@0 2358
michael@0 2359 if (endNode == startNode)
michael@0 2360 {
michael@0 2361 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2362 res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
michael@0 2363 NS_ENSURE_SUCCESS(res, res);
michael@0 2364 }
michael@0 2365 else
michael@0 2366 {
michael@0 2367 // figure out mailcite ancestors
michael@0 2368 nsCOMPtr<nsIDOMNode> endCiteNode, startCiteNode;
michael@0 2369 res = GetTopEnclosingMailCite(startNode, address_of(startCiteNode),
michael@0 2370 IsPlaintextEditor());
michael@0 2371 NS_ENSURE_SUCCESS(res, res);
michael@0 2372 res = GetTopEnclosingMailCite(endNode, address_of(endCiteNode),
michael@0 2373 IsPlaintextEditor());
michael@0 2374 NS_ENSURE_SUCCESS(res, res);
michael@0 2375
michael@0 2376 // if we only have a mailcite at one of the two endpoints, set the directionality
michael@0 2377 // of the deletion so that the selection will end up outside the mailcite.
michael@0 2378 if (startCiteNode && !endCiteNode)
michael@0 2379 {
michael@0 2380 aAction = nsIEditor::eNext;
michael@0 2381 }
michael@0 2382 else if (!startCiteNode && endCiteNode)
michael@0 2383 {
michael@0 2384 aAction = nsIEditor::ePrevious;
michael@0 2385 }
michael@0 2386
michael@0 2387 // figure out block parents
michael@0 2388 nsCOMPtr<nsIDOMNode> leftParent;
michael@0 2389 nsCOMPtr<nsIDOMNode> rightParent;
michael@0 2390 if (IsBlockNode(startNode))
michael@0 2391 leftParent = startNode;
michael@0 2392 else {
michael@0 2393 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2394 leftParent = mHTMLEditor->GetBlockNodeParent(startNode);
michael@0 2395 }
michael@0 2396
michael@0 2397 if (IsBlockNode(endNode))
michael@0 2398 rightParent = endNode;
michael@0 2399 else {
michael@0 2400 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2401 rightParent = mHTMLEditor->GetBlockNodeParent(endNode);
michael@0 2402 }
michael@0 2403
michael@0 2404 // are endpoint block parents the same? use default deletion
michael@0 2405 if (leftParent == rightParent)
michael@0 2406 {
michael@0 2407 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2408 res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
michael@0 2409 }
michael@0 2410 else
michael@0 2411 {
michael@0 2412 // deleting across blocks
michael@0 2413 // are the blocks of same type?
michael@0 2414 NS_ENSURE_STATE(leftParent && rightParent);
michael@0 2415
michael@0 2416 // are the blocks siblings?
michael@0 2417 nsCOMPtr<nsIDOMNode> leftBlockParent;
michael@0 2418 nsCOMPtr<nsIDOMNode> rightBlockParent;
michael@0 2419 leftParent->GetParentNode(getter_AddRefs(leftBlockParent));
michael@0 2420 rightParent->GetParentNode(getter_AddRefs(rightBlockParent));
michael@0 2421
michael@0 2422 // MOOSE: this could conceivably screw up a table.. fix me.
michael@0 2423 if ( (leftBlockParent == rightBlockParent)
michael@0 2424 && (!mHTMLEditor || mHTMLEditor->NodesSameType(leftParent, rightParent)) )
michael@0 2425 {
michael@0 2426 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2427 if (nsHTMLEditUtils::IsParagraph(leftParent))
michael@0 2428 {
michael@0 2429 // first delete the selection
michael@0 2430 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2431 res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
michael@0 2432 NS_ENSURE_SUCCESS(res, res);
michael@0 2433 // then join para's, insert break
michael@0 2434 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2435 res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset);
michael@0 2436 NS_ENSURE_SUCCESS(res, res);
michael@0 2437 // fix up selection
michael@0 2438 res = aSelection->Collapse(selNode,selOffset);
michael@0 2439 return res;
michael@0 2440 }
michael@0 2441 if (nsHTMLEditUtils::IsListItem(leftParent)
michael@0 2442 || nsHTMLEditUtils::IsHeader(leftParent))
michael@0 2443 {
michael@0 2444 // first delete the selection
michael@0 2445 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2446 res = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
michael@0 2447 NS_ENSURE_SUCCESS(res, res);
michael@0 2448 // join blocks
michael@0 2449 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2450 res = mHTMLEditor->JoinNodeDeep(leftParent,rightParent,address_of(selNode),&selOffset);
michael@0 2451 NS_ENSURE_SUCCESS(res, res);
michael@0 2452 // fix up selection
michael@0 2453 res = aSelection->Collapse(selNode,selOffset);
michael@0 2454 return res;
michael@0 2455 }
michael@0 2456 }
michael@0 2457
michael@0 2458 // else blocks not same type, or not siblings. Delete everything except
michael@0 2459 // table elements.
michael@0 2460 join = true;
michael@0 2461
michael@0 2462 uint32_t rangeCount = aSelection->GetRangeCount();
michael@0 2463 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
michael@0 2464 nsRefPtr<nsRange> range = aSelection->GetRangeAt(rangeIdx);
michael@0 2465
michael@0 2466 // build a list of nodes in the range
michael@0 2467 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 2468 nsTrivialFunctor functor;
michael@0 2469 nsDOMSubtreeIterator iter;
michael@0 2470 res = iter.Init(range);
michael@0 2471 NS_ENSURE_SUCCESS(res, res);
michael@0 2472 res = iter.AppendList(functor, arrayOfNodes);
michael@0 2473 NS_ENSURE_SUCCESS(res, res);
michael@0 2474
michael@0 2475 // now that we have the list, delete non table elements
michael@0 2476 int32_t listCount = arrayOfNodes.Count();
michael@0 2477 for (int32_t j = 0; j < listCount; j++) {
michael@0 2478 nsCOMPtr<nsINode> somenode = do_QueryInterface(arrayOfNodes[0]);
michael@0 2479 NS_ENSURE_STATE(somenode);
michael@0 2480 DeleteNonTableElements(somenode);
michael@0 2481 arrayOfNodes.RemoveObjectAt(0);
michael@0 2482 // If something visible is deleted, no need to join.
michael@0 2483 // Visible means all nodes except non-visible textnodes and breaks.
michael@0 2484 if (join && origCollapsed) {
michael@0 2485 if (!somenode->IsContent()) {
michael@0 2486 join = false;
michael@0 2487 continue;
michael@0 2488 }
michael@0 2489 nsCOMPtr<nsIContent> content = somenode->AsContent();
michael@0 2490 if (content->NodeType() == nsIDOMNode::TEXT_NODE) {
michael@0 2491 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2492 mHTMLEditor->IsVisTextNode(content, &join, true);
michael@0 2493 } else {
michael@0 2494 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2495 join = content->IsHTML(nsGkAtoms::br) &&
michael@0 2496 !mHTMLEditor->IsVisBreak(somenode->AsDOMNode());
michael@0 2497 }
michael@0 2498 }
michael@0 2499 }
michael@0 2500 }
michael@0 2501
michael@0 2502 // check endopints for possible text deletion.
michael@0 2503 // we can assume that if text node is found, we can
michael@0 2504 // delete to end or to begining as appropriate,
michael@0 2505 // since the case where both sel endpoints in same
michael@0 2506 // text node was already handled (we wouldn't be here)
michael@0 2507 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2508 if ( mHTMLEditor->IsTextNode(startNode) )
michael@0 2509 {
michael@0 2510 // delete to last character
michael@0 2511 nsCOMPtr<nsIDOMCharacterData>nodeAsText;
michael@0 2512 uint32_t len;
michael@0 2513 nodeAsText = do_QueryInterface(startNode);
michael@0 2514 nodeAsText->GetLength(&len);
michael@0 2515 if (len > (uint32_t)startOffset)
michael@0 2516 {
michael@0 2517 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2518 res = mHTMLEditor->DeleteText(nodeAsText,startOffset,len-startOffset);
michael@0 2519 NS_ENSURE_SUCCESS(res, res);
michael@0 2520 }
michael@0 2521 }
michael@0 2522 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2523 if ( mHTMLEditor->IsTextNode(endNode) )
michael@0 2524 {
michael@0 2525 // delete to first character
michael@0 2526 nsCOMPtr<nsIDOMCharacterData>nodeAsText;
michael@0 2527 nodeAsText = do_QueryInterface(endNode);
michael@0 2528 if (endOffset)
michael@0 2529 {
michael@0 2530 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2531 res = mHTMLEditor->DeleteText(nodeAsText,0,endOffset);
michael@0 2532 NS_ENSURE_SUCCESS(res, res);
michael@0 2533 }
michael@0 2534 }
michael@0 2535
michael@0 2536 if (join) {
michael@0 2537 res = JoinBlocks(leftParent, rightParent, aCancel);
michael@0 2538 NS_ENSURE_SUCCESS(res, res);
michael@0 2539 }
michael@0 2540 }
michael@0 2541 }
michael@0 2542 }
michael@0 2543 //If we're joining blocks: if deleting forward the selection should be
michael@0 2544 //collapsed to the end of the selection, if deleting backward the selection
michael@0 2545 //should be collapsed to the beginning of the selection. But if we're not
michael@0 2546 //joining then the selection should collapse to the beginning of the
michael@0 2547 //selection if we'redeleting forward, because the end of the selection will
michael@0 2548 //still be in the next block. And same thing for deleting backwards
michael@0 2549 //(selection should collapse to the end, because the beginning will still
michael@0 2550 //be in the first block). See Bug 507936
michael@0 2551 if (join ? aAction == nsIEditor::eNext : aAction == nsIEditor::ePrevious)
michael@0 2552 {
michael@0 2553 res = aSelection->Collapse(endNode,endOffset);
michael@0 2554 }
michael@0 2555 else
michael@0 2556 {
michael@0 2557 res = aSelection->Collapse(startNode,startOffset);
michael@0 2558 }
michael@0 2559 return res;
michael@0 2560 }
michael@0 2561
michael@0 2562
michael@0 2563 /*****************************************************************************************************
michael@0 2564 * InsertBRIfNeeded: determines if a br is needed for current selection to not be spastic.
michael@0 2565 * If so, it inserts one. Callers responsibility to only call with collapsed selection.
michael@0 2566 * nsISelection *aSelection the collapsed selection
michael@0 2567 */
michael@0 2568 nsresult
michael@0 2569 nsHTMLEditRules::InsertBRIfNeeded(nsISelection *aSelection)
michael@0 2570 {
michael@0 2571 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
michael@0 2572
michael@0 2573 // get selection
michael@0 2574 nsCOMPtr<nsIDOMNode> node;
michael@0 2575 int32_t offset;
michael@0 2576 nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset);
michael@0 2577 NS_ENSURE_SUCCESS(res, res);
michael@0 2578 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
michael@0 2579
michael@0 2580 // inline elements don't need any br
michael@0 2581 if (!IsBlockNode(node))
michael@0 2582 return res;
michael@0 2583
michael@0 2584 // examine selection
michael@0 2585 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2586 nsWSRunObject wsObj(mHTMLEditor, node, offset);
michael@0 2587 if (((wsObj.mStartReason & WSType::block) ||
michael@0 2588 (wsObj.mStartReason & WSType::br)) &&
michael@0 2589 (wsObj.mEndReason & WSType::block)) {
michael@0 2590 // if we are tucked between block boundaries then insert a br
michael@0 2591 // first check that we are allowed to
michael@0 2592 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2593 if (mHTMLEditor->CanContainTag(node, nsGkAtoms::br)) {
michael@0 2594 nsCOMPtr<nsIDOMNode> brNode;
michael@0 2595 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2596 res = mHTMLEditor->CreateBR(node, offset, address_of(brNode), nsIEditor::ePrevious);
michael@0 2597 }
michael@0 2598 }
michael@0 2599 return res;
michael@0 2600 }
michael@0 2601
michael@0 2602 /*****************************************************************************************************
michael@0 2603 * GetGoodSelPointForNode: Finds where at a node you would want to set the selection if you were
michael@0 2604 * trying to have a caret next to it.
michael@0 2605 * nsIDOMNode *aNode the node
michael@0 2606 * nsIEditor::EDirection aAction which edge to find: eNext indicates beginning, ePrevious ending
michael@0 2607 * nsCOMPtr<nsIDOMNode> *outSelNode desired sel node
michael@0 2608 * int32_t *outSelOffset desired sel offset
michael@0 2609 */
michael@0 2610 nsresult
michael@0 2611 nsHTMLEditRules::GetGoodSelPointForNode(nsIDOMNode *aNode, nsIEditor::EDirection aAction,
michael@0 2612 nsCOMPtr<nsIDOMNode> *outSelNode, int32_t *outSelOffset)
michael@0 2613 {
michael@0 2614 NS_ENSURE_TRUE(aNode && outSelNode && outSelOffset, NS_ERROR_NULL_POINTER);
michael@0 2615
michael@0 2616 nsresult res = NS_OK;
michael@0 2617
michael@0 2618 // default values
michael@0 2619 *outSelNode = aNode;
michael@0 2620 *outSelOffset = 0;
michael@0 2621
michael@0 2622 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2623 if (mHTMLEditor->IsTextNode(aNode) ||
michael@0 2624 !mHTMLEditor || mHTMLEditor->IsContainer(aNode))
michael@0 2625 {
michael@0 2626 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2627 if (aAction == nsIEditor::ePrevious)
michael@0 2628 {
michael@0 2629 uint32_t len;
michael@0 2630 res = mHTMLEditor->GetLengthOfDOMNode(aNode, len);
michael@0 2631 *outSelOffset = int32_t(len);
michael@0 2632 NS_ENSURE_SUCCESS(res, res);
michael@0 2633 }
michael@0 2634 }
michael@0 2635 else
michael@0 2636 {
michael@0 2637 *outSelNode = nsEditor::GetNodeLocation(aNode, outSelOffset);
michael@0 2638 if (!nsTextEditUtils::IsBreak(aNode) ||
michael@0 2639 !mHTMLEditor || mHTMLEditor->IsVisBreak(aNode))
michael@0 2640 {
michael@0 2641 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2642 if (aAction == nsIEditor::ePrevious)
michael@0 2643 (*outSelOffset)++;
michael@0 2644 }
michael@0 2645 }
michael@0 2646 return res;
michael@0 2647 }
michael@0 2648
michael@0 2649
michael@0 2650 /*****************************************************************************************************
michael@0 2651 * JoinBlocks: this method is used to join two block elements. The right element is always joined
michael@0 2652 * to the left element. If the elements are the same type and not nested within each other,
michael@0 2653 * JoinNodesSmart is called (example, joining two list items together into one). If the elements
michael@0 2654 * are not the same type, or one is a descendant of the other, we instead destroy the right block
michael@0 2655 * placing its children into leftblock. DTD containment rules are followed throughout.
michael@0 2656 * nsCOMPtr<nsIDOMNode> *aLeftBlock pointer to the left block
michael@0 2657 * nsCOMPtr<nsIDOMNode> *aRightBlock pointer to the right block; will have contents moved to left block
michael@0 2658 * bool *aCanceled return TRUE if we had to cancel operation
michael@0 2659 */
michael@0 2660 nsresult
michael@0 2661 nsHTMLEditRules::JoinBlocks(nsIDOMNode *aLeftNode,
michael@0 2662 nsIDOMNode *aRightNode,
michael@0 2663 bool *aCanceled)
michael@0 2664 {
michael@0 2665 NS_ENSURE_ARG_POINTER(aLeftNode && aRightNode);
michael@0 2666
michael@0 2667 nsCOMPtr<nsIDOMNode> aLeftBlock, aRightBlock;
michael@0 2668
michael@0 2669 if (IsBlockNode(aLeftNode)) {
michael@0 2670 aLeftBlock = aLeftNode;
michael@0 2671 } else if (aLeftNode) {
michael@0 2672 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2673 aLeftBlock = mHTMLEditor->GetBlockNodeParent(aLeftNode);
michael@0 2674 }
michael@0 2675
michael@0 2676 if (IsBlockNode(aRightNode)) {
michael@0 2677 aRightBlock = aRightNode;
michael@0 2678 } else if (aRightNode) {
michael@0 2679 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2680 aRightBlock = mHTMLEditor->GetBlockNodeParent(aRightNode);
michael@0 2681 }
michael@0 2682
michael@0 2683 // sanity checks
michael@0 2684 NS_ENSURE_TRUE(aLeftBlock && aRightBlock, NS_ERROR_NULL_POINTER);
michael@0 2685 NS_ENSURE_STATE(aLeftBlock != aRightBlock);
michael@0 2686
michael@0 2687 if (nsHTMLEditUtils::IsTableElement(aLeftBlock) ||
michael@0 2688 nsHTMLEditUtils::IsTableElement(aRightBlock)) {
michael@0 2689 // do not try to merge table elements
michael@0 2690 *aCanceled = true;
michael@0 2691 return NS_OK;
michael@0 2692 }
michael@0 2693
michael@0 2694 // make sure we don't try to move thing's into HR's, which look like blocks but aren't containers
michael@0 2695 if (nsHTMLEditUtils::IsHR(aLeftBlock)) {
michael@0 2696 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2697 nsCOMPtr<nsIDOMNode> realLeft = mHTMLEditor->GetBlockNodeParent(aLeftBlock);
michael@0 2698 aLeftBlock = realLeft;
michael@0 2699 }
michael@0 2700 if (nsHTMLEditUtils::IsHR(aRightBlock)) {
michael@0 2701 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2702 nsCOMPtr<nsIDOMNode> realRight = mHTMLEditor->GetBlockNodeParent(aRightBlock);
michael@0 2703 aRightBlock = realRight;
michael@0 2704 }
michael@0 2705
michael@0 2706 // bail if both blocks the same
michael@0 2707 if (aLeftBlock == aRightBlock) {
michael@0 2708 *aCanceled = true;
michael@0 2709 return NS_OK;
michael@0 2710 }
michael@0 2711
michael@0 2712 // Joining a list item to its parent is a NOP.
michael@0 2713 if (nsHTMLEditUtils::IsList(aLeftBlock) &&
michael@0 2714 nsHTMLEditUtils::IsListItem(aRightBlock)) {
michael@0 2715 nsCOMPtr<nsIDOMNode> rightParent;
michael@0 2716 aRightBlock->GetParentNode(getter_AddRefs(rightParent));
michael@0 2717 if (rightParent == aLeftBlock) {
michael@0 2718 return NS_OK;
michael@0 2719 }
michael@0 2720 }
michael@0 2721
michael@0 2722 // special rule here: if we are trying to join list items, and they are in different lists,
michael@0 2723 // join the lists instead.
michael@0 2724 bool bMergeLists = false;
michael@0 2725 nsIAtom* existingList = nsGkAtoms::_empty;
michael@0 2726 int32_t theOffset;
michael@0 2727 nsCOMPtr<nsIDOMNode> leftList, rightList;
michael@0 2728 if (nsHTMLEditUtils::IsListItem(aLeftBlock) &&
michael@0 2729 nsHTMLEditUtils::IsListItem(aRightBlock)) {
michael@0 2730 aLeftBlock->GetParentNode(getter_AddRefs(leftList));
michael@0 2731 aRightBlock->GetParentNode(getter_AddRefs(rightList));
michael@0 2732 if (leftList && rightList && (leftList!=rightList))
michael@0 2733 {
michael@0 2734 // there are some special complications if the lists are descendants of
michael@0 2735 // the other lists' items. Note that it is ok for them to be descendants
michael@0 2736 // of the other lists themselves, which is the usual case for sublists
michael@0 2737 // in our impllementation.
michael@0 2738 if (!nsEditorUtils::IsDescendantOf(leftList, aRightBlock, &theOffset) &&
michael@0 2739 !nsEditorUtils::IsDescendantOf(rightList, aLeftBlock, &theOffset))
michael@0 2740 {
michael@0 2741 aLeftBlock = leftList;
michael@0 2742 aRightBlock = rightList;
michael@0 2743 bMergeLists = true;
michael@0 2744 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2745 existingList = mHTMLEditor->GetTag(leftList);
michael@0 2746 }
michael@0 2747 }
michael@0 2748 }
michael@0 2749
michael@0 2750 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2751 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
michael@0 2752
michael@0 2753 nsresult res = NS_OK;
michael@0 2754 int32_t rightOffset = 0;
michael@0 2755 int32_t leftOffset = -1;
michael@0 2756
michael@0 2757 // theOffset below is where you find yourself in aRightBlock when you traverse upwards
michael@0 2758 // from aLeftBlock
michael@0 2759 if (nsEditorUtils::IsDescendantOf(aLeftBlock, aRightBlock, &rightOffset)) {
michael@0 2760 // tricky case. left block is inside right block.
michael@0 2761 // Do ws adjustment. This just destroys non-visible ws at boundaries we will be joining.
michael@0 2762 rightOffset++;
michael@0 2763 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2764 res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor,
michael@0 2765 address_of(aLeftBlock),
michael@0 2766 nsWSRunObject::kBlockEnd);
michael@0 2767 NS_ENSURE_SUCCESS(res, res);
michael@0 2768 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2769 res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor,
michael@0 2770 address_of(aRightBlock),
michael@0 2771 nsWSRunObject::kAfterBlock,
michael@0 2772 &rightOffset);
michael@0 2773 NS_ENSURE_SUCCESS(res, res);
michael@0 2774 // Do br adjustment.
michael@0 2775 nsCOMPtr<nsIDOMNode> brNode;
michael@0 2776 res = CheckForInvisibleBR(aLeftBlock, kBlockEnd, address_of(brNode));
michael@0 2777 NS_ENSURE_SUCCESS(res, res);
michael@0 2778 if (bMergeLists)
michael@0 2779 {
michael@0 2780 // idea here is to take all children in rightList that are past
michael@0 2781 // theOffset, and pull them into leftlist.
michael@0 2782 nsCOMPtr<nsIDOMNode> childToMove;
michael@0 2783 nsCOMPtr<nsIContent> parent(do_QueryInterface(rightList));
michael@0 2784 NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
michael@0 2785
michael@0 2786 nsIContent *child = parent->GetChildAt(theOffset);
michael@0 2787 while (child)
michael@0 2788 {
michael@0 2789 childToMove = do_QueryInterface(child);
michael@0 2790 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2791 res = mHTMLEditor->MoveNode(childToMove, leftList, -1);
michael@0 2792 NS_ENSURE_SUCCESS(res, res);
michael@0 2793
michael@0 2794 child = parent->GetChildAt(rightOffset);
michael@0 2795 }
michael@0 2796 }
michael@0 2797 else
michael@0 2798 {
michael@0 2799 res = MoveBlock(aLeftBlock, aRightBlock, leftOffset, rightOffset);
michael@0 2800 }
michael@0 2801 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2802 if (brNode) mHTMLEditor->DeleteNode(brNode);
michael@0 2803 // theOffset below is where you find yourself in aLeftBlock when you traverse upwards
michael@0 2804 // from aRightBlock
michael@0 2805 } else if (nsEditorUtils::IsDescendantOf(aRightBlock, aLeftBlock, &leftOffset)) {
michael@0 2806 // tricky case. right block is inside left block.
michael@0 2807 // Do ws adjustment. This just destroys non-visible ws at boundaries we will be joining.
michael@0 2808 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2809 res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor,
michael@0 2810 address_of(aRightBlock),
michael@0 2811 nsWSRunObject::kBlockStart);
michael@0 2812 NS_ENSURE_SUCCESS(res, res);
michael@0 2813 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2814 res = nsWSRunObject::ScrubBlockBoundary(mHTMLEditor,
michael@0 2815 address_of(aLeftBlock),
michael@0 2816 nsWSRunObject::kBeforeBlock,
michael@0 2817 &leftOffset);
michael@0 2818 NS_ENSURE_SUCCESS(res, res);
michael@0 2819 // Do br adjustment.
michael@0 2820 nsCOMPtr<nsIDOMNode> brNode;
michael@0 2821 res = CheckForInvisibleBR(aLeftBlock, kBeforeBlock, address_of(brNode),
michael@0 2822 leftOffset);
michael@0 2823 NS_ENSURE_SUCCESS(res, res);
michael@0 2824 if (bMergeLists)
michael@0 2825 {
michael@0 2826 res = MoveContents(rightList, leftList, &leftOffset);
michael@0 2827 }
michael@0 2828 else
michael@0 2829 {
michael@0 2830 // Left block is a parent of right block, and the parent of the previous
michael@0 2831 // visible content. Right block is a child and contains the contents we
michael@0 2832 // want to move.
michael@0 2833
michael@0 2834 int32_t previousContentOffset;
michael@0 2835 nsCOMPtr<nsIDOMNode> previousContentParent;
michael@0 2836
michael@0 2837 if (aLeftNode == aLeftBlock) {
michael@0 2838 // We are working with valid HTML, aLeftNode is a block node, and is
michael@0 2839 // therefore allowed to contain aRightBlock. This is the simple case,
michael@0 2840 // we will simply move the content in aRightBlock out of its block.
michael@0 2841 previousContentParent = aLeftBlock;
michael@0 2842 previousContentOffset = leftOffset;
michael@0 2843 } else {
michael@0 2844 // We try to work as well as possible with HTML that's already invalid.
michael@0 2845 // Although "right block" is a block, and a block must not be contained
michael@0 2846 // in inline elements, reality is that broken documents do exist. The
michael@0 2847 // DIRECT parent of "left NODE" might be an inline element. Previous
michael@0 2848 // versions of this code skipped inline parents until the first block
michael@0 2849 // parent was found (and used "left block" as the destination).
michael@0 2850 // However, in some situations this strategy moves the content to an
michael@0 2851 // unexpected position. (see bug 200416) The new idea is to make the
michael@0 2852 // moving content a sibling, next to the previous visible content.
michael@0 2853
michael@0 2854 previousContentParent =
michael@0 2855 nsEditor::GetNodeLocation(aLeftNode, &previousContentOffset);
michael@0 2856
michael@0 2857 // We want to move our content just after the previous visible node.
michael@0 2858 previousContentOffset++;
michael@0 2859 }
michael@0 2860
michael@0 2861 // Because we don't want the moving content to receive the style of the
michael@0 2862 // previous content, we split the previous content's style.
michael@0 2863
michael@0 2864 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2865 nsCOMPtr<nsINode> editorRoot = mHTMLEditor->GetEditorRoot();
michael@0 2866 if (!editorRoot || aLeftNode != editorRoot->AsDOMNode()) {
michael@0 2867 nsCOMPtr<nsIDOMNode> splittedPreviousContent;
michael@0 2868 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2869 res = mHTMLEditor->SplitStyleAbovePoint(address_of(previousContentParent),
michael@0 2870 &previousContentOffset,
michael@0 2871 nullptr, nullptr, nullptr,
michael@0 2872 address_of(splittedPreviousContent));
michael@0 2873 NS_ENSURE_SUCCESS(res, res);
michael@0 2874
michael@0 2875 if (splittedPreviousContent) {
michael@0 2876 previousContentParent =
michael@0 2877 nsEditor::GetNodeLocation(splittedPreviousContent,
michael@0 2878 &previousContentOffset);
michael@0 2879 }
michael@0 2880 }
michael@0 2881
michael@0 2882 res = MoveBlock(previousContentParent, aRightBlock,
michael@0 2883 previousContentOffset, rightOffset);
michael@0 2884 }
michael@0 2885 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2886 if (brNode) mHTMLEditor->DeleteNode(brNode);
michael@0 2887 }
michael@0 2888 else
michael@0 2889 {
michael@0 2890 // normal case. blocks are siblings, or at least close enough to siblings. An example
michael@0 2891 // of the latter is a <p>paragraph</p><ul><li>one<li>two<li>three</ul>. The first
michael@0 2892 // li and the p are not true siblings, but we still want to join them if you backspace
michael@0 2893 // from li into p.
michael@0 2894
michael@0 2895 // adjust whitespace at block boundaries
michael@0 2896 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2897 res = nsWSRunObject::PrepareToJoinBlocks(mHTMLEditor, aLeftBlock, aRightBlock);
michael@0 2898 NS_ENSURE_SUCCESS(res, res);
michael@0 2899 // Do br adjustment.
michael@0 2900 nsCOMPtr<nsIDOMNode> brNode;
michael@0 2901 res = CheckForInvisibleBR(aLeftBlock, kBlockEnd, address_of(brNode));
michael@0 2902 NS_ENSURE_SUCCESS(res, res);
michael@0 2903 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2904 if (bMergeLists || mHTMLEditor->NodesSameType(aLeftBlock, aRightBlock)) {
michael@0 2905 // nodes are same type. merge them.
michael@0 2906 nsCOMPtr<nsIDOMNode> parent;
michael@0 2907 int32_t offset;
michael@0 2908 res = JoinNodesSmart(aLeftBlock, aRightBlock, address_of(parent), &offset);
michael@0 2909 if (NS_SUCCEEDED(res) && bMergeLists)
michael@0 2910 {
michael@0 2911 nsCOMPtr<nsIDOMNode> newBlock;
michael@0 2912 res = ConvertListType(aRightBlock, address_of(newBlock),
michael@0 2913 existingList, nsGkAtoms::li);
michael@0 2914 }
michael@0 2915 }
michael@0 2916 else
michael@0 2917 {
michael@0 2918 // nodes are disimilar types.
michael@0 2919 res = MoveBlock(aLeftBlock, aRightBlock, leftOffset, rightOffset);
michael@0 2920 }
michael@0 2921 if (NS_SUCCEEDED(res) && brNode)
michael@0 2922 {
michael@0 2923 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2924 res = mHTMLEditor->DeleteNode(brNode);
michael@0 2925 }
michael@0 2926 }
michael@0 2927 return res;
michael@0 2928 }
michael@0 2929
michael@0 2930
michael@0 2931 /*****************************************************************************************************
michael@0 2932 * MoveBlock: this method is used to move the content from rightBlock into leftBlock
michael@0 2933 * Note that the "block" might merely be inline nodes between <br>s, or between blocks, etc.
michael@0 2934 * DTD containment rules are followed throughout.
michael@0 2935 * nsIDOMNode *aLeftBlock parent to receive moved content
michael@0 2936 * nsIDOMNode *aRightBlock parent to provide moved content
michael@0 2937 * int32_t aLeftOffset offset in aLeftBlock to move content to
michael@0 2938 * int32_t aRightOffset offset in aRightBlock to move content from
michael@0 2939 */
michael@0 2940 nsresult
michael@0 2941 nsHTMLEditRules::MoveBlock(nsIDOMNode *aLeftBlock, nsIDOMNode *aRightBlock, int32_t aLeftOffset, int32_t aRightOffset)
michael@0 2942 {
michael@0 2943 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 2944 nsCOMPtr<nsISupports> isupports;
michael@0 2945 // GetNodesFromPoint is the workhorse that figures out what we wnat to move.
michael@0 2946 nsresult res = GetNodesFromPoint(::DOMPoint(aRightBlock,aRightOffset),
michael@0 2947 EditAction::makeList, arrayOfNodes, true);
michael@0 2948 NS_ENSURE_SUCCESS(res, res);
michael@0 2949 int32_t listCount = arrayOfNodes.Count();
michael@0 2950 int32_t i;
michael@0 2951 for (i=0; i<listCount; i++)
michael@0 2952 {
michael@0 2953 // get the node to act on
michael@0 2954 nsIDOMNode* curNode = arrayOfNodes[i];
michael@0 2955 if (IsBlockNode(curNode))
michael@0 2956 {
michael@0 2957 // For block nodes, move their contents only, then delete block.
michael@0 2958 res = MoveContents(curNode, aLeftBlock, &aLeftOffset);
michael@0 2959 NS_ENSURE_SUCCESS(res, res);
michael@0 2960 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2961 res = mHTMLEditor->DeleteNode(curNode);
michael@0 2962 }
michael@0 2963 else
michael@0 2964 {
michael@0 2965 // otherwise move the content as is, checking against the dtd.
michael@0 2966 res = MoveNodeSmart(curNode, aLeftBlock, &aLeftOffset);
michael@0 2967 }
michael@0 2968 }
michael@0 2969 return res;
michael@0 2970 }
michael@0 2971
michael@0 2972 /*****************************************************************************************************
michael@0 2973 * MoveNodeSmart: this method is used to move node aSource to (aDest,aOffset).
michael@0 2974 * DTD containment rules are followed throughout. aOffset is updated to point _after_
michael@0 2975 * inserted content.
michael@0 2976 * nsIDOMNode *aSource the selection.
michael@0 2977 * nsIDOMNode *aDest parent to receive moved content
michael@0 2978 * int32_t *aOffset offset in aDest to move content to
michael@0 2979 */
michael@0 2980 nsresult
michael@0 2981 nsHTMLEditRules::MoveNodeSmart(nsIDOMNode *aSource, nsIDOMNode *aDest, int32_t *aOffset)
michael@0 2982 {
michael@0 2983 NS_ENSURE_TRUE(aSource && aDest && aOffset, NS_ERROR_NULL_POINTER);
michael@0 2984
michael@0 2985 nsresult res;
michael@0 2986 // check if this node can go into the destination node
michael@0 2987 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2988 if (mHTMLEditor->CanContain(aDest, aSource)) {
michael@0 2989 // if it can, move it there
michael@0 2990 NS_ENSURE_STATE(mHTMLEditor);
michael@0 2991 res = mHTMLEditor->MoveNode(aSource, aDest, *aOffset);
michael@0 2992 NS_ENSURE_SUCCESS(res, res);
michael@0 2993 if (*aOffset != -1) ++(*aOffset);
michael@0 2994 }
michael@0 2995 else
michael@0 2996 {
michael@0 2997 // if it can't, move its children, and then delete it.
michael@0 2998 res = MoveContents(aSource, aDest, aOffset);
michael@0 2999 NS_ENSURE_SUCCESS(res, res);
michael@0 3000 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3001 res = mHTMLEditor->DeleteNode(aSource);
michael@0 3002 NS_ENSURE_SUCCESS(res, res);
michael@0 3003 }
michael@0 3004 return NS_OK;
michael@0 3005 }
michael@0 3006
michael@0 3007 /*****************************************************************************************************
michael@0 3008 * MoveContents: this method is used to move node the _contents_ of aSource to (aDest,aOffset).
michael@0 3009 * DTD containment rules are followed throughout. aOffset is updated to point _after_
michael@0 3010 * inserted content. aSource is deleted.
michael@0 3011 * nsIDOMNode *aSource the selection.
michael@0 3012 * nsIDOMNode *aDest parent to receive moved content
michael@0 3013 * int32_t *aOffset offset in aDest to move content to
michael@0 3014 */
michael@0 3015 nsresult
michael@0 3016 nsHTMLEditRules::MoveContents(nsIDOMNode *aSource, nsIDOMNode *aDest, int32_t *aOffset)
michael@0 3017 {
michael@0 3018 NS_ENSURE_TRUE(aSource && aDest && aOffset, NS_ERROR_NULL_POINTER);
michael@0 3019 if (aSource == aDest) return NS_ERROR_ILLEGAL_VALUE;
michael@0 3020 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3021 NS_ASSERTION(!mHTMLEditor->IsTextNode(aSource), "#text does not have contents");
michael@0 3022
michael@0 3023 nsCOMPtr<nsIDOMNode> child;
michael@0 3024 nsAutoString tag;
michael@0 3025 nsresult res;
michael@0 3026 aSource->GetFirstChild(getter_AddRefs(child));
michael@0 3027 while (child)
michael@0 3028 {
michael@0 3029 res = MoveNodeSmart(child, aDest, aOffset);
michael@0 3030 NS_ENSURE_SUCCESS(res, res);
michael@0 3031 aSource->GetFirstChild(getter_AddRefs(child));
michael@0 3032 }
michael@0 3033 return NS_OK;
michael@0 3034 }
michael@0 3035
michael@0 3036
michael@0 3037 nsresult
michael@0 3038 nsHTMLEditRules::DeleteNonTableElements(nsINode* aNode)
michael@0 3039 {
michael@0 3040 MOZ_ASSERT(aNode);
michael@0 3041 if (!nsHTMLEditUtils::IsTableElementButNotTable(aNode)) {
michael@0 3042 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3043 return mHTMLEditor->DeleteNode(aNode->AsDOMNode());
michael@0 3044 }
michael@0 3045
michael@0 3046 for (int32_t i = aNode->GetChildCount() - 1; i >= 0; --i) {
michael@0 3047 nsresult rv = DeleteNonTableElements(aNode->GetChildAt(i));
michael@0 3048 NS_ENSURE_SUCCESS(rv, rv);
michael@0 3049 }
michael@0 3050 return NS_OK;
michael@0 3051 }
michael@0 3052
michael@0 3053 nsresult
michael@0 3054 nsHTMLEditRules::DidDeleteSelection(nsISelection *aSelection,
michael@0 3055 nsIEditor::EDirection aDir,
michael@0 3056 nsresult aResult)
michael@0 3057 {
michael@0 3058 if (!aSelection) { return NS_ERROR_NULL_POINTER; }
michael@0 3059
michael@0 3060 // find where we are
michael@0 3061 nsCOMPtr<nsIDOMNode> startNode;
michael@0 3062 int32_t startOffset;
michael@0 3063 nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
michael@0 3064 NS_ENSURE_SUCCESS(res, res);
michael@0 3065 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
michael@0 3066
michael@0 3067 // find any enclosing mailcite
michael@0 3068 nsCOMPtr<nsIDOMNode> citeNode;
michael@0 3069 res = GetTopEnclosingMailCite(startNode, address_of(citeNode),
michael@0 3070 IsPlaintextEditor());
michael@0 3071 NS_ENSURE_SUCCESS(res, res);
michael@0 3072 if (citeNode) {
michael@0 3073 nsCOMPtr<nsINode> cite = do_QueryInterface(citeNode);
michael@0 3074 bool isEmpty = true, seenBR = false;
michael@0 3075 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3076 mHTMLEditor->IsEmptyNodeImpl(cite, &isEmpty, true, true, false, &seenBR);
michael@0 3077 if (isEmpty)
michael@0 3078 {
michael@0 3079 nsCOMPtr<nsIDOMNode> brNode;
michael@0 3080 int32_t offset;
michael@0 3081 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(citeNode, &offset);
michael@0 3082 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3083 res = mHTMLEditor->DeleteNode(citeNode);
michael@0 3084 NS_ENSURE_SUCCESS(res, res);
michael@0 3085 if (parent && seenBR)
michael@0 3086 {
michael@0 3087 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3088 res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode));
michael@0 3089 NS_ENSURE_SUCCESS(res, res);
michael@0 3090 aSelection->Collapse(parent, offset);
michael@0 3091 }
michael@0 3092 }
michael@0 3093 }
michael@0 3094
michael@0 3095 // call through to base class
michael@0 3096 return nsTextEditRules::DidDeleteSelection(aSelection, aDir, aResult);
michael@0 3097 }
michael@0 3098
michael@0 3099 nsresult
michael@0 3100 nsHTMLEditRules::WillMakeList(Selection* aSelection,
michael@0 3101 const nsAString* aListType,
michael@0 3102 bool aEntireList,
michael@0 3103 const nsAString* aBulletType,
michael@0 3104 bool* aCancel,
michael@0 3105 bool* aHandled,
michael@0 3106 const nsAString* aItemType)
michael@0 3107 {
michael@0 3108 if (!aSelection || !aListType || !aCancel || !aHandled) {
michael@0 3109 return NS_ERROR_NULL_POINTER;
michael@0 3110 }
michael@0 3111 nsCOMPtr<nsIAtom> listTypeAtom = do_GetAtom(*aListType);
michael@0 3112 NS_ENSURE_TRUE(listTypeAtom, NS_ERROR_OUT_OF_MEMORY);
michael@0 3113
michael@0 3114 nsresult res = WillInsert(aSelection, aCancel);
michael@0 3115 NS_ENSURE_SUCCESS(res, res);
michael@0 3116
michael@0 3117 // initialize out param
michael@0 3118 // we want to ignore result of WillInsert()
michael@0 3119 *aCancel = false;
michael@0 3120 *aHandled = false;
michael@0 3121
michael@0 3122 // deduce what tag to use for list items
michael@0 3123 nsCOMPtr<nsIAtom> itemType;
michael@0 3124 if (aItemType) {
michael@0 3125 itemType = do_GetAtom(*aItemType);
michael@0 3126 NS_ENSURE_TRUE(itemType, NS_ERROR_OUT_OF_MEMORY);
michael@0 3127 } else if (listTypeAtom == nsGkAtoms::dl) {
michael@0 3128 itemType = nsGkAtoms::dd;
michael@0 3129 } else {
michael@0 3130 itemType = nsGkAtoms::li;
michael@0 3131 }
michael@0 3132
michael@0 3133 // convert the selection ranges into "promoted" selection ranges:
michael@0 3134 // this basically just expands the range to include the immediate
michael@0 3135 // block parent, and then further expands to include any ancestors
michael@0 3136 // whose children are all in the range
michael@0 3137
michael@0 3138 *aHandled = true;
michael@0 3139
michael@0 3140 res = NormalizeSelection(aSelection);
michael@0 3141 NS_ENSURE_SUCCESS(res, res);
michael@0 3142 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3143 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
michael@0 3144
michael@0 3145 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 3146 res = GetListActionNodes(arrayOfNodes, aEntireList);
michael@0 3147 NS_ENSURE_SUCCESS(res, res);
michael@0 3148
michael@0 3149 int32_t listCount = arrayOfNodes.Count();
michael@0 3150
michael@0 3151 // check if all our nodes are <br>s, or empty inlines
michael@0 3152 bool bOnlyBreaks = true;
michael@0 3153 for (int32_t j = 0; j < listCount; j++) {
michael@0 3154 nsIDOMNode* curNode = arrayOfNodes[j];
michael@0 3155 // if curNode is not a Break or empty inline, we're done
michael@0 3156 if (!nsTextEditUtils::IsBreak(curNode) && !IsEmptyInline(curNode)) {
michael@0 3157 bOnlyBreaks = false;
michael@0 3158 break;
michael@0 3159 }
michael@0 3160 }
michael@0 3161
michael@0 3162 // if no nodes, we make empty list. Ditto if the user tried to make a list
michael@0 3163 // of some # of breaks.
michael@0 3164 if (!listCount || bOnlyBreaks) {
michael@0 3165 nsCOMPtr<nsIDOMNode> parent, theList, theListItem;
michael@0 3166 int32_t offset;
michael@0 3167
michael@0 3168 // if only breaks, delete them
michael@0 3169 if (bOnlyBreaks) {
michael@0 3170 for (int32_t j = 0; j < (int32_t)listCount; j++) {
michael@0 3171 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3172 res = mHTMLEditor->DeleteNode(arrayOfNodes[j]);
michael@0 3173 NS_ENSURE_SUCCESS(res, res);
michael@0 3174 }
michael@0 3175 }
michael@0 3176
michael@0 3177 // get selection location
michael@0 3178 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3179 res = mHTMLEditor->GetStartNodeAndOffset(aSelection,
michael@0 3180 getter_AddRefs(parent), &offset);
michael@0 3181 NS_ENSURE_SUCCESS(res, res);
michael@0 3182
michael@0 3183 // make sure we can put a list here
michael@0 3184 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3185 if (!mHTMLEditor->CanContainTag(parent, listTypeAtom)) {
michael@0 3186 *aCancel = true;
michael@0 3187 return NS_OK;
michael@0 3188 }
michael@0 3189 res = SplitAsNeeded(aListType, address_of(parent), &offset);
michael@0 3190 NS_ENSURE_SUCCESS(res, res);
michael@0 3191 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3192 res = mHTMLEditor->CreateNode(*aListType, parent, offset,
michael@0 3193 getter_AddRefs(theList));
michael@0 3194 NS_ENSURE_SUCCESS(res, res);
michael@0 3195 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3196 res = mHTMLEditor->CreateNode(nsDependentAtomString(itemType), theList, 0,
michael@0 3197 getter_AddRefs(theListItem));
michael@0 3198 NS_ENSURE_SUCCESS(res, res);
michael@0 3199 // remember our new block for postprocessing
michael@0 3200 mNewBlock = theListItem;
michael@0 3201 // put selection in new list item
michael@0 3202 res = aSelection->Collapse(theListItem, 0);
michael@0 3203 // to prevent selection resetter from overriding us
michael@0 3204 selectionResetter.Abort();
michael@0 3205 *aHandled = true;
michael@0 3206 return res;
michael@0 3207 }
michael@0 3208
michael@0 3209 // if there is only one node in the array, and it is a list, div, or
michael@0 3210 // blockquote, then look inside of it until we find inner list or content.
michael@0 3211
michael@0 3212 res = LookInsideDivBQandList(arrayOfNodes);
michael@0 3213 NS_ENSURE_SUCCESS(res, res);
michael@0 3214
michael@0 3215 // Ok, now go through all the nodes and put then in the list,
michael@0 3216 // or whatever is approriate. Wohoo!
michael@0 3217
michael@0 3218 listCount = arrayOfNodes.Count();
michael@0 3219 nsCOMPtr<nsIDOMNode> curParent;
michael@0 3220 nsCOMPtr<nsIDOMNode> curList;
michael@0 3221 nsCOMPtr<nsIDOMNode> prevListItem;
michael@0 3222
michael@0 3223 for (int32_t i = 0; i < listCount; i++) {
michael@0 3224 // here's where we actually figure out what to do
michael@0 3225 nsCOMPtr<nsIDOMNode> newBlock;
michael@0 3226 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
michael@0 3227 int32_t offset;
michael@0 3228 curParent = nsEditor::GetNodeLocation(curNode, &offset);
michael@0 3229
michael@0 3230 // make sure we don't assemble content that is in different table cells
michael@0 3231 // into the same list. respect table cell boundaries when listifying.
michael@0 3232 if (curList && InDifferentTableElements(curList, curNode)) {
michael@0 3233 curList = nullptr;
michael@0 3234 }
michael@0 3235
michael@0 3236 // if curNode is a Break, delete it, and quit remembering prev list item
michael@0 3237 if (nsTextEditUtils::IsBreak(curNode)) {
michael@0 3238 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3239 res = mHTMLEditor->DeleteNode(curNode);
michael@0 3240 NS_ENSURE_SUCCESS(res, res);
michael@0 3241 prevListItem = 0;
michael@0 3242 continue;
michael@0 3243 } else if (IsEmptyInline(curNode)) {
michael@0 3244 // if curNode is an empty inline container, delete it
michael@0 3245 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3246 res = mHTMLEditor->DeleteNode(curNode);
michael@0 3247 NS_ENSURE_SUCCESS(res, res);
michael@0 3248 continue;
michael@0 3249 }
michael@0 3250
michael@0 3251 if (nsHTMLEditUtils::IsList(curNode)) {
michael@0 3252 // do we have a curList already?
michael@0 3253 if (curList && !nsEditorUtils::IsDescendantOf(curNode, curList)) {
michael@0 3254 // move all of our children into curList. cheezy way to do it: move
michael@0 3255 // whole list and then RemoveContainer() on the list. ConvertListType
michael@0 3256 // first: that routine handles converting the list item types, if
michael@0 3257 // needed
michael@0 3258 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3259 res = mHTMLEditor->MoveNode(curNode, curList, -1);
michael@0 3260 NS_ENSURE_SUCCESS(res, res);
michael@0 3261 res = ConvertListType(curNode, address_of(newBlock), listTypeAtom,
michael@0 3262 itemType);
michael@0 3263 NS_ENSURE_SUCCESS(res, res);
michael@0 3264 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3265 res = mHTMLEditor->RemoveBlockContainer(newBlock);
michael@0 3266 NS_ENSURE_SUCCESS(res, res);
michael@0 3267 } else {
michael@0 3268 // replace list with new list type
michael@0 3269 res = ConvertListType(curNode, address_of(newBlock), listTypeAtom,
michael@0 3270 itemType);
michael@0 3271 NS_ENSURE_SUCCESS(res, res);
michael@0 3272 curList = newBlock;
michael@0 3273 }
michael@0 3274 prevListItem = 0;
michael@0 3275 continue;
michael@0 3276 }
michael@0 3277
michael@0 3278 if (nsHTMLEditUtils::IsListItem(curNode)) {
michael@0 3279 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3280 if (mHTMLEditor->GetTag(curParent) != listTypeAtom) {
michael@0 3281 // list item is in wrong type of list. if we don't have a curList,
michael@0 3282 // split the old list and make a new list of correct type.
michael@0 3283 if (!curList || nsEditorUtils::IsDescendantOf(curNode, curList)) {
michael@0 3284 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3285 res = mHTMLEditor->SplitNode(curParent, offset,
michael@0 3286 getter_AddRefs(newBlock));
michael@0 3287 NS_ENSURE_SUCCESS(res, res);
michael@0 3288 int32_t offset;
michael@0 3289 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(curParent, &offset);
michael@0 3290 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3291 res = mHTMLEditor->CreateNode(*aListType, parent, offset,
michael@0 3292 getter_AddRefs(curList));
michael@0 3293 NS_ENSURE_SUCCESS(res, res);
michael@0 3294 }
michael@0 3295 // move list item to new list
michael@0 3296 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3297 res = mHTMLEditor->MoveNode(curNode, curList, -1);
michael@0 3298 NS_ENSURE_SUCCESS(res, res);
michael@0 3299 // convert list item type if needed
michael@0 3300 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3301 if (!mHTMLEditor->NodeIsType(curNode, itemType)) {
michael@0 3302 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3303 res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock),
michael@0 3304 nsDependentAtomString(itemType));
michael@0 3305 NS_ENSURE_SUCCESS(res, res);
michael@0 3306 }
michael@0 3307 } else {
michael@0 3308 // item is in right type of list. But we might still have to move it.
michael@0 3309 // and we might need to convert list item types.
michael@0 3310 if (!curList) {
michael@0 3311 curList = curParent;
michael@0 3312 } else if (curParent != curList) {
michael@0 3313 // move list item to new list
michael@0 3314 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3315 res = mHTMLEditor->MoveNode(curNode, curList, -1);
michael@0 3316 NS_ENSURE_SUCCESS(res, res);
michael@0 3317 }
michael@0 3318 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3319 if (!mHTMLEditor->NodeIsType(curNode, itemType)) {
michael@0 3320 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3321 res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock),
michael@0 3322 nsDependentAtomString(itemType));
michael@0 3323 NS_ENSURE_SUCCESS(res, res);
michael@0 3324 }
michael@0 3325 }
michael@0 3326 nsCOMPtr<nsIDOMElement> curElement = do_QueryInterface(curNode);
michael@0 3327 NS_NAMED_LITERAL_STRING(typestr, "type");
michael@0 3328 if (aBulletType && !aBulletType->IsEmpty()) {
michael@0 3329 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3330 res = mHTMLEditor->SetAttribute(curElement, typestr, *aBulletType);
michael@0 3331 } else {
michael@0 3332 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3333 res = mHTMLEditor->RemoveAttribute(curElement, typestr);
michael@0 3334 }
michael@0 3335 NS_ENSURE_SUCCESS(res, res);
michael@0 3336 continue;
michael@0 3337 }
michael@0 3338
michael@0 3339 // if we hit a div clear our prevListItem, insert divs contents
michael@0 3340 // into our node array, and remove the div
michael@0 3341 if (nsHTMLEditUtils::IsDiv(curNode)) {
michael@0 3342 prevListItem = nullptr;
michael@0 3343 int32_t j = i + 1;
michael@0 3344 res = GetInnerContent(curNode, arrayOfNodes, &j);
michael@0 3345 NS_ENSURE_SUCCESS(res, res);
michael@0 3346 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3347 res = mHTMLEditor->RemoveContainer(curNode);
michael@0 3348 NS_ENSURE_SUCCESS(res, res);
michael@0 3349 listCount = arrayOfNodes.Count();
michael@0 3350 continue;
michael@0 3351 }
michael@0 3352
michael@0 3353 // need to make a list to put things in if we haven't already,
michael@0 3354 if (!curList) {
michael@0 3355 res = SplitAsNeeded(aListType, address_of(curParent), &offset);
michael@0 3356 NS_ENSURE_SUCCESS(res, res);
michael@0 3357 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3358 res = mHTMLEditor->CreateNode(*aListType, curParent, offset,
michael@0 3359 getter_AddRefs(curList));
michael@0 3360 NS_ENSURE_SUCCESS(res, res);
michael@0 3361 // remember our new block for postprocessing
michael@0 3362 mNewBlock = curList;
michael@0 3363 // curList is now the correct thing to put curNode in
michael@0 3364 prevListItem = 0;
michael@0 3365 }
michael@0 3366
michael@0 3367 // if curNode isn't a list item, we must wrap it in one
michael@0 3368 nsCOMPtr<nsIDOMNode> listItem;
michael@0 3369 if (!nsHTMLEditUtils::IsListItem(curNode)) {
michael@0 3370 if (IsInlineNode(curNode) && prevListItem) {
michael@0 3371 // this is a continuation of some inline nodes that belong together in
michael@0 3372 // the same list item. use prevListItem
michael@0 3373 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3374 res = mHTMLEditor->MoveNode(curNode, prevListItem, -1);
michael@0 3375 NS_ENSURE_SUCCESS(res, res);
michael@0 3376 } else {
michael@0 3377 // don't wrap li around a paragraph. instead replace paragraph with li
michael@0 3378 if (nsHTMLEditUtils::IsParagraph(curNode)) {
michael@0 3379 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3380 res = mHTMLEditor->ReplaceContainer(curNode, address_of(listItem),
michael@0 3381 nsDependentAtomString(itemType));
michael@0 3382 } else {
michael@0 3383 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3384 res = mHTMLEditor->InsertContainerAbove(curNode, address_of(listItem),
michael@0 3385 nsDependentAtomString(itemType));
michael@0 3386 }
michael@0 3387 NS_ENSURE_SUCCESS(res, res);
michael@0 3388 if (IsInlineNode(curNode)) {
michael@0 3389 prevListItem = listItem;
michael@0 3390 } else {
michael@0 3391 prevListItem = nullptr;
michael@0 3392 }
michael@0 3393 }
michael@0 3394 } else {
michael@0 3395 listItem = curNode;
michael@0 3396 }
michael@0 3397
michael@0 3398 if (listItem) {
michael@0 3399 // if we made a new list item, deal with it: tuck the listItem into the
michael@0 3400 // end of the active list
michael@0 3401 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3402 res = mHTMLEditor->MoveNode(listItem, curList, -1);
michael@0 3403 NS_ENSURE_SUCCESS(res, res);
michael@0 3404 }
michael@0 3405 }
michael@0 3406
michael@0 3407 return res;
michael@0 3408 }
michael@0 3409
michael@0 3410
michael@0 3411 nsresult
michael@0 3412 nsHTMLEditRules::WillRemoveList(Selection* aSelection,
michael@0 3413 bool aOrdered,
michael@0 3414 bool *aCancel,
michael@0 3415 bool *aHandled)
michael@0 3416 {
michael@0 3417 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 3418 // initialize out param
michael@0 3419 *aCancel = false;
michael@0 3420 *aHandled = true;
michael@0 3421
michael@0 3422 nsresult res = NormalizeSelection(aSelection);
michael@0 3423 NS_ENSURE_SUCCESS(res, res);
michael@0 3424 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3425 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
michael@0 3426
michael@0 3427 nsCOMArray<nsIDOMRange> arrayOfRanges;
michael@0 3428 res = GetPromotedRanges(aSelection, arrayOfRanges, EditAction::makeList);
michael@0 3429 NS_ENSURE_SUCCESS(res, res);
michael@0 3430
michael@0 3431 // use these ranges to contruct a list of nodes to act on.
michael@0 3432 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 3433 res = GetListActionNodes(arrayOfNodes, false);
michael@0 3434 NS_ENSURE_SUCCESS(res, res);
michael@0 3435
michael@0 3436 // Remove all non-editable nodes. Leave them be.
michael@0 3437 int32_t listCount = arrayOfNodes.Count();
michael@0 3438 int32_t i;
michael@0 3439 for (i=listCount-1; i>=0; i--)
michael@0 3440 {
michael@0 3441 nsIDOMNode* testNode = arrayOfNodes[i];
michael@0 3442 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3443 if (!mHTMLEditor->IsEditable(testNode))
michael@0 3444 {
michael@0 3445 arrayOfNodes.RemoveObjectAt(i);
michael@0 3446 }
michael@0 3447 }
michael@0 3448
michael@0 3449 // reset list count
michael@0 3450 listCount = arrayOfNodes.Count();
michael@0 3451
michael@0 3452 // Only act on lists or list items in the array
michael@0 3453 nsCOMPtr<nsIDOMNode> curParent;
michael@0 3454 for (i=0; i<listCount; i++)
michael@0 3455 {
michael@0 3456 // here's where we actually figure out what to do
michael@0 3457 nsIDOMNode* curNode = arrayOfNodes[i];
michael@0 3458 int32_t offset;
michael@0 3459 curParent = nsEditor::GetNodeLocation(curNode, &offset);
michael@0 3460
michael@0 3461 if (nsHTMLEditUtils::IsListItem(curNode)) // unlist this listitem
michael@0 3462 {
michael@0 3463 bool bOutOfList;
michael@0 3464 do
michael@0 3465 {
michael@0 3466 res = PopListItem(curNode, &bOutOfList);
michael@0 3467 NS_ENSURE_SUCCESS(res, res);
michael@0 3468 } while (!bOutOfList); // keep popping it out until it's not in a list anymore
michael@0 3469 }
michael@0 3470 else if (nsHTMLEditUtils::IsList(curNode)) // node is a list, move list items out
michael@0 3471 {
michael@0 3472 res = RemoveListStructure(curNode);
michael@0 3473 NS_ENSURE_SUCCESS(res, res);
michael@0 3474 }
michael@0 3475 }
michael@0 3476 return res;
michael@0 3477 }
michael@0 3478
michael@0 3479
michael@0 3480 nsresult
michael@0 3481 nsHTMLEditRules::WillMakeDefListItem(Selection* aSelection,
michael@0 3482 const nsAString *aItemType,
michael@0 3483 bool aEntireList,
michael@0 3484 bool *aCancel,
michael@0 3485 bool *aHandled)
michael@0 3486 {
michael@0 3487 // for now we let WillMakeList handle this
michael@0 3488 NS_NAMED_LITERAL_STRING(listType, "dl");
michael@0 3489 return WillMakeList(aSelection, &listType, aEntireList, nullptr, aCancel, aHandled, aItemType);
michael@0 3490 }
michael@0 3491
michael@0 3492 nsresult
michael@0 3493 nsHTMLEditRules::WillMakeBasicBlock(Selection* aSelection,
michael@0 3494 const nsAString *aBlockType,
michael@0 3495 bool *aCancel,
michael@0 3496 bool *aHandled)
michael@0 3497 {
michael@0 3498 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 3499 // initialize out param
michael@0 3500 *aCancel = false;
michael@0 3501 *aHandled = false;
michael@0 3502
michael@0 3503 nsresult res = WillInsert(aSelection, aCancel);
michael@0 3504 NS_ENSURE_SUCCESS(res, res);
michael@0 3505 // initialize out param
michael@0 3506 // we want to ignore result of WillInsert()
michael@0 3507 *aCancel = false;
michael@0 3508 res = NormalizeSelection(aSelection);
michael@0 3509 NS_ENSURE_SUCCESS(res, res);
michael@0 3510 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3511 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
michael@0 3512 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3513 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
michael@0 3514 *aHandled = true;
michael@0 3515 nsString tString(*aBlockType);
michael@0 3516
michael@0 3517 // contruct a list of nodes to act on.
michael@0 3518 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 3519 res = GetNodesFromSelection(aSelection, EditAction::makeBasicBlock,
michael@0 3520 arrayOfNodes);
michael@0 3521 NS_ENSURE_SUCCESS(res, res);
michael@0 3522
michael@0 3523 // Remove all non-editable nodes. Leave them be.
michael@0 3524 int32_t listCount = arrayOfNodes.Count();
michael@0 3525 int32_t i;
michael@0 3526 for (i=listCount-1; i>=0; i--)
michael@0 3527 {
michael@0 3528 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3529 if (!mHTMLEditor->IsEditable(arrayOfNodes[i]))
michael@0 3530 {
michael@0 3531 arrayOfNodes.RemoveObjectAt(i);
michael@0 3532 }
michael@0 3533 }
michael@0 3534
michael@0 3535 // reset list count
michael@0 3536 listCount = arrayOfNodes.Count();
michael@0 3537
michael@0 3538 // if nothing visible in list, make an empty block
michael@0 3539 if (ListIsEmptyLine(arrayOfNodes))
michael@0 3540 {
michael@0 3541 nsCOMPtr<nsIDOMNode> parent, theBlock;
michael@0 3542 int32_t offset;
michael@0 3543
michael@0 3544 // get selection location
michael@0 3545 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3546 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
michael@0 3547 NS_ENSURE_SUCCESS(res, res);
michael@0 3548 if (tString.EqualsLiteral("normal") ||
michael@0 3549 tString.IsEmpty() ) // we are removing blocks (going to "body text")
michael@0 3550 {
michael@0 3551 nsCOMPtr<nsIDOMNode> curBlock = parent;
michael@0 3552 if (!IsBlockNode(curBlock)) {
michael@0 3553 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3554 curBlock = mHTMLEditor->GetBlockNodeParent(parent);
michael@0 3555 }
michael@0 3556 nsCOMPtr<nsIDOMNode> curBlockPar;
michael@0 3557 NS_ENSURE_TRUE(curBlock, NS_ERROR_NULL_POINTER);
michael@0 3558 curBlock->GetParentNode(getter_AddRefs(curBlockPar));
michael@0 3559 if (nsHTMLEditUtils::IsFormatNode(curBlock))
michael@0 3560 {
michael@0 3561 // if the first editable node after selection is a br, consume it. Otherwise
michael@0 3562 // it gets pushed into a following block after the split, which is visually bad.
michael@0 3563 nsCOMPtr<nsIDOMNode> brNode;
michael@0 3564 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3565 res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode));
michael@0 3566 NS_ENSURE_SUCCESS(res, res);
michael@0 3567 if (brNode && nsTextEditUtils::IsBreak(brNode))
michael@0 3568 {
michael@0 3569 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3570 res = mHTMLEditor->DeleteNode(brNode);
michael@0 3571 NS_ENSURE_SUCCESS(res, res);
michael@0 3572 }
michael@0 3573 // do the splits!
michael@0 3574 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3575 res = mHTMLEditor->SplitNodeDeep(curBlock, parent, offset, &offset, true);
michael@0 3576 NS_ENSURE_SUCCESS(res, res);
michael@0 3577 // put a br at the split point
michael@0 3578 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3579 res = mHTMLEditor->CreateBR(curBlockPar, offset, address_of(brNode));
michael@0 3580 NS_ENSURE_SUCCESS(res, res);
michael@0 3581 // put selection at the split point
michael@0 3582 res = aSelection->Collapse(curBlockPar, offset);
michael@0 3583 selectionResetter.Abort(); // to prevent selection reseter from overriding us.
michael@0 3584 *aHandled = true;
michael@0 3585 }
michael@0 3586 // else nothing to do!
michael@0 3587 }
michael@0 3588 else // we are making a block
michael@0 3589 {
michael@0 3590 // consume a br, if needed
michael@0 3591 nsCOMPtr<nsIDOMNode> brNode;
michael@0 3592 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3593 res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode), true);
michael@0 3594 NS_ENSURE_SUCCESS(res, res);
michael@0 3595 if (brNode && nsTextEditUtils::IsBreak(brNode))
michael@0 3596 {
michael@0 3597 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3598 res = mHTMLEditor->DeleteNode(brNode);
michael@0 3599 NS_ENSURE_SUCCESS(res, res);
michael@0 3600 // we don't need to act on this node any more
michael@0 3601 arrayOfNodes.RemoveObject(brNode);
michael@0 3602 }
michael@0 3603 // make sure we can put a block here
michael@0 3604 res = SplitAsNeeded(aBlockType, address_of(parent), &offset);
michael@0 3605 NS_ENSURE_SUCCESS(res, res);
michael@0 3606 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3607 res = mHTMLEditor->CreateNode(*aBlockType, parent, offset, getter_AddRefs(theBlock));
michael@0 3608 NS_ENSURE_SUCCESS(res, res);
michael@0 3609 // remember our new block for postprocessing
michael@0 3610 mNewBlock = theBlock;
michael@0 3611 // delete anything that was in the list of nodes
michael@0 3612 for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j)
michael@0 3613 {
michael@0 3614 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0];
michael@0 3615 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3616 res = mHTMLEditor->DeleteNode(curNode);
michael@0 3617 NS_ENSURE_SUCCESS(res, res);
michael@0 3618 arrayOfNodes.RemoveObjectAt(0);
michael@0 3619 }
michael@0 3620 // put selection in new block
michael@0 3621 res = aSelection->Collapse(theBlock,0);
michael@0 3622 selectionResetter.Abort(); // to prevent selection reseter from overriding us.
michael@0 3623 *aHandled = true;
michael@0 3624 }
michael@0 3625 return res;
michael@0 3626 }
michael@0 3627 else
michael@0 3628 {
michael@0 3629 // Ok, now go through all the nodes and make the right kind of blocks,
michael@0 3630 // or whatever is approriate. Wohoo!
michael@0 3631 // Note: blockquote is handled a little differently
michael@0 3632 if (tString.EqualsLiteral("blockquote"))
michael@0 3633 res = MakeBlockquote(arrayOfNodes);
michael@0 3634 else if (tString.EqualsLiteral("normal") ||
michael@0 3635 tString.IsEmpty() )
michael@0 3636 res = RemoveBlockStyle(arrayOfNodes);
michael@0 3637 else
michael@0 3638 res = ApplyBlockStyle(arrayOfNodes, aBlockType);
michael@0 3639 return res;
michael@0 3640 }
michael@0 3641 return res;
michael@0 3642 }
michael@0 3643
michael@0 3644 nsresult
michael@0 3645 nsHTMLEditRules::DidMakeBasicBlock(nsISelection *aSelection,
michael@0 3646 nsRulesInfo *aInfo, nsresult aResult)
michael@0 3647 {
michael@0 3648 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
michael@0 3649 // check for empty block. if so, put a moz br in it.
michael@0 3650 if (!aSelection->Collapsed()) {
michael@0 3651 return NS_OK;
michael@0 3652 }
michael@0 3653
michael@0 3654 nsCOMPtr<nsIDOMNode> parent;
michael@0 3655 int32_t offset;
michael@0 3656 nsresult res = nsEditor::GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
michael@0 3657 NS_ENSURE_SUCCESS(res, res);
michael@0 3658 res = InsertMozBRIfNeeded(parent);
michael@0 3659 return res;
michael@0 3660 }
michael@0 3661
michael@0 3662 nsresult
michael@0 3663 nsHTMLEditRules::WillIndent(Selection* aSelection,
michael@0 3664 bool* aCancel, bool* aHandled)
michael@0 3665 {
michael@0 3666 nsresult res;
michael@0 3667 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3668 if (mHTMLEditor->IsCSSEnabled()) {
michael@0 3669 res = WillCSSIndent(aSelection, aCancel, aHandled);
michael@0 3670 }
michael@0 3671 else {
michael@0 3672 res = WillHTMLIndent(aSelection, aCancel, aHandled);
michael@0 3673 }
michael@0 3674 return res;
michael@0 3675 }
michael@0 3676
michael@0 3677 nsresult
michael@0 3678 nsHTMLEditRules::WillCSSIndent(Selection* aSelection,
michael@0 3679 bool* aCancel, bool* aHandled)
michael@0 3680 {
michael@0 3681 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 3682
michael@0 3683 nsresult res = WillInsert(aSelection, aCancel);
michael@0 3684 NS_ENSURE_SUCCESS(res, res);
michael@0 3685
michael@0 3686 // initialize out param
michael@0 3687 // we want to ignore result of WillInsert()
michael@0 3688 *aCancel = false;
michael@0 3689 *aHandled = true;
michael@0 3690
michael@0 3691 res = NormalizeSelection(aSelection);
michael@0 3692 NS_ENSURE_SUCCESS(res, res);
michael@0 3693 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3694 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
michael@0 3695 nsCOMArray<nsIDOMRange> arrayOfRanges;
michael@0 3696 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 3697
michael@0 3698 // short circuit: detect case of collapsed selection inside an <li>.
michael@0 3699 // just sublist that <li>. This prevents bug 97797.
michael@0 3700
michael@0 3701 nsCOMPtr<nsIDOMNode> liNode;
michael@0 3702 if (aSelection->Collapsed()) {
michael@0 3703 nsCOMPtr<nsIDOMNode> node, block;
michael@0 3704 int32_t offset;
michael@0 3705 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3706 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(node), &offset);
michael@0 3707 NS_ENSURE_SUCCESS(res, res);
michael@0 3708 if (IsBlockNode(node)) {
michael@0 3709 block = node;
michael@0 3710 } else {
michael@0 3711 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3712 block = mHTMLEditor->GetBlockNodeParent(node);
michael@0 3713 }
michael@0 3714 if (block && nsHTMLEditUtils::IsListItem(block))
michael@0 3715 liNode = block;
michael@0 3716 }
michael@0 3717
michael@0 3718 if (liNode)
michael@0 3719 {
michael@0 3720 arrayOfNodes.AppendObject(liNode);
michael@0 3721 }
michael@0 3722 else
michael@0 3723 {
michael@0 3724 // convert the selection ranges into "promoted" selection ranges:
michael@0 3725 // this basically just expands the range to include the immediate
michael@0 3726 // block parent, and then further expands to include any ancestors
michael@0 3727 // whose children are all in the range
michael@0 3728 res = GetNodesFromSelection(aSelection, EditAction::indent, arrayOfNodes);
michael@0 3729 NS_ENSURE_SUCCESS(res, res);
michael@0 3730 }
michael@0 3731
michael@0 3732 NS_NAMED_LITERAL_STRING(quoteType, "blockquote");
michael@0 3733 // if nothing visible in list, make an empty block
michael@0 3734 if (ListIsEmptyLine(arrayOfNodes))
michael@0 3735 {
michael@0 3736 nsCOMPtr<nsIDOMNode> parent, theBlock;
michael@0 3737 int32_t offset;
michael@0 3738 nsAutoString quoteType(NS_LITERAL_STRING("div"));
michael@0 3739 // get selection location
michael@0 3740 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3741 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
michael@0 3742 NS_ENSURE_SUCCESS(res, res);
michael@0 3743 // make sure we can put a block here
michael@0 3744 res = SplitAsNeeded(&quoteType, address_of(parent), &offset);
michael@0 3745 NS_ENSURE_SUCCESS(res, res);
michael@0 3746 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3747 res = mHTMLEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock));
michael@0 3748 NS_ENSURE_SUCCESS(res, res);
michael@0 3749 // remember our new block for postprocessing
michael@0 3750 mNewBlock = theBlock;
michael@0 3751 RelativeChangeIndentationOfElementNode(theBlock, +1);
michael@0 3752 // delete anything that was in the list of nodes
michael@0 3753 for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j)
michael@0 3754 {
michael@0 3755 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0];
michael@0 3756 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3757 res = mHTMLEditor->DeleteNode(curNode);
michael@0 3758 NS_ENSURE_SUCCESS(res, res);
michael@0 3759 arrayOfNodes.RemoveObjectAt(0);
michael@0 3760 }
michael@0 3761 // put selection in new block
michael@0 3762 res = aSelection->Collapse(theBlock,0);
michael@0 3763 selectionResetter.Abort(); // to prevent selection reseter from overriding us.
michael@0 3764 *aHandled = true;
michael@0 3765 return res;
michael@0 3766 }
michael@0 3767
michael@0 3768 // Ok, now go through all the nodes and put them in a blockquote,
michael@0 3769 // or whatever is appropriate. Wohoo!
michael@0 3770 int32_t i;
michael@0 3771 nsCOMPtr<nsIDOMNode> curParent;
michael@0 3772 nsCOMPtr<nsIDOMNode> curQuote;
michael@0 3773 nsCOMPtr<nsIDOMNode> curList;
michael@0 3774 nsCOMPtr<nsIDOMNode> sibling;
michael@0 3775 int32_t listCount = arrayOfNodes.Count();
michael@0 3776 for (i=0; i<listCount; i++)
michael@0 3777 {
michael@0 3778 // here's where we actually figure out what to do
michael@0 3779 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
michael@0 3780
michael@0 3781 // Ignore all non-editable nodes. Leave them be.
michael@0 3782 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3783 if (!mHTMLEditor->IsEditable(curNode)) continue;
michael@0 3784
michael@0 3785 int32_t offset;
michael@0 3786 curParent = nsEditor::GetNodeLocation(curNode, &offset);
michael@0 3787
michael@0 3788 // some logic for putting list items into nested lists...
michael@0 3789 if (nsHTMLEditUtils::IsList(curParent))
michael@0 3790 {
michael@0 3791 sibling = nullptr;
michael@0 3792
michael@0 3793 // Check for whether we should join a list that follows curNode.
michael@0 3794 // We do this if the next element is a list, and the list is of the
michael@0 3795 // same type (li/ol) as curNode was a part it.
michael@0 3796 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3797 mHTMLEditor->GetNextHTMLSibling(curNode, address_of(sibling));
michael@0 3798 if (sibling && nsHTMLEditUtils::IsList(sibling))
michael@0 3799 {
michael@0 3800 nsAutoString curListTag, siblingListTag;
michael@0 3801 nsEditor::GetTagString(curParent, curListTag);
michael@0 3802 nsEditor::GetTagString(sibling, siblingListTag);
michael@0 3803 if (curListTag == siblingListTag)
michael@0 3804 {
michael@0 3805 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3806 res = mHTMLEditor->MoveNode(curNode, sibling, 0);
michael@0 3807 NS_ENSURE_SUCCESS(res, res);
michael@0 3808 continue;
michael@0 3809 }
michael@0 3810 }
michael@0 3811 // Check for whether we should join a list that preceeds curNode.
michael@0 3812 // We do this if the previous element is a list, and the list is of
michael@0 3813 // the same type (li/ol) as curNode was a part of.
michael@0 3814 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3815 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
michael@0 3816 if (sibling && nsHTMLEditUtils::IsList(sibling))
michael@0 3817 {
michael@0 3818 nsAutoString curListTag, siblingListTag;
michael@0 3819 nsEditor::GetTagString(curParent, curListTag);
michael@0 3820 nsEditor::GetTagString(sibling, siblingListTag);
michael@0 3821 if (curListTag == siblingListTag)
michael@0 3822 {
michael@0 3823 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3824 res = mHTMLEditor->MoveNode(curNode, sibling, -1);
michael@0 3825 NS_ENSURE_SUCCESS(res, res);
michael@0 3826 continue;
michael@0 3827 }
michael@0 3828 }
michael@0 3829 sibling = nullptr;
michael@0 3830
michael@0 3831 // check to see if curList is still appropriate. Which it is if
michael@0 3832 // curNode is still right after it in the same list.
michael@0 3833 if (curList) {
michael@0 3834 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3835 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
michael@0 3836 }
michael@0 3837
michael@0 3838 if (!curList || (sibling && sibling != curList))
michael@0 3839 {
michael@0 3840 nsAutoString listTag;
michael@0 3841 nsEditor::GetTagString(curParent,listTag);
michael@0 3842 ToLowerCase(listTag);
michael@0 3843 // create a new nested list of correct type
michael@0 3844 res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
michael@0 3845 NS_ENSURE_SUCCESS(res, res);
michael@0 3846 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3847 res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList));
michael@0 3848 NS_ENSURE_SUCCESS(res, res);
michael@0 3849 // curList is now the correct thing to put curNode in
michael@0 3850 // remember our new block for postprocessing
michael@0 3851 mNewBlock = curList;
michael@0 3852 }
michael@0 3853 // tuck the node into the end of the active list
michael@0 3854 uint32_t listLen;
michael@0 3855 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3856 res = mHTMLEditor->GetLengthOfDOMNode(curList, listLen);
michael@0 3857 NS_ENSURE_SUCCESS(res, res);
michael@0 3858 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3859 res = mHTMLEditor->MoveNode(curNode, curList, listLen);
michael@0 3860 NS_ENSURE_SUCCESS(res, res);
michael@0 3861 }
michael@0 3862
michael@0 3863 else // not a list item
michael@0 3864 {
michael@0 3865 if (IsBlockNode(curNode)) {
michael@0 3866 RelativeChangeIndentationOfElementNode(curNode, +1);
michael@0 3867 curQuote = nullptr;
michael@0 3868 }
michael@0 3869 else {
michael@0 3870 if (!curQuote)
michael@0 3871 {
michael@0 3872 // First, check that our element can contain a div.
michael@0 3873 if (!mEditor->CanContainTag(curParent, nsGkAtoms::div)) {
michael@0 3874 return NS_OK; // cancelled
michael@0 3875 }
michael@0 3876
michael@0 3877 NS_NAMED_LITERAL_STRING(divquoteType, "div");
michael@0 3878 res = SplitAsNeeded(&divquoteType, address_of(curParent), &offset);
michael@0 3879 NS_ENSURE_SUCCESS(res, res);
michael@0 3880 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3881 res = mHTMLEditor->CreateNode(divquoteType, curParent, offset, getter_AddRefs(curQuote));
michael@0 3882 NS_ENSURE_SUCCESS(res, res);
michael@0 3883 RelativeChangeIndentationOfElementNode(curQuote, +1);
michael@0 3884 // remember our new block for postprocessing
michael@0 3885 mNewBlock = curQuote;
michael@0 3886 // curQuote is now the correct thing to put curNode in
michael@0 3887 }
michael@0 3888
michael@0 3889 // tuck the node into the end of the active blockquote
michael@0 3890 uint32_t quoteLen;
michael@0 3891 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3892 res = mHTMLEditor->GetLengthOfDOMNode(curQuote, quoteLen);
michael@0 3893 NS_ENSURE_SUCCESS(res, res);
michael@0 3894 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3895 res = mHTMLEditor->MoveNode(curNode, curQuote, quoteLen);
michael@0 3896 NS_ENSURE_SUCCESS(res, res);
michael@0 3897 }
michael@0 3898 }
michael@0 3899 }
michael@0 3900 return res;
michael@0 3901 }
michael@0 3902
michael@0 3903 nsresult
michael@0 3904 nsHTMLEditRules::WillHTMLIndent(Selection* aSelection,
michael@0 3905 bool* aCancel, bool* aHandled)
michael@0 3906 {
michael@0 3907 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 3908 nsresult res = WillInsert(aSelection, aCancel);
michael@0 3909 NS_ENSURE_SUCCESS(res, res);
michael@0 3910
michael@0 3911 // initialize out param
michael@0 3912 // we want to ignore result of WillInsert()
michael@0 3913 *aCancel = false;
michael@0 3914 *aHandled = true;
michael@0 3915
michael@0 3916 res = NormalizeSelection(aSelection);
michael@0 3917 NS_ENSURE_SUCCESS(res, res);
michael@0 3918 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3919 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
michael@0 3920
michael@0 3921 // convert the selection ranges into "promoted" selection ranges:
michael@0 3922 // this basically just expands the range to include the immediate
michael@0 3923 // block parent, and then further expands to include any ancestors
michael@0 3924 // whose children are all in the range
michael@0 3925
michael@0 3926 nsCOMArray<nsIDOMRange> arrayOfRanges;
michael@0 3927 res = GetPromotedRanges(aSelection, arrayOfRanges, EditAction::indent);
michael@0 3928 NS_ENSURE_SUCCESS(res, res);
michael@0 3929
michael@0 3930 // use these ranges to contruct a list of nodes to act on.
michael@0 3931 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 3932 res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditAction::indent);
michael@0 3933 NS_ENSURE_SUCCESS(res, res);
michael@0 3934
michael@0 3935 NS_NAMED_LITERAL_STRING(quoteType, "blockquote");
michael@0 3936
michael@0 3937 // if nothing visible in list, make an empty block
michael@0 3938 if (ListIsEmptyLine(arrayOfNodes))
michael@0 3939 {
michael@0 3940 nsCOMPtr<nsIDOMNode> parent, theBlock;
michael@0 3941 int32_t offset;
michael@0 3942
michael@0 3943 // get selection location
michael@0 3944 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3945 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
michael@0 3946 NS_ENSURE_SUCCESS(res, res);
michael@0 3947 // make sure we can put a block here
michael@0 3948 res = SplitAsNeeded(&quoteType, address_of(parent), &offset);
michael@0 3949 NS_ENSURE_SUCCESS(res, res);
michael@0 3950 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3951 res = mHTMLEditor->CreateNode(quoteType, parent, offset, getter_AddRefs(theBlock));
michael@0 3952 NS_ENSURE_SUCCESS(res, res);
michael@0 3953 // remember our new block for postprocessing
michael@0 3954 mNewBlock = theBlock;
michael@0 3955 // delete anything that was in the list of nodes
michael@0 3956 for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j)
michael@0 3957 {
michael@0 3958 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0];
michael@0 3959 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3960 res = mHTMLEditor->DeleteNode(curNode);
michael@0 3961 NS_ENSURE_SUCCESS(res, res);
michael@0 3962 arrayOfNodes.RemoveObjectAt(0);
michael@0 3963 }
michael@0 3964 // put selection in new block
michael@0 3965 res = aSelection->Collapse(theBlock,0);
michael@0 3966 selectionResetter.Abort(); // to prevent selection reseter from overriding us.
michael@0 3967 *aHandled = true;
michael@0 3968 return res;
michael@0 3969 }
michael@0 3970
michael@0 3971 // Ok, now go through all the nodes and put them in a blockquote,
michael@0 3972 // or whatever is appropriate. Wohoo!
michael@0 3973 int32_t i;
michael@0 3974 nsCOMPtr<nsIDOMNode> curParent, curQuote, curList, indentedLI, sibling;
michael@0 3975 int32_t listCount = arrayOfNodes.Count();
michael@0 3976 for (i=0; i<listCount; i++)
michael@0 3977 {
michael@0 3978 // here's where we actually figure out what to do
michael@0 3979 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
michael@0 3980
michael@0 3981 // Ignore all non-editable nodes. Leave them be.
michael@0 3982 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3983 if (!mHTMLEditor->IsEditable(curNode)) continue;
michael@0 3984
michael@0 3985 int32_t offset;
michael@0 3986 curParent = nsEditor::GetNodeLocation(curNode, &offset);
michael@0 3987
michael@0 3988 // some logic for putting list items into nested lists...
michael@0 3989 if (nsHTMLEditUtils::IsList(curParent))
michael@0 3990 {
michael@0 3991 sibling = nullptr;
michael@0 3992
michael@0 3993 // Check for whether we should join a list that follows curNode.
michael@0 3994 // We do this if the next element is a list, and the list is of the
michael@0 3995 // same type (li/ol) as curNode was a part it.
michael@0 3996 NS_ENSURE_STATE(mHTMLEditor);
michael@0 3997 mHTMLEditor->GetNextHTMLSibling(curNode, address_of(sibling));
michael@0 3998 if (sibling && nsHTMLEditUtils::IsList(sibling))
michael@0 3999 {
michael@0 4000 nsAutoString curListTag, siblingListTag;
michael@0 4001 nsEditor::GetTagString(curParent, curListTag);
michael@0 4002 nsEditor::GetTagString(sibling, siblingListTag);
michael@0 4003 if (curListTag == siblingListTag)
michael@0 4004 {
michael@0 4005 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4006 res = mHTMLEditor->MoveNode(curNode, sibling, 0);
michael@0 4007 NS_ENSURE_SUCCESS(res, res);
michael@0 4008 continue;
michael@0 4009 }
michael@0 4010 }
michael@0 4011
michael@0 4012 // Check for whether we should join a list that preceeds curNode.
michael@0 4013 // We do this if the previous element is a list, and the list is of
michael@0 4014 // the same type (li/ol) as curNode was a part of.
michael@0 4015 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4016 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
michael@0 4017 if (sibling && nsHTMLEditUtils::IsList(sibling))
michael@0 4018 {
michael@0 4019 nsAutoString curListTag, siblingListTag;
michael@0 4020 nsEditor::GetTagString(curParent, curListTag);
michael@0 4021 nsEditor::GetTagString(sibling, siblingListTag);
michael@0 4022 if (curListTag == siblingListTag)
michael@0 4023 {
michael@0 4024 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4025 res = mHTMLEditor->MoveNode(curNode, sibling, -1);
michael@0 4026 NS_ENSURE_SUCCESS(res, res);
michael@0 4027 continue;
michael@0 4028 }
michael@0 4029 }
michael@0 4030
michael@0 4031 sibling = nullptr;
michael@0 4032
michael@0 4033 // check to see if curList is still appropriate. Which it is if
michael@0 4034 // curNode is still right after it in the same list.
michael@0 4035 if (curList) {
michael@0 4036 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4037 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
michael@0 4038 }
michael@0 4039
michael@0 4040 if (!curList || (sibling && sibling != curList) )
michael@0 4041 {
michael@0 4042 nsAutoString listTag;
michael@0 4043 nsEditor::GetTagString(curParent,listTag);
michael@0 4044 ToLowerCase(listTag);
michael@0 4045 // create a new nested list of correct type
michael@0 4046 res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
michael@0 4047 NS_ENSURE_SUCCESS(res, res);
michael@0 4048 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4049 res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList));
michael@0 4050 NS_ENSURE_SUCCESS(res, res);
michael@0 4051 // curList is now the correct thing to put curNode in
michael@0 4052 // remember our new block for postprocessing
michael@0 4053 mNewBlock = curList;
michael@0 4054 }
michael@0 4055 // tuck the node into the end of the active list
michael@0 4056 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4057 res = mHTMLEditor->MoveNode(curNode, curList, -1);
michael@0 4058 NS_ENSURE_SUCCESS(res, res);
michael@0 4059 // forget curQuote, if any
michael@0 4060 curQuote = nullptr;
michael@0 4061 }
michael@0 4062
michael@0 4063 else // not a list item, use blockquote?
michael@0 4064 {
michael@0 4065 // if we are inside a list item, we don't want to blockquote, we want
michael@0 4066 // to sublist the list item. We may have several nodes listed in the
michael@0 4067 // array of nodes to act on, that are in the same list item. Since
michael@0 4068 // we only want to indent that li once, we must keep track of the most
michael@0 4069 // recent indented list item, and not indent it if we find another node
michael@0 4070 // to act on that is still inside the same li.
michael@0 4071 nsCOMPtr<nsIDOMNode> listitem=IsInListItem(curNode);
michael@0 4072 if (listitem)
michael@0 4073 {
michael@0 4074 if (indentedLI == listitem) continue; // already indented this list item
michael@0 4075 curParent = nsEditor::GetNodeLocation(listitem, &offset);
michael@0 4076 // check to see if curList is still appropriate. Which it is if
michael@0 4077 // curNode is still right after it in the same list.
michael@0 4078 if (curList)
michael@0 4079 {
michael@0 4080 sibling = nullptr;
michael@0 4081 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4082 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
michael@0 4083 }
michael@0 4084
michael@0 4085 if (!curList || (sibling && sibling != curList) )
michael@0 4086 {
michael@0 4087 nsAutoString listTag;
michael@0 4088 nsEditor::GetTagString(curParent,listTag);
michael@0 4089 ToLowerCase(listTag);
michael@0 4090 // create a new nested list of correct type
michael@0 4091 res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
michael@0 4092 NS_ENSURE_SUCCESS(res, res);
michael@0 4093 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4094 res = mHTMLEditor->CreateNode(listTag, curParent, offset, getter_AddRefs(curList));
michael@0 4095 NS_ENSURE_SUCCESS(res, res);
michael@0 4096 }
michael@0 4097 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4098 res = mHTMLEditor->MoveNode(listitem, curList, -1);
michael@0 4099 NS_ENSURE_SUCCESS(res, res);
michael@0 4100 // remember we indented this li
michael@0 4101 indentedLI = listitem;
michael@0 4102 }
michael@0 4103
michael@0 4104 else
michael@0 4105 {
michael@0 4106 // need to make a blockquote to put things in if we haven't already,
michael@0 4107 // or if this node doesn't go in blockquote we used earlier.
michael@0 4108 // One reason it might not go in prio blockquote is if we are now
michael@0 4109 // in a different table cell.
michael@0 4110 if (curQuote && InDifferentTableElements(curQuote, curNode)) {
michael@0 4111 curQuote = nullptr;
michael@0 4112 }
michael@0 4113
michael@0 4114 if (!curQuote)
michael@0 4115 {
michael@0 4116 // First, check that our element can contain a blockquote.
michael@0 4117 if (!mEditor->CanContainTag(curParent, nsGkAtoms::blockquote)) {
michael@0 4118 return NS_OK; // cancelled
michael@0 4119 }
michael@0 4120
michael@0 4121 res = SplitAsNeeded(&quoteType, address_of(curParent), &offset);
michael@0 4122 NS_ENSURE_SUCCESS(res, res);
michael@0 4123 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4124 res = mHTMLEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curQuote));
michael@0 4125 NS_ENSURE_SUCCESS(res, res);
michael@0 4126 // remember our new block for postprocessing
michael@0 4127 mNewBlock = curQuote;
michael@0 4128 // curQuote is now the correct thing to put curNode in
michael@0 4129 }
michael@0 4130
michael@0 4131 // tuck the node into the end of the active blockquote
michael@0 4132 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4133 res = mHTMLEditor->MoveNode(curNode, curQuote, -1);
michael@0 4134 NS_ENSURE_SUCCESS(res, res);
michael@0 4135 // forget curList, if any
michael@0 4136 curList = nullptr;
michael@0 4137 }
michael@0 4138 }
michael@0 4139 }
michael@0 4140 return res;
michael@0 4141 }
michael@0 4142
michael@0 4143
michael@0 4144 nsresult
michael@0 4145 nsHTMLEditRules::WillOutdent(Selection* aSelection,
michael@0 4146 bool* aCancel, bool* aHandled)
michael@0 4147 {
michael@0 4148 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 4149 // initialize out param
michael@0 4150 *aCancel = false;
michael@0 4151 *aHandled = true;
michael@0 4152 nsresult res = NS_OK;
michael@0 4153 nsCOMPtr<nsIDOMNode> rememberedLeftBQ, rememberedRightBQ;
michael@0 4154 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4155 bool useCSS = mHTMLEditor->IsCSSEnabled();
michael@0 4156
michael@0 4157 res = NormalizeSelection(aSelection);
michael@0 4158 NS_ENSURE_SUCCESS(res, res);
michael@0 4159 // some scoping for selection resetting - we may need to tweak it
michael@0 4160 {
michael@0 4161 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4162 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
michael@0 4163
michael@0 4164 // convert the selection ranges into "promoted" selection ranges:
michael@0 4165 // this basically just expands the range to include the immediate
michael@0 4166 // block parent, and then further expands to include any ancestors
michael@0 4167 // whose children are all in the range
michael@0 4168 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 4169 res = GetNodesFromSelection(aSelection, EditAction::outdent,
michael@0 4170 arrayOfNodes);
michael@0 4171 NS_ENSURE_SUCCESS(res, res);
michael@0 4172
michael@0 4173 // Ok, now go through all the nodes and remove a level of blockquoting,
michael@0 4174 // or whatever is appropriate. Wohoo!
michael@0 4175
michael@0 4176 nsCOMPtr<nsIDOMNode> curBlockQuote, firstBQChild, lastBQChild;
michael@0 4177 bool curBlockQuoteIsIndentedWithCSS = false;
michael@0 4178 int32_t listCount = arrayOfNodes.Count();
michael@0 4179 int32_t i;
michael@0 4180 nsCOMPtr<nsIDOMNode> curParent;
michael@0 4181 for (i=0; i<listCount; i++)
michael@0 4182 {
michael@0 4183 // here's where we actually figure out what to do
michael@0 4184 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
michael@0 4185 int32_t offset;
michael@0 4186 curParent = nsEditor::GetNodeLocation(curNode, &offset);
michael@0 4187
michael@0 4188 // is it a blockquote?
michael@0 4189 if (nsHTMLEditUtils::IsBlockquote(curNode))
michael@0 4190 {
michael@0 4191 // if it is a blockquote, remove it.
michael@0 4192 // So we need to finish up dealng with any curBlockQuote first.
michael@0 4193 if (curBlockQuote)
michael@0 4194 {
michael@0 4195 res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild,
michael@0 4196 curBlockQuoteIsIndentedWithCSS,
michael@0 4197 address_of(rememberedLeftBQ),
michael@0 4198 address_of(rememberedRightBQ));
michael@0 4199 NS_ENSURE_SUCCESS(res, res);
michael@0 4200 curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0;
michael@0 4201 curBlockQuoteIsIndentedWithCSS = false;
michael@0 4202 }
michael@0 4203 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4204 res = mHTMLEditor->RemoveBlockContainer(curNode);
michael@0 4205 NS_ENSURE_SUCCESS(res, res);
michael@0 4206 continue;
michael@0 4207 }
michael@0 4208 // is it a block with a 'margin' property?
michael@0 4209 if (useCSS && IsBlockNode(curNode))
michael@0 4210 {
michael@0 4211 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4212 nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode);
michael@0 4213 nsAutoString value;
michael@0 4214 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4215 mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(curNode, marginProperty, value);
michael@0 4216 float f;
michael@0 4217 nsCOMPtr<nsIAtom> unit;
michael@0 4218 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4219 mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit));
michael@0 4220 if (f > 0)
michael@0 4221 {
michael@0 4222 RelativeChangeIndentationOfElementNode(curNode, -1);
michael@0 4223 continue;
michael@0 4224 }
michael@0 4225 }
michael@0 4226 // is it a list item?
michael@0 4227 if (nsHTMLEditUtils::IsListItem(curNode))
michael@0 4228 {
michael@0 4229 // if it is a list item, that means we are not outdenting whole list.
michael@0 4230 // So we need to finish up dealing with any curBlockQuote, and then
michael@0 4231 // pop this list item.
michael@0 4232 if (curBlockQuote)
michael@0 4233 {
michael@0 4234 res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild,
michael@0 4235 curBlockQuoteIsIndentedWithCSS,
michael@0 4236 address_of(rememberedLeftBQ),
michael@0 4237 address_of(rememberedRightBQ));
michael@0 4238 NS_ENSURE_SUCCESS(res, res);
michael@0 4239 curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0;
michael@0 4240 curBlockQuoteIsIndentedWithCSS = false;
michael@0 4241 }
michael@0 4242 bool bOutOfList;
michael@0 4243 res = PopListItem(curNode, &bOutOfList);
michael@0 4244 NS_ENSURE_SUCCESS(res, res);
michael@0 4245 continue;
michael@0 4246 }
michael@0 4247 // do we have a blockquote that we are already committed to removing?
michael@0 4248 if (curBlockQuote)
michael@0 4249 {
michael@0 4250 // if so, is this node a descendant?
michael@0 4251 if (nsEditorUtils::IsDescendantOf(curNode, curBlockQuote))
michael@0 4252 {
michael@0 4253 lastBQChild = curNode;
michael@0 4254 continue; // then we don't need to do anything different for this node
michael@0 4255 }
michael@0 4256 else
michael@0 4257 {
michael@0 4258 // otherwise, we have progressed beyond end of curBlockQuote,
michael@0 4259 // so lets handle it now. We need to remove the portion of
michael@0 4260 // curBlockQuote that contains [firstBQChild - lastBQChild].
michael@0 4261 res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild,
michael@0 4262 curBlockQuoteIsIndentedWithCSS,
michael@0 4263 address_of(rememberedLeftBQ),
michael@0 4264 address_of(rememberedRightBQ));
michael@0 4265 NS_ENSURE_SUCCESS(res, res);
michael@0 4266 curBlockQuote = 0; firstBQChild = 0; lastBQChild = 0;
michael@0 4267 curBlockQuoteIsIndentedWithCSS = false;
michael@0 4268 // fall out and handle curNode
michael@0 4269 }
michael@0 4270 }
michael@0 4271
michael@0 4272 // are we inside a blockquote?
michael@0 4273 nsCOMPtr<nsIDOMNode> n = curNode;
michael@0 4274 nsCOMPtr<nsIDOMNode> tmp;
michael@0 4275 curBlockQuoteIsIndentedWithCSS = false;
michael@0 4276 // keep looking up the hierarchy as long as we don't hit the body or the
michael@0 4277 // active editing host or a table element (other than an entire table)
michael@0 4278 while (!nsTextEditUtils::IsBody(n) && mHTMLEditor &&
michael@0 4279 mHTMLEditor->IsDescendantOfEditorRoot(n)
michael@0 4280 && (nsHTMLEditUtils::IsTable(n) || !nsHTMLEditUtils::IsTableElement(n)))
michael@0 4281 {
michael@0 4282 n->GetParentNode(getter_AddRefs(tmp));
michael@0 4283 if (!tmp) {
michael@0 4284 break;
michael@0 4285 }
michael@0 4286 n = tmp;
michael@0 4287 if (nsHTMLEditUtils::IsBlockquote(n))
michael@0 4288 {
michael@0 4289 // if so, remember it, and remember first node we are taking out of it.
michael@0 4290 curBlockQuote = n;
michael@0 4291 firstBQChild = curNode;
michael@0 4292 lastBQChild = curNode;
michael@0 4293 break;
michael@0 4294 }
michael@0 4295 else if (useCSS)
michael@0 4296 {
michael@0 4297 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4298 nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, curNode);
michael@0 4299 nsAutoString value;
michael@0 4300 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4301 mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(n, marginProperty, value);
michael@0 4302 float f;
michael@0 4303 nsCOMPtr<nsIAtom> unit;
michael@0 4304 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4305 mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit));
michael@0 4306 if (f > 0 && !(nsHTMLEditUtils::IsList(curParent) && nsHTMLEditUtils::IsList(curNode)))
michael@0 4307 {
michael@0 4308 curBlockQuote = n;
michael@0 4309 firstBQChild = curNode;
michael@0 4310 lastBQChild = curNode;
michael@0 4311 curBlockQuoteIsIndentedWithCSS = true;
michael@0 4312 break;
michael@0 4313 }
michael@0 4314 }
michael@0 4315 }
michael@0 4316
michael@0 4317 if (!curBlockQuote)
michael@0 4318 {
michael@0 4319 // could not find an enclosing blockquote for this node. handle list cases.
michael@0 4320 if (nsHTMLEditUtils::IsList(curParent)) // move node out of list
michael@0 4321 {
michael@0 4322 if (nsHTMLEditUtils::IsList(curNode)) // just unwrap this sublist
michael@0 4323 {
michael@0 4324 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4325 res = mHTMLEditor->RemoveBlockContainer(curNode);
michael@0 4326 NS_ENSURE_SUCCESS(res, res);
michael@0 4327 }
michael@0 4328 // handled list item case above
michael@0 4329 }
michael@0 4330 else if (nsHTMLEditUtils::IsList(curNode)) // node is a list, but parent is non-list: move list items out
michael@0 4331 {
michael@0 4332 nsCOMPtr<nsIDOMNode> child;
michael@0 4333 curNode->GetLastChild(getter_AddRefs(child));
michael@0 4334 while (child)
michael@0 4335 {
michael@0 4336 if (nsHTMLEditUtils::IsListItem(child))
michael@0 4337 {
michael@0 4338 bool bOutOfList;
michael@0 4339 res = PopListItem(child, &bOutOfList);
michael@0 4340 NS_ENSURE_SUCCESS(res, res);
michael@0 4341 }
michael@0 4342 else if (nsHTMLEditUtils::IsList(child))
michael@0 4343 {
michael@0 4344 // We have an embedded list, so move it out from under the
michael@0 4345 // parent list. Be sure to put it after the parent list
michael@0 4346 // because this loop iterates backwards through the parent's
michael@0 4347 // list of children.
michael@0 4348
michael@0 4349 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4350 res = mHTMLEditor->MoveNode(child, curParent, offset + 1);
michael@0 4351 NS_ENSURE_SUCCESS(res, res);
michael@0 4352 }
michael@0 4353 else
michael@0 4354 {
michael@0 4355 // delete any non- list items for now
michael@0 4356 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4357 res = mHTMLEditor->DeleteNode(child);
michael@0 4358 NS_ENSURE_SUCCESS(res, res);
michael@0 4359 }
michael@0 4360 curNode->GetLastChild(getter_AddRefs(child));
michael@0 4361 }
michael@0 4362 // delete the now-empty list
michael@0 4363 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4364 res = mHTMLEditor->RemoveBlockContainer(curNode);
michael@0 4365 NS_ENSURE_SUCCESS(res, res);
michael@0 4366 }
michael@0 4367 else if (useCSS) {
michael@0 4368 nsCOMPtr<nsIDOMElement> element;
michael@0 4369 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(curNode);
michael@0 4370 if (textNode) {
michael@0 4371 // We want to outdent the parent of text nodes
michael@0 4372 nsCOMPtr<nsIDOMNode> parent;
michael@0 4373 textNode->GetParentNode(getter_AddRefs(parent));
michael@0 4374 element = do_QueryInterface(parent);
michael@0 4375 } else {
michael@0 4376 element = do_QueryInterface(curNode);
michael@0 4377 }
michael@0 4378 if (element) {
michael@0 4379 RelativeChangeIndentationOfElementNode(element, -1);
michael@0 4380 }
michael@0 4381 }
michael@0 4382 }
michael@0 4383 }
michael@0 4384 if (curBlockQuote)
michael@0 4385 {
michael@0 4386 // we have a blockquote we haven't finished handling
michael@0 4387 res = OutdentPartOfBlock(curBlockQuote, firstBQChild, lastBQChild,
michael@0 4388 curBlockQuoteIsIndentedWithCSS,
michael@0 4389 address_of(rememberedLeftBQ),
michael@0 4390 address_of(rememberedRightBQ));
michael@0 4391 NS_ENSURE_SUCCESS(res, res);
michael@0 4392 }
michael@0 4393 }
michael@0 4394 // make sure selection didn't stick to last piece of content in old bq
michael@0 4395 // (only a problem for collapsed selections)
michael@0 4396 if (rememberedLeftBQ || rememberedRightBQ) {
michael@0 4397 if (aSelection->Collapsed()) {
michael@0 4398 // push selection past end of rememberedLeftBQ
michael@0 4399 nsCOMPtr<nsIDOMNode> sNode;
michael@0 4400 int32_t sOffset;
michael@0 4401 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4402 mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(sNode), &sOffset);
michael@0 4403 if (rememberedLeftBQ &&
michael@0 4404 ((sNode == rememberedLeftBQ) || nsEditorUtils::IsDescendantOf(sNode, rememberedLeftBQ)))
michael@0 4405 {
michael@0 4406 // selection is inside rememberedLeftBQ - push it past it.
michael@0 4407 sNode = nsEditor::GetNodeLocation(rememberedLeftBQ, &sOffset);
michael@0 4408 sOffset++;
michael@0 4409 aSelection->Collapse(sNode, sOffset);
michael@0 4410 }
michael@0 4411 // and pull selection before beginning of rememberedRightBQ
michael@0 4412 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4413 mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(sNode), &sOffset);
michael@0 4414 if (rememberedRightBQ &&
michael@0 4415 ((sNode == rememberedRightBQ) || nsEditorUtils::IsDescendantOf(sNode, rememberedRightBQ)))
michael@0 4416 {
michael@0 4417 // selection is inside rememberedRightBQ - push it before it.
michael@0 4418 sNode = nsEditor::GetNodeLocation(rememberedRightBQ, &sOffset);
michael@0 4419 aSelection->Collapse(sNode, sOffset);
michael@0 4420 }
michael@0 4421 }
michael@0 4422 return NS_OK;
michael@0 4423 }
michael@0 4424 return res;
michael@0 4425 }
michael@0 4426
michael@0 4427
michael@0 4428 ///////////////////////////////////////////////////////////////////////////
michael@0 4429 // RemovePartOfBlock: split aBlock and move aStartChild to aEndChild out
michael@0 4430 // of aBlock. return left side of block (if any) in
michael@0 4431 // aLeftNode. return right side of block (if any) in
michael@0 4432 // aRightNode.
michael@0 4433 //
michael@0 4434 nsresult
michael@0 4435 nsHTMLEditRules::RemovePartOfBlock(nsIDOMNode *aBlock,
michael@0 4436 nsIDOMNode *aStartChild,
michael@0 4437 nsIDOMNode *aEndChild,
michael@0 4438 nsCOMPtr<nsIDOMNode> *aLeftNode,
michael@0 4439 nsCOMPtr<nsIDOMNode> *aRightNode)
michael@0 4440 {
michael@0 4441 nsCOMPtr<nsIDOMNode> middleNode;
michael@0 4442 nsresult res = SplitBlock(aBlock, aStartChild, aEndChild,
michael@0 4443 aLeftNode, aRightNode,
michael@0 4444 address_of(middleNode));
michael@0 4445 NS_ENSURE_SUCCESS(res, res);
michael@0 4446 // get rid of part of blockquote we are outdenting
michael@0 4447
michael@0 4448 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4449 return mHTMLEditor->RemoveBlockContainer(aBlock);
michael@0 4450 }
michael@0 4451
michael@0 4452 nsresult
michael@0 4453 nsHTMLEditRules::SplitBlock(nsIDOMNode *aBlock,
michael@0 4454 nsIDOMNode *aStartChild,
michael@0 4455 nsIDOMNode *aEndChild,
michael@0 4456 nsCOMPtr<nsIDOMNode> *aLeftNode,
michael@0 4457 nsCOMPtr<nsIDOMNode> *aRightNode,
michael@0 4458 nsCOMPtr<nsIDOMNode> *aMiddleNode)
michael@0 4459 {
michael@0 4460 NS_ENSURE_TRUE(aBlock && aStartChild && aEndChild, NS_ERROR_NULL_POINTER);
michael@0 4461
michael@0 4462 nsCOMPtr<nsIDOMNode> leftNode, rightNode;
michael@0 4463 int32_t startOffset, endOffset, offset;
michael@0 4464 nsresult res;
michael@0 4465
michael@0 4466 // get split point location
michael@0 4467 nsCOMPtr<nsIDOMNode> startParent = nsEditor::GetNodeLocation(aStartChild, &startOffset);
michael@0 4468
michael@0 4469 // do the splits!
michael@0 4470 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4471 res = mHTMLEditor->SplitNodeDeep(aBlock, startParent, startOffset, &offset,
michael@0 4472 true, address_of(leftNode), address_of(rightNode));
michael@0 4473 NS_ENSURE_SUCCESS(res, res);
michael@0 4474 if (rightNode) aBlock = rightNode;
michael@0 4475
michael@0 4476 // remember left portion of block if caller requested
michael@0 4477 if (aLeftNode)
michael@0 4478 *aLeftNode = leftNode;
michael@0 4479
michael@0 4480 // get split point location
michael@0 4481 nsCOMPtr<nsIDOMNode> endParent = nsEditor::GetNodeLocation(aEndChild, &endOffset);
michael@0 4482 endOffset++; // want to be after lastBQChild
michael@0 4483
michael@0 4484 // do the splits!
michael@0 4485 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4486 res = mHTMLEditor->SplitNodeDeep(aBlock, endParent, endOffset, &offset,
michael@0 4487 true, address_of(leftNode), address_of(rightNode));
michael@0 4488 NS_ENSURE_SUCCESS(res, res);
michael@0 4489 if (leftNode) aBlock = leftNode;
michael@0 4490
michael@0 4491 // remember right portion of block if caller requested
michael@0 4492 if (aRightNode)
michael@0 4493 *aRightNode = rightNode;
michael@0 4494
michael@0 4495 if (aMiddleNode)
michael@0 4496 *aMiddleNode = aBlock;
michael@0 4497
michael@0 4498 return NS_OK;
michael@0 4499 }
michael@0 4500
michael@0 4501 nsresult
michael@0 4502 nsHTMLEditRules::OutdentPartOfBlock(nsIDOMNode *aBlock,
michael@0 4503 nsIDOMNode *aStartChild,
michael@0 4504 nsIDOMNode *aEndChild,
michael@0 4505 bool aIsBlockIndentedWithCSS,
michael@0 4506 nsCOMPtr<nsIDOMNode> *aLeftNode,
michael@0 4507 nsCOMPtr<nsIDOMNode> *aRightNode)
michael@0 4508 {
michael@0 4509 nsCOMPtr<nsIDOMNode> middleNode;
michael@0 4510 nsresult res = SplitBlock(aBlock, aStartChild, aEndChild,
michael@0 4511 aLeftNode,
michael@0 4512 aRightNode,
michael@0 4513 address_of(middleNode));
michael@0 4514 NS_ENSURE_SUCCESS(res, res);
michael@0 4515 if (aIsBlockIndentedWithCSS) {
michael@0 4516 res = RelativeChangeIndentationOfElementNode(middleNode, -1);
michael@0 4517 } else {
michael@0 4518 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4519 res = mHTMLEditor->RemoveBlockContainer(middleNode);
michael@0 4520 }
michael@0 4521 return res;
michael@0 4522 }
michael@0 4523
michael@0 4524 ///////////////////////////////////////////////////////////////////////////
michael@0 4525 // ConvertListType: convert list type and list item type.
michael@0 4526 //
michael@0 4527 //
michael@0 4528 nsresult
michael@0 4529 nsHTMLEditRules::ConvertListType(nsIDOMNode* aList,
michael@0 4530 nsCOMPtr<nsIDOMNode>* outList,
michael@0 4531 nsIAtom* aListType,
michael@0 4532 nsIAtom* aItemType)
michael@0 4533 {
michael@0 4534 MOZ_ASSERT(aListType);
michael@0 4535 MOZ_ASSERT(aItemType);
michael@0 4536
michael@0 4537 NS_ENSURE_TRUE(aList && outList, NS_ERROR_NULL_POINTER);
michael@0 4538 nsCOMPtr<nsINode> list = do_QueryInterface(aList);
michael@0 4539 NS_ENSURE_STATE(list);
michael@0 4540
michael@0 4541 nsCOMPtr<dom::Element> outNode;
michael@0 4542 nsresult rv = ConvertListType(list, getter_AddRefs(outNode), aListType, aItemType);
michael@0 4543 *outList = outNode ? outNode->AsDOMNode() : nullptr;
michael@0 4544 return rv;
michael@0 4545 }
michael@0 4546
michael@0 4547 nsresult
michael@0 4548 nsHTMLEditRules::ConvertListType(nsINode* aList,
michael@0 4549 dom::Element** aOutList,
michael@0 4550 nsIAtom* aListType,
michael@0 4551 nsIAtom* aItemType)
michael@0 4552 {
michael@0 4553 MOZ_ASSERT(aList);
michael@0 4554 MOZ_ASSERT(aOutList);
michael@0 4555 MOZ_ASSERT(aListType);
michael@0 4556 MOZ_ASSERT(aItemType);
michael@0 4557
michael@0 4558 nsCOMPtr<nsINode> child = aList->GetFirstChild();
michael@0 4559 while (child)
michael@0 4560 {
michael@0 4561 if (child->IsElement()) {
michael@0 4562 dom::Element* element = child->AsElement();
michael@0 4563 if (nsHTMLEditUtils::IsListItem(element) && !element->IsHTML(aItemType)) {
michael@0 4564 nsCOMPtr<dom::Element> temp;
michael@0 4565 nsresult rv =
michael@0 4566 mHTMLEditor->ReplaceContainer(child, getter_AddRefs(temp),
michael@0 4567 nsDependentAtomString(aItemType));
michael@0 4568 NS_ENSURE_SUCCESS(rv, rv);
michael@0 4569 child = temp.forget();
michael@0 4570 } else if (nsHTMLEditUtils::IsList(element) &&
michael@0 4571 !element->IsHTML(aListType)) {
michael@0 4572 nsCOMPtr<dom::Element> temp;
michael@0 4573 nsresult rv =
michael@0 4574 ConvertListType(child, getter_AddRefs(temp), aListType, aItemType);
michael@0 4575 NS_ENSURE_SUCCESS(rv, rv);
michael@0 4576 child = temp.forget();
michael@0 4577 }
michael@0 4578 }
michael@0 4579 child = child->GetNextSibling();
michael@0 4580 }
michael@0 4581
michael@0 4582 if (aList->IsElement() && aList->AsElement()->IsHTML(aListType)) {
michael@0 4583 nsCOMPtr<dom::Element> list = aList->AsElement();
michael@0 4584 list.forget(aOutList);
michael@0 4585 return NS_OK;
michael@0 4586 }
michael@0 4587
michael@0 4588 return mHTMLEditor->ReplaceContainer(aList, aOutList,
michael@0 4589 nsDependentAtomString(aListType));
michael@0 4590 }
michael@0 4591
michael@0 4592
michael@0 4593 ///////////////////////////////////////////////////////////////////////////
michael@0 4594 // CreateStyleForInsertText: take care of clearing and setting appropriate
michael@0 4595 // style nodes for text insertion.
michael@0 4596 //
michael@0 4597 //
michael@0 4598 nsresult
michael@0 4599 nsHTMLEditRules::CreateStyleForInsertText(nsISelection *aSelection,
michael@0 4600 nsIDOMDocument *aDoc)
michael@0 4601 {
michael@0 4602 MOZ_ASSERT(aSelection && aDoc && mHTMLEditor->mTypeInState);
michael@0 4603
michael@0 4604 bool weDidSomething = false;
michael@0 4605 nsCOMPtr<nsIDOMNode> node, tmp;
michael@0 4606 int32_t offset;
michael@0 4607 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4608 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection,
michael@0 4609 getter_AddRefs(node),
michael@0 4610 &offset);
michael@0 4611 NS_ENSURE_SUCCESS(res, res);
michael@0 4612
michael@0 4613 // next examine our present style and make sure default styles are either
michael@0 4614 // present or explicitly overridden. If neither, add the default style to
michael@0 4615 // the TypeInState
michael@0 4616 int32_t length = mHTMLEditor->mDefaultStyles.Length();
michael@0 4617 for (int32_t j = 0; j < length; j++) {
michael@0 4618 PropItem* propItem = mHTMLEditor->mDefaultStyles[j];
michael@0 4619 MOZ_ASSERT(propItem);
michael@0 4620 bool bFirst, bAny, bAll;
michael@0 4621
michael@0 4622 // GetInlineProperty also examine TypeInState. The only gotcha here is
michael@0 4623 // that a cleared property looks like an unset property. For now I'm
michael@0 4624 // assuming that's not a problem: that default styles will always be
michael@0 4625 // multivalue styles (like font face or size) where clearing the style
michael@0 4626 // means we want to go back to the default. If we ever wanted a "toggle"
michael@0 4627 // style like bold for a default, though, I'll have to add code to detect
michael@0 4628 // the difference between unset and explicitly cleared, else user would
michael@0 4629 // never be able to unbold, for instance.
michael@0 4630 nsAutoString curValue;
michael@0 4631 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4632 res = mHTMLEditor->GetInlinePropertyBase(propItem->tag, &propItem->attr,
michael@0 4633 nullptr, &bFirst, &bAny, &bAll,
michael@0 4634 &curValue, false);
michael@0 4635 NS_ENSURE_SUCCESS(res, res);
michael@0 4636
michael@0 4637 if (!bAny) {
michael@0 4638 // no style set for this prop/attr
michael@0 4639 mHTMLEditor->mTypeInState->SetProp(propItem->tag, propItem->attr,
michael@0 4640 propItem->value);
michael@0 4641 }
michael@0 4642 }
michael@0 4643
michael@0 4644 nsCOMPtr<nsIDOMElement> rootElement;
michael@0 4645 res = aDoc->GetDocumentElement(getter_AddRefs(rootElement));
michael@0 4646 NS_ENSURE_SUCCESS(res, res);
michael@0 4647
michael@0 4648 // process clearing any styles first
michael@0 4649 nsAutoPtr<PropItem> item(mHTMLEditor->mTypeInState->TakeClearProperty());
michael@0 4650 while (item && node != rootElement) {
michael@0 4651 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4652 res = mHTMLEditor->ClearStyle(address_of(node), &offset,
michael@0 4653 item->tag, &item->attr);
michael@0 4654 NS_ENSURE_SUCCESS(res, res);
michael@0 4655 item = mHTMLEditor->mTypeInState->TakeClearProperty();
michael@0 4656 weDidSomething = true;
michael@0 4657 }
michael@0 4658
michael@0 4659 // then process setting any styles
michael@0 4660 int32_t relFontSize = mHTMLEditor->mTypeInState->TakeRelativeFontSize();
michael@0 4661 item = mHTMLEditor->mTypeInState->TakeSetProperty();
michael@0 4662
michael@0 4663 if (item || relFontSize) {
michael@0 4664 // we have at least one style to add; make a new text node to insert style
michael@0 4665 // nodes above.
michael@0 4666 if (mHTMLEditor->IsTextNode(node)) {
michael@0 4667 // if we are in a text node, split it
michael@0 4668 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4669 res = mHTMLEditor->SplitNodeDeep(node, node, offset, &offset);
michael@0 4670 NS_ENSURE_SUCCESS(res, res);
michael@0 4671 node->GetParentNode(getter_AddRefs(tmp));
michael@0 4672 node = tmp;
michael@0 4673 }
michael@0 4674 if (!mHTMLEditor->IsContainer(node)) {
michael@0 4675 return NS_OK;
michael@0 4676 }
michael@0 4677 nsCOMPtr<nsIDOMNode> newNode;
michael@0 4678 nsCOMPtr<nsIDOMText> nodeAsText;
michael@0 4679 res = aDoc->CreateTextNode(EmptyString(), getter_AddRefs(nodeAsText));
michael@0 4680 NS_ENSURE_SUCCESS(res, res);
michael@0 4681 NS_ENSURE_TRUE(nodeAsText, NS_ERROR_NULL_POINTER);
michael@0 4682 newNode = do_QueryInterface(nodeAsText);
michael@0 4683 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4684 res = mHTMLEditor->InsertNode(newNode, node, offset);
michael@0 4685 NS_ENSURE_SUCCESS(res, res);
michael@0 4686 node = newNode;
michael@0 4687 offset = 0;
michael@0 4688 weDidSomething = true;
michael@0 4689
michael@0 4690 if (relFontSize) {
michael@0 4691 // dir indicated bigger versus smaller. 1 = bigger, -1 = smaller
michael@0 4692 int32_t dir = relFontSize > 0 ? 1 : -1;
michael@0 4693 for (int32_t j = 0; j < DeprecatedAbs(relFontSize); j++) {
michael@0 4694 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4695 res = mHTMLEditor->RelativeFontChangeOnTextNode(dir, nodeAsText,
michael@0 4696 0, -1);
michael@0 4697 NS_ENSURE_SUCCESS(res, res);
michael@0 4698 }
michael@0 4699 }
michael@0 4700
michael@0 4701 while (item) {
michael@0 4702 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4703 res = mHTMLEditor->SetInlinePropertyOnNode(node, item->tag, &item->attr,
michael@0 4704 &item->value);
michael@0 4705 NS_ENSURE_SUCCESS(res, res);
michael@0 4706 item = mHTMLEditor->mTypeInState->TakeSetProperty();
michael@0 4707 }
michael@0 4708 }
michael@0 4709 if (weDidSomething) {
michael@0 4710 return aSelection->Collapse(node, offset);
michael@0 4711 }
michael@0 4712
michael@0 4713 return NS_OK;
michael@0 4714 }
michael@0 4715
michael@0 4716
michael@0 4717 ///////////////////////////////////////////////////////////////////////////
michael@0 4718 // IsEmptyBlock: figure out if aNode is (or is inside) an empty block.
michael@0 4719 // A block can have children and still be considered empty,
michael@0 4720 // if the children are empty or non-editable.
michael@0 4721 //
michael@0 4722 nsresult
michael@0 4723 nsHTMLEditRules::IsEmptyBlock(nsIDOMNode *aNode,
michael@0 4724 bool *outIsEmptyBlock,
michael@0 4725 bool aMozBRDoesntCount,
michael@0 4726 bool aListItemsNotEmpty)
michael@0 4727 {
michael@0 4728 NS_ENSURE_TRUE(aNode && outIsEmptyBlock, NS_ERROR_NULL_POINTER);
michael@0 4729 *outIsEmptyBlock = true;
michael@0 4730
michael@0 4731 // nsresult res = NS_OK;
michael@0 4732 nsCOMPtr<nsIDOMNode> nodeToTest;
michael@0 4733 if (IsBlockNode(aNode)) nodeToTest = do_QueryInterface(aNode);
michael@0 4734 // else nsCOMPtr<nsIDOMElement> block;
michael@0 4735 // looks like I forgot to finish this. Wonder what I was going to do?
michael@0 4736
michael@0 4737 NS_ENSURE_TRUE(nodeToTest, NS_ERROR_NULL_POINTER);
michael@0 4738 return mHTMLEditor->IsEmptyNode(nodeToTest, outIsEmptyBlock,
michael@0 4739 aMozBRDoesntCount, aListItemsNotEmpty);
michael@0 4740 }
michael@0 4741
michael@0 4742
michael@0 4743 nsresult
michael@0 4744 nsHTMLEditRules::WillAlign(Selection* aSelection,
michael@0 4745 const nsAString *alignType,
michael@0 4746 bool *aCancel,
michael@0 4747 bool *aHandled)
michael@0 4748 {
michael@0 4749 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 4750
michael@0 4751 nsresult res = WillInsert(aSelection, aCancel);
michael@0 4752 NS_ENSURE_SUCCESS(res, res);
michael@0 4753
michael@0 4754 // initialize out param
michael@0 4755 // we want to ignore result of WillInsert()
michael@0 4756 *aCancel = false;
michael@0 4757 *aHandled = false;
michael@0 4758
michael@0 4759 res = NormalizeSelection(aSelection);
michael@0 4760 NS_ENSURE_SUCCESS(res, res);
michael@0 4761 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
michael@0 4762
michael@0 4763 // convert the selection ranges into "promoted" selection ranges:
michael@0 4764 // this basically just expands the range to include the immediate
michael@0 4765 // block parent, and then further expands to include any ancestors
michael@0 4766 // whose children are all in the range
michael@0 4767 *aHandled = true;
michael@0 4768 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 4769 res = GetNodesFromSelection(aSelection, EditAction::align, arrayOfNodes);
michael@0 4770 NS_ENSURE_SUCCESS(res, res);
michael@0 4771
michael@0 4772 // if we don't have any nodes, or we have only a single br, then we are
michael@0 4773 // creating an empty alignment div. We have to do some different things for these.
michael@0 4774 bool emptyDiv = false;
michael@0 4775 int32_t listCount = arrayOfNodes.Count();
michael@0 4776 if (!listCount) emptyDiv = true;
michael@0 4777 if (listCount == 1)
michael@0 4778 {
michael@0 4779 nsCOMPtr<nsIDOMNode> theNode = arrayOfNodes[0];
michael@0 4780
michael@0 4781 if (nsHTMLEditUtils::SupportsAlignAttr(theNode))
michael@0 4782 {
michael@0 4783 // the node is a table element, an horiz rule, a paragraph, a div
michael@0 4784 // or a section header; in HTML 4, it can directly carry the ALIGN
michael@0 4785 // attribute and we don't need to make a div! If we are in CSS mode,
michael@0 4786 // all the work is done in AlignBlock
michael@0 4787 nsCOMPtr<nsIDOMElement> theElem = do_QueryInterface(theNode);
michael@0 4788 res = AlignBlock(theElem, alignType, true);
michael@0 4789 NS_ENSURE_SUCCESS(res, res);
michael@0 4790 return NS_OK;
michael@0 4791 }
michael@0 4792
michael@0 4793 if (nsTextEditUtils::IsBreak(theNode))
michael@0 4794 {
michael@0 4795 // The special case emptyDiv code (below) that consumes BRs can
michael@0 4796 // cause tables to split if the start node of the selection is
michael@0 4797 // not in a table cell or caption, for example parent is a <tr>.
michael@0 4798 // Avoid this unnecessary splitting if possible by leaving emptyDiv
michael@0 4799 // FALSE so that we fall through to the normal case alignment code.
michael@0 4800 //
michael@0 4801 // XXX: It seems a little error prone for the emptyDiv special
michael@0 4802 // case code to assume that the start node of the selection
michael@0 4803 // is the parent of the single node in the arrayOfNodes, as
michael@0 4804 // the paragraph above points out. Do we rely on the selection
michael@0 4805 // start node because of the fact that arrayOfNodes can be empty?
michael@0 4806 // We should probably revisit this issue. - kin
michael@0 4807
michael@0 4808 nsCOMPtr<nsIDOMNode> parent;
michael@0 4809 int32_t offset;
michael@0 4810 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4811 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
michael@0 4812
michael@0 4813 if (!nsHTMLEditUtils::IsTableElement(parent) || nsHTMLEditUtils::IsTableCellOrCaption(parent))
michael@0 4814 emptyDiv = true;
michael@0 4815 }
michael@0 4816 }
michael@0 4817 if (emptyDiv)
michael@0 4818 {
michael@0 4819 int32_t offset;
michael@0 4820 nsCOMPtr<nsIDOMNode> brNode, parent, theDiv, sib;
michael@0 4821 NS_NAMED_LITERAL_STRING(divType, "div");
michael@0 4822 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4823 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
michael@0 4824 NS_ENSURE_SUCCESS(res, res);
michael@0 4825 res = SplitAsNeeded(&divType, address_of(parent), &offset);
michael@0 4826 NS_ENSURE_SUCCESS(res, res);
michael@0 4827 // consume a trailing br, if any. This is to keep an alignment from
michael@0 4828 // creating extra lines, if possible.
michael@0 4829 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4830 res = mHTMLEditor->GetNextHTMLNode(parent, offset, address_of(brNode));
michael@0 4831 NS_ENSURE_SUCCESS(res, res);
michael@0 4832 if (brNode && nsTextEditUtils::IsBreak(brNode))
michael@0 4833 {
michael@0 4834 // making use of html structure... if next node after where
michael@0 4835 // we are putting our div is not a block, then the br we
michael@0 4836 // found is in same block we are, so its safe to consume it.
michael@0 4837 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4838 res = mHTMLEditor->GetNextHTMLSibling(parent, offset, address_of(sib));
michael@0 4839 NS_ENSURE_SUCCESS(res, res);
michael@0 4840 if (!IsBlockNode(sib))
michael@0 4841 {
michael@0 4842 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4843 res = mHTMLEditor->DeleteNode(brNode);
michael@0 4844 NS_ENSURE_SUCCESS(res, res);
michael@0 4845 }
michael@0 4846 }
michael@0 4847 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4848 res = mHTMLEditor->CreateNode(divType, parent, offset, getter_AddRefs(theDiv));
michael@0 4849 NS_ENSURE_SUCCESS(res, res);
michael@0 4850 // remember our new block for postprocessing
michael@0 4851 mNewBlock = theDiv;
michael@0 4852 // set up the alignment on the div, using HTML or CSS
michael@0 4853 nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(theDiv);
michael@0 4854 res = AlignBlock(divElem, alignType, true);
michael@0 4855 NS_ENSURE_SUCCESS(res, res);
michael@0 4856 *aHandled = true;
michael@0 4857 // put in a moz-br so that it won't get deleted
michael@0 4858 res = CreateMozBR(theDiv, 0);
michael@0 4859 NS_ENSURE_SUCCESS(res, res);
michael@0 4860 res = aSelection->Collapse(theDiv, 0);
michael@0 4861 selectionResetter.Abort(); // don't reset our selection in this case.
michael@0 4862 return res;
michael@0 4863 }
michael@0 4864
michael@0 4865 // Next we detect all the transitions in the array, where a transition
michael@0 4866 // means that adjacent nodes in the array don't have the same parent.
michael@0 4867
michael@0 4868 nsTArray<bool> transitionList;
michael@0 4869 res = MakeTransitionList(arrayOfNodes, transitionList);
michael@0 4870 NS_ENSURE_SUCCESS(res, res);
michael@0 4871
michael@0 4872 // Ok, now go through all the nodes and give them an align attrib or put them in a div,
michael@0 4873 // or whatever is appropriate. Wohoo!
michael@0 4874
michael@0 4875 nsCOMPtr<nsIDOMNode> curParent;
michael@0 4876 nsCOMPtr<nsIDOMNode> curDiv;
michael@0 4877 bool useCSS = mHTMLEditor->IsCSSEnabled();
michael@0 4878 for (int32_t i = 0; i < listCount; ++i) {
michael@0 4879 // here's where we actually figure out what to do
michael@0 4880 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
michael@0 4881
michael@0 4882 // Ignore all non-editable nodes. Leave them be.
michael@0 4883 if (!mHTMLEditor->IsEditable(curNode)) continue;
michael@0 4884
michael@0 4885 int32_t offset;
michael@0 4886 curParent = nsEditor::GetNodeLocation(curNode, &offset);
michael@0 4887
michael@0 4888 // the node is a table element, an horiz rule, a paragraph, a div
michael@0 4889 // or a section header; in HTML 4, it can directly carry the ALIGN
michael@0 4890 // attribute and we don't need to nest it, just set the alignment.
michael@0 4891 // In CSS, assign the corresponding CSS styles in AlignBlock
michael@0 4892 if (nsHTMLEditUtils::SupportsAlignAttr(curNode))
michael@0 4893 {
michael@0 4894 nsCOMPtr<nsIDOMElement> curElem = do_QueryInterface(curNode);
michael@0 4895 res = AlignBlock(curElem, alignType, false);
michael@0 4896 NS_ENSURE_SUCCESS(res, res);
michael@0 4897 // clear out curDiv so that we don't put nodes after this one into it
michael@0 4898 curDiv = 0;
michael@0 4899 continue;
michael@0 4900 }
michael@0 4901
michael@0 4902 // Skip insignificant formatting text nodes to prevent
michael@0 4903 // unnecessary structure splitting!
michael@0 4904 bool isEmptyTextNode = false;
michael@0 4905 if (nsEditor::IsTextNode(curNode) &&
michael@0 4906 ((nsHTMLEditUtils::IsTableElement(curParent) && !nsHTMLEditUtils::IsTableCellOrCaption(curParent)) ||
michael@0 4907 nsHTMLEditUtils::IsList(curParent) ||
michael@0 4908 (NS_SUCCEEDED(mHTMLEditor->IsEmptyNode(curNode, &isEmptyTextNode)) && isEmptyTextNode)))
michael@0 4909 continue;
michael@0 4910
michael@0 4911 // if it's a list item, or a list
michael@0 4912 // inside a list, forget any "current" div, and instead put divs inside
michael@0 4913 // the appropriate block (td, li, etc)
michael@0 4914 if ( nsHTMLEditUtils::IsListItem(curNode)
michael@0 4915 || nsHTMLEditUtils::IsList(curNode))
michael@0 4916 {
michael@0 4917 res = RemoveAlignment(curNode, *alignType, true);
michael@0 4918 NS_ENSURE_SUCCESS(res, res);
michael@0 4919 if (useCSS) {
michael@0 4920 nsCOMPtr<nsIDOMElement> curElem = do_QueryInterface(curNode);
michael@0 4921 NS_NAMED_LITERAL_STRING(attrName, "align");
michael@0 4922 int32_t count;
michael@0 4923 mHTMLEditor->mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(curNode, nullptr,
michael@0 4924 &attrName, alignType,
michael@0 4925 &count, false);
michael@0 4926 curDiv = 0;
michael@0 4927 continue;
michael@0 4928 }
michael@0 4929 else if (nsHTMLEditUtils::IsList(curParent)) {
michael@0 4930 // if we don't use CSS, add a contraint to list element : they have
michael@0 4931 // to be inside another list, ie >= second level of nesting
michael@0 4932 res = AlignInnerBlocks(curNode, alignType);
michael@0 4933 NS_ENSURE_SUCCESS(res, res);
michael@0 4934 curDiv = 0;
michael@0 4935 continue;
michael@0 4936 }
michael@0 4937 // clear out curDiv so that we don't put nodes after this one into it
michael@0 4938 }
michael@0 4939
michael@0 4940 // need to make a div to put things in if we haven't already,
michael@0 4941 // or if this node doesn't go in div we used earlier.
michael@0 4942 if (!curDiv || transitionList[i])
michael@0 4943 {
michael@0 4944 // First, check that our element can contain a div.
michael@0 4945 NS_NAMED_LITERAL_STRING(divType, "div");
michael@0 4946 if (!mEditor->CanContainTag(curParent, nsGkAtoms::div)) {
michael@0 4947 return NS_OK; // cancelled
michael@0 4948 }
michael@0 4949
michael@0 4950 res = SplitAsNeeded(&divType, address_of(curParent), &offset);
michael@0 4951 NS_ENSURE_SUCCESS(res, res);
michael@0 4952 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4953 res = mHTMLEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curDiv));
michael@0 4954 NS_ENSURE_SUCCESS(res, res);
michael@0 4955 // remember our new block for postprocessing
michael@0 4956 mNewBlock = curDiv;
michael@0 4957 // set up the alignment on the div
michael@0 4958 nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(curDiv);
michael@0 4959 res = AlignBlock(divElem, alignType, true);
michael@0 4960 //nsAutoString attr(NS_LITERAL_STRING("align"));
michael@0 4961 //res = mHTMLEditor->SetAttribute(divElem, attr, *alignType);
michael@0 4962 //NS_ENSURE_SUCCESS(res, res);
michael@0 4963 // curDiv is now the correct thing to put curNode in
michael@0 4964 }
michael@0 4965
michael@0 4966 // tuck the node into the end of the active div
michael@0 4967 NS_ENSURE_STATE(mHTMLEditor);
michael@0 4968 res = mHTMLEditor->MoveNode(curNode, curDiv, -1);
michael@0 4969 NS_ENSURE_SUCCESS(res, res);
michael@0 4970 }
michael@0 4971
michael@0 4972 return res;
michael@0 4973 }
michael@0 4974
michael@0 4975
michael@0 4976 ///////////////////////////////////////////////////////////////////////////
michael@0 4977 // AlignInnerBlocks: align inside table cells or list items
michael@0 4978 //
michael@0 4979 nsresult
michael@0 4980 nsHTMLEditRules::AlignInnerBlocks(nsIDOMNode *aNode, const nsAString *alignType)
michael@0 4981 {
michael@0 4982 NS_ENSURE_TRUE(aNode && alignType, NS_ERROR_NULL_POINTER);
michael@0 4983 nsresult res;
michael@0 4984
michael@0 4985 // gather list of table cells or list items
michael@0 4986 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 4987 nsTableCellAndListItemFunctor functor;
michael@0 4988 nsDOMIterator iter;
michael@0 4989 res = iter.Init(aNode);
michael@0 4990 NS_ENSURE_SUCCESS(res, res);
michael@0 4991 res = iter.AppendList(functor, arrayOfNodes);
michael@0 4992 NS_ENSURE_SUCCESS(res, res);
michael@0 4993
michael@0 4994 // now that we have the list, align their contents as requested
michael@0 4995 int32_t listCount = arrayOfNodes.Count();
michael@0 4996 int32_t j;
michael@0 4997
michael@0 4998 for (j = 0; j < listCount; j++)
michael@0 4999 {
michael@0 5000 nsIDOMNode* node = arrayOfNodes[0];
michael@0 5001 res = AlignBlockContents(node, alignType);
michael@0 5002 NS_ENSURE_SUCCESS(res, res);
michael@0 5003 arrayOfNodes.RemoveObjectAt(0);
michael@0 5004 }
michael@0 5005
michael@0 5006 return res;
michael@0 5007 }
michael@0 5008
michael@0 5009
michael@0 5010 ///////////////////////////////////////////////////////////////////////////
michael@0 5011 // AlignBlockContents: align contents of a block element
michael@0 5012 //
michael@0 5013 nsresult
michael@0 5014 nsHTMLEditRules::AlignBlockContents(nsIDOMNode *aNode, const nsAString *alignType)
michael@0 5015 {
michael@0 5016 NS_ENSURE_TRUE(aNode && alignType, NS_ERROR_NULL_POINTER);
michael@0 5017 nsresult res;
michael@0 5018 nsCOMPtr <nsIDOMNode> firstChild, lastChild, divNode;
michael@0 5019
michael@0 5020 bool useCSS = mHTMLEditor->IsCSSEnabled();
michael@0 5021
michael@0 5022 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5023 res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(firstChild));
michael@0 5024 NS_ENSURE_SUCCESS(res, res);
michael@0 5025 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5026 res = mHTMLEditor->GetLastEditableChild(aNode, address_of(lastChild));
michael@0 5027 NS_ENSURE_SUCCESS(res, res);
michael@0 5028 NS_NAMED_LITERAL_STRING(attr, "align");
michael@0 5029 if (!firstChild)
michael@0 5030 {
michael@0 5031 // this cell has no content, nothing to align
michael@0 5032 }
michael@0 5033 else if ((firstChild==lastChild) && nsHTMLEditUtils::IsDiv(firstChild))
michael@0 5034 {
michael@0 5035 // the cell already has a div containing all of its content: just
michael@0 5036 // act on this div.
michael@0 5037 nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(firstChild);
michael@0 5038 if (useCSS) {
michael@0 5039 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5040 res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, false);
michael@0 5041 }
michael@0 5042 else {
michael@0 5043 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5044 res = mHTMLEditor->SetAttribute(divElem, attr, *alignType);
michael@0 5045 }
michael@0 5046 NS_ENSURE_SUCCESS(res, res);
michael@0 5047 }
michael@0 5048 else
michael@0 5049 {
michael@0 5050 // else we need to put in a div, set the alignment, and toss in all the children
michael@0 5051 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5052 res = mHTMLEditor->CreateNode(NS_LITERAL_STRING("div"), aNode, 0, getter_AddRefs(divNode));
michael@0 5053 NS_ENSURE_SUCCESS(res, res);
michael@0 5054 // set up the alignment on the div
michael@0 5055 nsCOMPtr<nsIDOMElement> divElem = do_QueryInterface(divNode);
michael@0 5056 if (useCSS) {
michael@0 5057 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5058 res = mHTMLEditor->SetAttributeOrEquivalent(divElem, attr, *alignType, false);
michael@0 5059 }
michael@0 5060 else {
michael@0 5061 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5062 res = mHTMLEditor->SetAttribute(divElem, attr, *alignType);
michael@0 5063 }
michael@0 5064 NS_ENSURE_SUCCESS(res, res);
michael@0 5065 // tuck the children into the end of the active div
michael@0 5066 while (lastChild && (lastChild != divNode))
michael@0 5067 {
michael@0 5068 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5069 res = mHTMLEditor->MoveNode(lastChild, divNode, 0);
michael@0 5070 NS_ENSURE_SUCCESS(res, res);
michael@0 5071 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5072 res = mHTMLEditor->GetLastEditableChild(aNode, address_of(lastChild));
michael@0 5073 NS_ENSURE_SUCCESS(res, res);
michael@0 5074 }
michael@0 5075 }
michael@0 5076 return res;
michael@0 5077 }
michael@0 5078
michael@0 5079 ///////////////////////////////////////////////////////////////////////////
michael@0 5080 // CheckForEmptyBlock: Called by WillDeleteSelection to detect and handle
michael@0 5081 // case of deleting from inside an empty block.
michael@0 5082 //
michael@0 5083 nsresult
michael@0 5084 nsHTMLEditRules::CheckForEmptyBlock(nsIDOMNode *aStartNode,
michael@0 5085 nsIDOMNode *aBodyNode,
michael@0 5086 nsISelection *aSelection,
michael@0 5087 bool *aHandled)
michael@0 5088 {
michael@0 5089 // If the editing host is an inline element, bail out early.
michael@0 5090 if (IsInlineNode(aBodyNode)) {
michael@0 5091 return NS_OK;
michael@0 5092 }
michael@0 5093 // if we are inside an empty block, delete it.
michael@0 5094 // Note: do NOT delete table elements this way.
michael@0 5095 nsresult res = NS_OK;
michael@0 5096 nsCOMPtr<nsIDOMNode> block, emptyBlock;
michael@0 5097 if (IsBlockNode(aStartNode))
michael@0 5098 block = aStartNode;
michael@0 5099 else
michael@0 5100 block = mHTMLEditor->GetBlockNodeParent(aStartNode);
michael@0 5101 bool bIsEmptyNode;
michael@0 5102 if (block != aBodyNode) // efficiency hack. avoiding IsEmptyNode() call when in body
michael@0 5103 {
michael@0 5104 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5105 res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, true, false);
michael@0 5106 NS_ENSURE_SUCCESS(res, res);
michael@0 5107 while (bIsEmptyNode && !nsHTMLEditUtils::IsTableElement(block) && (block != aBodyNode))
michael@0 5108 {
michael@0 5109 emptyBlock = block;
michael@0 5110 block = mHTMLEditor->GetBlockNodeParent(emptyBlock);
michael@0 5111 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5112 res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, true, false);
michael@0 5113 NS_ENSURE_SUCCESS(res, res);
michael@0 5114 }
michael@0 5115 }
michael@0 5116
michael@0 5117 nsCOMPtr<nsIContent> emptyContent = do_QueryInterface(emptyBlock);
michael@0 5118 if (emptyBlock && emptyContent->IsEditable())
michael@0 5119 {
michael@0 5120 int32_t offset;
michael@0 5121 nsCOMPtr<nsIDOMNode> blockParent = nsEditor::GetNodeLocation(emptyBlock, &offset);
michael@0 5122 NS_ENSURE_TRUE(blockParent && offset >= 0, NS_ERROR_FAILURE);
michael@0 5123
michael@0 5124 if (nsHTMLEditUtils::IsListItem(emptyBlock))
michael@0 5125 {
michael@0 5126 // are we the first list item in the list?
michael@0 5127 bool bIsFirst;
michael@0 5128 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5129 res = mHTMLEditor->IsFirstEditableChild(emptyBlock, &bIsFirst);
michael@0 5130 NS_ENSURE_SUCCESS(res, res);
michael@0 5131 if (bIsFirst)
michael@0 5132 {
michael@0 5133 int32_t listOffset;
michael@0 5134 nsCOMPtr<nsIDOMNode> listParent = nsEditor::GetNodeLocation(blockParent,
michael@0 5135 &listOffset);
michael@0 5136 NS_ENSURE_TRUE(listParent && listOffset >= 0, NS_ERROR_FAILURE);
michael@0 5137 // if we are a sublist, skip the br creation
michael@0 5138 if (!nsHTMLEditUtils::IsList(listParent))
michael@0 5139 {
michael@0 5140 // create a br before list
michael@0 5141 nsCOMPtr<nsIDOMNode> brNode;
michael@0 5142 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5143 res = mHTMLEditor->CreateBR(listParent, listOffset, address_of(brNode));
michael@0 5144 NS_ENSURE_SUCCESS(res, res);
michael@0 5145 // adjust selection to be right before it
michael@0 5146 res = aSelection->Collapse(listParent, listOffset);
michael@0 5147 NS_ENSURE_SUCCESS(res, res);
michael@0 5148 }
michael@0 5149 // else just let selection perculate up. We'll adjust it in AfterEdit()
michael@0 5150 }
michael@0 5151 }
michael@0 5152 else
michael@0 5153 {
michael@0 5154 // adjust selection to be right after it
michael@0 5155 res = aSelection->Collapse(blockParent, offset+1);
michael@0 5156 NS_ENSURE_SUCCESS(res, res);
michael@0 5157 }
michael@0 5158 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5159 res = mHTMLEditor->DeleteNode(emptyBlock);
michael@0 5160 *aHandled = true;
michael@0 5161 }
michael@0 5162 return res;
michael@0 5163 }
michael@0 5164
michael@0 5165 nsresult
michael@0 5166 nsHTMLEditRules::CheckForInvisibleBR(nsIDOMNode *aBlock,
michael@0 5167 BRLocation aWhere,
michael@0 5168 nsCOMPtr<nsIDOMNode> *outBRNode,
michael@0 5169 int32_t aOffset)
michael@0 5170 {
michael@0 5171 NS_ENSURE_TRUE(aBlock && outBRNode, NS_ERROR_NULL_POINTER);
michael@0 5172 *outBRNode = nullptr;
michael@0 5173
michael@0 5174 nsCOMPtr<nsIDOMNode> testNode;
michael@0 5175 int32_t testOffset = 0;
michael@0 5176 bool runTest = false;
michael@0 5177
michael@0 5178 if (aWhere == kBlockEnd)
michael@0 5179 {
michael@0 5180 nsCOMPtr<nsIDOMNode> rightmostNode =
michael@0 5181 mHTMLEditor->GetRightmostChild(aBlock, true); // no block crossing
michael@0 5182
michael@0 5183 if (rightmostNode)
michael@0 5184 {
michael@0 5185 int32_t nodeOffset;
michael@0 5186 nsCOMPtr<nsIDOMNode> nodeParent = nsEditor::GetNodeLocation(rightmostNode,
michael@0 5187 &nodeOffset);
michael@0 5188 runTest = true;
michael@0 5189 testNode = nodeParent;
michael@0 5190 // use offset + 1, because we want the last node included in our
michael@0 5191 // evaluation
michael@0 5192 testOffset = nodeOffset + 1;
michael@0 5193 }
michael@0 5194 }
michael@0 5195 else if (aOffset)
michael@0 5196 {
michael@0 5197 runTest = true;
michael@0 5198 testNode = aBlock;
michael@0 5199 // we'll check everything to the left of the input position
michael@0 5200 testOffset = aOffset;
michael@0 5201 }
michael@0 5202
michael@0 5203 if (runTest)
michael@0 5204 {
michael@0 5205 nsWSRunObject wsTester(mHTMLEditor, testNode, testOffset);
michael@0 5206 if (WSType::br == wsTester.mStartReason) {
michael@0 5207 *outBRNode = wsTester.mStartReasonNode;
michael@0 5208 }
michael@0 5209 }
michael@0 5210
michael@0 5211 return NS_OK;
michael@0 5212 }
michael@0 5213
michael@0 5214
michael@0 5215 ///////////////////////////////////////////////////////////////////////////
michael@0 5216 // GetInnerContent: aList and aTbl allow the caller to specify what kind
michael@0 5217 // of content to "look inside". If aTbl is true, look inside
michael@0 5218 // any table content, and insert the inner content into the
michael@0 5219 // supplied issupportsarray at offset aIndex.
michael@0 5220 // Similarly with aList and list content.
michael@0 5221 // aIndex is updated to point past inserted elements.
michael@0 5222 //
michael@0 5223 nsresult
michael@0 5224 nsHTMLEditRules::GetInnerContent(nsIDOMNode *aNode, nsCOMArray<nsIDOMNode> &outArrayOfNodes,
michael@0 5225 int32_t *aIndex, bool aList, bool aTbl)
michael@0 5226 {
michael@0 5227 NS_ENSURE_TRUE(aNode && aIndex, NS_ERROR_NULL_POINTER);
michael@0 5228
michael@0 5229 nsCOMPtr<nsIDOMNode> node;
michael@0 5230
michael@0 5231 nsresult res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(node));
michael@0 5232 while (NS_SUCCEEDED(res) && node)
michael@0 5233 {
michael@0 5234 if ( ( aList && (nsHTMLEditUtils::IsList(node) ||
michael@0 5235 nsHTMLEditUtils::IsListItem(node) ) )
michael@0 5236 || ( aTbl && nsHTMLEditUtils::IsTableElement(node) ) )
michael@0 5237 {
michael@0 5238 res = GetInnerContent(node, outArrayOfNodes, aIndex, aList, aTbl);
michael@0 5239 NS_ENSURE_SUCCESS(res, res);
michael@0 5240 }
michael@0 5241 else
michael@0 5242 {
michael@0 5243 outArrayOfNodes.InsertObjectAt(node, *aIndex);
michael@0 5244 (*aIndex)++;
michael@0 5245 }
michael@0 5246 nsCOMPtr<nsIDOMNode> tmp;
michael@0 5247 res = node->GetNextSibling(getter_AddRefs(tmp));
michael@0 5248 node = tmp;
michael@0 5249 }
michael@0 5250
michael@0 5251 return res;
michael@0 5252 }
michael@0 5253
michael@0 5254 ///////////////////////////////////////////////////////////////////////////
michael@0 5255 // ExpandSelectionForDeletion: this promotes our selection to include blocks
michael@0 5256 // that have all their children selected.
michael@0 5257 //
michael@0 5258 nsresult
michael@0 5259 nsHTMLEditRules::ExpandSelectionForDeletion(nsISelection *aSelection)
michael@0 5260 {
michael@0 5261 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
michael@0 5262
michael@0 5263 // don't need to touch collapsed selections
michael@0 5264 if (aSelection->Collapsed()) {
michael@0 5265 return NS_OK;
michael@0 5266 }
michael@0 5267
michael@0 5268 int32_t rangeCount;
michael@0 5269 nsresult res = aSelection->GetRangeCount(&rangeCount);
michael@0 5270 NS_ENSURE_SUCCESS(res, res);
michael@0 5271
michael@0 5272 // we don't need to mess with cell selections, and we assume multirange selections are those.
michael@0 5273 if (rangeCount != 1) return NS_OK;
michael@0 5274
michael@0 5275 // find current sel start and end
michael@0 5276 nsCOMPtr<nsIDOMRange> range;
michael@0 5277 res = aSelection->GetRangeAt(0, getter_AddRefs(range));
michael@0 5278 NS_ENSURE_SUCCESS(res, res);
michael@0 5279 NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
michael@0 5280 nsCOMPtr<nsIDOMNode> selStartNode, selEndNode, selCommon;
michael@0 5281 int32_t selStartOffset, selEndOffset;
michael@0 5282
michael@0 5283 res = range->GetStartContainer(getter_AddRefs(selStartNode));
michael@0 5284 NS_ENSURE_SUCCESS(res, res);
michael@0 5285 res = range->GetStartOffset(&selStartOffset);
michael@0 5286 NS_ENSURE_SUCCESS(res, res);
michael@0 5287 res = range->GetEndContainer(getter_AddRefs(selEndNode));
michael@0 5288 NS_ENSURE_SUCCESS(res, res);
michael@0 5289 res = range->GetEndOffset(&selEndOffset);
michael@0 5290 NS_ENSURE_SUCCESS(res, res);
michael@0 5291
michael@0 5292 // find current selection common block parent
michael@0 5293 res = range->GetCommonAncestorContainer(getter_AddRefs(selCommon));
michael@0 5294 NS_ENSURE_SUCCESS(res, res);
michael@0 5295 if (!IsBlockNode(selCommon))
michael@0 5296 selCommon = nsHTMLEditor::GetBlockNodeParent(selCommon);
michael@0 5297
michael@0 5298 // set up for loops and cache our root element
michael@0 5299 bool stillLooking = true;
michael@0 5300 nsCOMPtr<nsIDOMNode> visNode, firstBRParent;
michael@0 5301 int32_t visOffset=0, firstBROffset=0;
michael@0 5302 WSType wsType;
michael@0 5303 nsCOMPtr<nsIContent> rootContent = mHTMLEditor->GetActiveEditingHost();
michael@0 5304 nsCOMPtr<nsIDOMNode> rootElement = do_QueryInterface(rootContent);
michael@0 5305 NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE);
michael@0 5306
michael@0 5307 // find previous visible thingy before start of selection
michael@0 5308 if ((selStartNode!=selCommon) && (selStartNode!=rootElement))
michael@0 5309 {
michael@0 5310 while (stillLooking)
michael@0 5311 {
michael@0 5312 nsWSRunObject wsObj(mHTMLEditor, selStartNode, selStartOffset);
michael@0 5313 wsObj.PriorVisibleNode(selStartNode, selStartOffset, address_of(visNode),
michael@0 5314 &visOffset, &wsType);
michael@0 5315 if (wsType == WSType::thisBlock) {
michael@0 5316 // we want to keep looking up. But stop if we are crossing table element
michael@0 5317 // boundaries, or if we hit the root.
michael@0 5318 if ( nsHTMLEditUtils::IsTableElement(wsObj.mStartReasonNode) ||
michael@0 5319 (selCommon == wsObj.mStartReasonNode) ||
michael@0 5320 (rootElement == wsObj.mStartReasonNode) )
michael@0 5321 {
michael@0 5322 stillLooking = false;
michael@0 5323 }
michael@0 5324 else
michael@0 5325 {
michael@0 5326 selStartNode = nsEditor::GetNodeLocation(wsObj.mStartReasonNode,
michael@0 5327 &selStartOffset);
michael@0 5328 }
michael@0 5329 }
michael@0 5330 else
michael@0 5331 {
michael@0 5332 stillLooking = false;
michael@0 5333 }
michael@0 5334 }
michael@0 5335 }
michael@0 5336
michael@0 5337 stillLooking = true;
michael@0 5338 // find next visible thingy after end of selection
michael@0 5339 if ((selEndNode!=selCommon) && (selEndNode!=rootElement))
michael@0 5340 {
michael@0 5341 while (stillLooking)
michael@0 5342 {
michael@0 5343 nsWSRunObject wsObj(mHTMLEditor, selEndNode, selEndOffset);
michael@0 5344 wsObj.NextVisibleNode(selEndNode, selEndOffset, address_of(visNode),
michael@0 5345 &visOffset, &wsType);
michael@0 5346 if (wsType == WSType::br) {
michael@0 5347 if (mHTMLEditor->IsVisBreak(wsObj.mEndReasonNode))
michael@0 5348 {
michael@0 5349 stillLooking = false;
michael@0 5350 }
michael@0 5351 else
michael@0 5352 {
michael@0 5353 if (!firstBRParent)
michael@0 5354 {
michael@0 5355 firstBRParent = selEndNode;
michael@0 5356 firstBROffset = selEndOffset;
michael@0 5357 }
michael@0 5358 selEndNode = nsEditor::GetNodeLocation(wsObj.mEndReasonNode, &selEndOffset);
michael@0 5359 ++selEndOffset;
michael@0 5360 }
michael@0 5361 } else if (wsType == WSType::thisBlock) {
michael@0 5362 // we want to keep looking up. But stop if we are crossing table element
michael@0 5363 // boundaries, or if we hit the root.
michael@0 5364 if ( nsHTMLEditUtils::IsTableElement(wsObj.mEndReasonNode) ||
michael@0 5365 (selCommon == wsObj.mEndReasonNode) ||
michael@0 5366 (rootElement == wsObj.mEndReasonNode) )
michael@0 5367 {
michael@0 5368 stillLooking = false;
michael@0 5369 }
michael@0 5370 else
michael@0 5371 {
michael@0 5372 selEndNode = nsEditor::GetNodeLocation(wsObj.mEndReasonNode, &selEndOffset);
michael@0 5373 ++selEndOffset;
michael@0 5374 }
michael@0 5375 }
michael@0 5376 else
michael@0 5377 {
michael@0 5378 stillLooking = false;
michael@0 5379 }
michael@0 5380 }
michael@0 5381 }
michael@0 5382 // now set the selection to the new range
michael@0 5383 aSelection->Collapse(selStartNode, selStartOffset);
michael@0 5384
michael@0 5385 // expand selection endpoint only if we didnt pass a br,
michael@0 5386 // or if we really needed to pass that br (ie, its block is now
michael@0 5387 // totally selected)
michael@0 5388 bool doEndExpansion = true;
michael@0 5389 if (firstBRParent)
michael@0 5390 {
michael@0 5391 // find block node containing br
michael@0 5392 nsCOMPtr<nsIDOMNode> brBlock = firstBRParent;
michael@0 5393 if (!IsBlockNode(brBlock))
michael@0 5394 brBlock = nsHTMLEditor::GetBlockNodeParent(brBlock);
michael@0 5395 bool nodeBefore=false, nodeAfter=false;
michael@0 5396
michael@0 5397 // create a range that represents expanded selection
michael@0 5398 nsCOMPtr<nsINode> node = do_QueryInterface(selStartNode);
michael@0 5399 NS_ENSURE_STATE(node);
michael@0 5400 nsRefPtr<nsRange> range = new nsRange(node);
michael@0 5401 res = range->SetStart(selStartNode, selStartOffset);
michael@0 5402 NS_ENSURE_SUCCESS(res, res);
michael@0 5403 res = range->SetEnd(selEndNode, selEndOffset);
michael@0 5404 NS_ENSURE_SUCCESS(res, res);
michael@0 5405
michael@0 5406 // check if block is entirely inside range
michael@0 5407 nsCOMPtr<nsIContent> brContentBlock = do_QueryInterface(brBlock);
michael@0 5408 res = nsRange::CompareNodeToRange(brContentBlock, range, &nodeBefore, &nodeAfter);
michael@0 5409
michael@0 5410 // if block isn't contained, forgo grabbing the br in the expanded selection
michael@0 5411 if (nodeBefore || nodeAfter)
michael@0 5412 doEndExpansion = false;
michael@0 5413 }
michael@0 5414 if (doEndExpansion)
michael@0 5415 {
michael@0 5416 res = aSelection->Extend(selEndNode, selEndOffset);
michael@0 5417 }
michael@0 5418 else
michael@0 5419 {
michael@0 5420 // only expand to just before br
michael@0 5421 res = aSelection->Extend(firstBRParent, firstBROffset);
michael@0 5422 }
michael@0 5423
michael@0 5424 return res;
michael@0 5425 }
michael@0 5426
michael@0 5427
michael@0 5428 ///////////////////////////////////////////////////////////////////////////
michael@0 5429 // NormalizeSelection: tweak non-collapsed selections to be more "natural".
michael@0 5430 // Idea here is to adjust selection endpoint so that they do not cross
michael@0 5431 // breaks or block boundaries unless something editable beyond that boundary
michael@0 5432 // is also selected. This adjustment makes it much easier for the various
michael@0 5433 // block operations to determine what nodes to act on.
michael@0 5434 //
michael@0 5435 nsresult
michael@0 5436 nsHTMLEditRules::NormalizeSelection(nsISelection *inSelection)
michael@0 5437 {
michael@0 5438 NS_ENSURE_TRUE(inSelection, NS_ERROR_NULL_POINTER);
michael@0 5439
michael@0 5440 // don't need to touch collapsed selections
michael@0 5441 if (inSelection->Collapsed()) {
michael@0 5442 return NS_OK;
michael@0 5443 }
michael@0 5444
michael@0 5445 int32_t rangeCount;
michael@0 5446 nsresult res = inSelection->GetRangeCount(&rangeCount);
michael@0 5447 NS_ENSURE_SUCCESS(res, res);
michael@0 5448
michael@0 5449 // we don't need to mess with cell selections, and we assume multirange selections are those.
michael@0 5450 if (rangeCount != 1) return NS_OK;
michael@0 5451
michael@0 5452 nsCOMPtr<nsIDOMRange> range;
michael@0 5453 res = inSelection->GetRangeAt(0, getter_AddRefs(range));
michael@0 5454 NS_ENSURE_SUCCESS(res, res);
michael@0 5455 NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
michael@0 5456 nsCOMPtr<nsIDOMNode> startNode, endNode;
michael@0 5457 int32_t startOffset, endOffset;
michael@0 5458 nsCOMPtr<nsIDOMNode> newStartNode, newEndNode;
michael@0 5459 int32_t newStartOffset, newEndOffset;
michael@0 5460
michael@0 5461 res = range->GetStartContainer(getter_AddRefs(startNode));
michael@0 5462 NS_ENSURE_SUCCESS(res, res);
michael@0 5463 res = range->GetStartOffset(&startOffset);
michael@0 5464 NS_ENSURE_SUCCESS(res, res);
michael@0 5465 res = range->GetEndContainer(getter_AddRefs(endNode));
michael@0 5466 NS_ENSURE_SUCCESS(res, res);
michael@0 5467 res = range->GetEndOffset(&endOffset);
michael@0 5468 NS_ENSURE_SUCCESS(res, res);
michael@0 5469
michael@0 5470 // adjusted values default to original values
michael@0 5471 newStartNode = startNode;
michael@0 5472 newStartOffset = startOffset;
michael@0 5473 newEndNode = endNode;
michael@0 5474 newEndOffset = endOffset;
michael@0 5475
michael@0 5476 // some locals we need for whitespace code
michael@0 5477 nsCOMPtr<nsIDOMNode> someNode;
michael@0 5478 int32_t offset;
michael@0 5479 WSType wsType;
michael@0 5480
michael@0 5481 // let the whitespace code do the heavy lifting
michael@0 5482 nsWSRunObject wsEndObj(mHTMLEditor, endNode, endOffset);
michael@0 5483 // is there any intervening visible whitespace? if so we can't push selection past that,
michael@0 5484 // it would visibly change maening of users selection
michael@0 5485 wsEndObj.PriorVisibleNode(endNode, endOffset, address_of(someNode),
michael@0 5486 &offset, &wsType);
michael@0 5487 if (wsType != WSType::text && wsType != WSType::normalWS) {
michael@0 5488 // eThisBlock and eOtherBlock conveniently distinquish cases
michael@0 5489 // of going "down" into a block and "up" out of a block.
michael@0 5490 if (wsEndObj.mStartReason == WSType::otherBlock) {
michael@0 5491 // endpoint is just after the close of a block.
michael@0 5492 nsCOMPtr<nsIDOMNode> child = mHTMLEditor->GetRightmostChild(wsEndObj.mStartReasonNode, true);
michael@0 5493 if (child)
michael@0 5494 {
michael@0 5495 newEndNode = nsEditor::GetNodeLocation(child, &newEndOffset);
michael@0 5496 ++newEndOffset; // offset *after* child
michael@0 5497 }
michael@0 5498 // else block is empty - we can leave selection alone here, i think.
michael@0 5499 } else if (wsEndObj.mStartReason == WSType::thisBlock) {
michael@0 5500 // endpoint is just after start of this block
michael@0 5501 nsCOMPtr<nsIDOMNode> child;
michael@0 5502 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5503 res = mHTMLEditor->GetPriorHTMLNode(endNode, endOffset, address_of(child));
michael@0 5504 if (child)
michael@0 5505 {
michael@0 5506 newEndNode = nsEditor::GetNodeLocation(child, &newEndOffset);
michael@0 5507 ++newEndOffset; // offset *after* child
michael@0 5508 }
michael@0 5509 // else block is empty - we can leave selection alone here, i think.
michael@0 5510 } else if (wsEndObj.mStartReason == WSType::br) {
michael@0 5511 // endpoint is just after break. lets adjust it to before it.
michael@0 5512 newEndNode = nsEditor::GetNodeLocation(wsEndObj.mStartReasonNode,
michael@0 5513 &newEndOffset);
michael@0 5514 }
michael@0 5515 }
michael@0 5516
michael@0 5517
michael@0 5518 // similar dealio for start of range
michael@0 5519 nsWSRunObject wsStartObj(mHTMLEditor, startNode, startOffset);
michael@0 5520 // is there any intervening visible whitespace? if so we can't push selection past that,
michael@0 5521 // it would visibly change maening of users selection
michael@0 5522 wsStartObj.NextVisibleNode(startNode, startOffset, address_of(someNode),
michael@0 5523 &offset, &wsType);
michael@0 5524 if (wsType != WSType::text && wsType != WSType::normalWS) {
michael@0 5525 // eThisBlock and eOtherBlock conveniently distinquish cases
michael@0 5526 // of going "down" into a block and "up" out of a block.
michael@0 5527 if (wsStartObj.mEndReason == WSType::otherBlock) {
michael@0 5528 // startpoint is just before the start of a block.
michael@0 5529 nsCOMPtr<nsIDOMNode> child = mHTMLEditor->GetLeftmostChild(wsStartObj.mEndReasonNode, true);
michael@0 5530 if (child)
michael@0 5531 {
michael@0 5532 newStartNode = nsEditor::GetNodeLocation(child, &newStartOffset);
michael@0 5533 }
michael@0 5534 // else block is empty - we can leave selection alone here, i think.
michael@0 5535 } else if (wsStartObj.mEndReason == WSType::thisBlock) {
michael@0 5536 // startpoint is just before end of this block
michael@0 5537 nsCOMPtr<nsIDOMNode> child;
michael@0 5538 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5539 res = mHTMLEditor->GetNextHTMLNode(startNode, startOffset, address_of(child));
michael@0 5540 if (child)
michael@0 5541 {
michael@0 5542 newStartNode = nsEditor::GetNodeLocation(child, &newStartOffset);
michael@0 5543 }
michael@0 5544 // else block is empty - we can leave selection alone here, i think.
michael@0 5545 } else if (wsStartObj.mEndReason == WSType::br) {
michael@0 5546 // startpoint is just before a break. lets adjust it to after it.
michael@0 5547 newStartNode = nsEditor::GetNodeLocation(wsStartObj.mEndReasonNode,
michael@0 5548 &newStartOffset);
michael@0 5549 ++newStartOffset; // offset *after* break
michael@0 5550 }
michael@0 5551 }
michael@0 5552
michael@0 5553 // there is a demented possiblity we have to check for. We might have a very strange selection
michael@0 5554 // that is not collapsed and yet does not contain any editable content, and satisfies some of the
michael@0 5555 // above conditions that cause tweaking. In this case we don't want to tweak the selection into
michael@0 5556 // a block it was never in, etc. There are a variety of strategies one might use to try to
michael@0 5557 // detect these cases, but I think the most straightforward is to see if the adjusted locations
michael@0 5558 // "cross" the old values: ie, new end before old start, or new start after old end. If so
michael@0 5559 // then just leave things alone.
michael@0 5560
michael@0 5561 int16_t comp;
michael@0 5562 comp = nsContentUtils::ComparePoints(startNode, startOffset,
michael@0 5563 newEndNode, newEndOffset);
michael@0 5564 if (comp == 1) return NS_OK; // new end before old start
michael@0 5565 comp = nsContentUtils::ComparePoints(newStartNode, newStartOffset,
michael@0 5566 endNode, endOffset);
michael@0 5567 if (comp == 1) return NS_OK; // new start after old end
michael@0 5568
michael@0 5569 // otherwise set selection to new values.
michael@0 5570 inSelection->Collapse(newStartNode, newStartOffset);
michael@0 5571 inSelection->Extend(newEndNode, newEndOffset);
michael@0 5572 return NS_OK;
michael@0 5573 }
michael@0 5574
michael@0 5575
michael@0 5576 ///////////////////////////////////////////////////////////////////////////
michael@0 5577 // GetPromotedPoint: figure out where a start or end point for a block
michael@0 5578 // operation really is
michael@0 5579 void
michael@0 5580 nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode* aNode,
michael@0 5581 int32_t aOffset,
michael@0 5582 EditAction actionID,
michael@0 5583 nsCOMPtr<nsIDOMNode>* outNode,
michael@0 5584 int32_t* outOffset)
michael@0 5585 {
michael@0 5586 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
michael@0 5587 MOZ_ASSERT(node && outNode && outOffset);
michael@0 5588
michael@0 5589 // default values
michael@0 5590 *outNode = node->AsDOMNode();
michael@0 5591 *outOffset = aOffset;
michael@0 5592
michael@0 5593 // we do one thing for text actions, something else entirely for other
michael@0 5594 // actions
michael@0 5595 if (actionID == EditAction::insertText ||
michael@0 5596 actionID == EditAction::insertIMEText ||
michael@0 5597 actionID == EditAction::insertBreak ||
michael@0 5598 actionID == EditAction::deleteText) {
michael@0 5599 bool isSpace, isNBSP;
michael@0 5600 nsCOMPtr<nsIContent> content = do_QueryInterface(node), temp;
michael@0 5601 // for text actions, we want to look backwards (or forwards, as
michael@0 5602 // appropriate) for additional whitespace or nbsp's. We may have to act on
michael@0 5603 // these later even though they are outside of the initial selection. Even
michael@0 5604 // if they are in another node!
michael@0 5605 while (content) {
michael@0 5606 int32_t offset;
michael@0 5607 if (aWhere == kStart) {
michael@0 5608 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5609 mHTMLEditor->IsPrevCharInNodeWhitespace(content, *outOffset,
michael@0 5610 &isSpace, &isNBSP,
michael@0 5611 getter_AddRefs(temp), &offset);
michael@0 5612 } else {
michael@0 5613 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5614 mHTMLEditor->IsNextCharInNodeWhitespace(content, *outOffset,
michael@0 5615 &isSpace, &isNBSP,
michael@0 5616 getter_AddRefs(temp), &offset);
michael@0 5617 }
michael@0 5618 if (isSpace || isNBSP) {
michael@0 5619 content = temp;
michael@0 5620 *outOffset = offset;
michael@0 5621 } else {
michael@0 5622 break;
michael@0 5623 }
michael@0 5624 }
michael@0 5625
michael@0 5626 *outNode = content->AsDOMNode();
michael@0 5627 return;
michael@0 5628 }
michael@0 5629
michael@0 5630 int32_t offset = aOffset;
michael@0 5631
michael@0 5632 // else not a text section. In this case we want to see if we should grab
michael@0 5633 // any adjacent inline nodes and/or parents and other ancestors
michael@0 5634 if (aWhere == kStart) {
michael@0 5635 // some special casing for text nodes
michael@0 5636 if (node->IsNodeOfType(nsINode::eTEXT)) {
michael@0 5637 if (!node->GetParentNode()) {
michael@0 5638 // Okay, can't promote any further
michael@0 5639 return;
michael@0 5640 }
michael@0 5641 offset = node->GetParentNode()->IndexOf(node);
michael@0 5642 node = node->GetParentNode();
michael@0 5643 }
michael@0 5644
michael@0 5645 // look back through any further inline nodes that aren't across a <br>
michael@0 5646 // from us, and that are enclosed in the same block.
michael@0 5647 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5648 nsCOMPtr<nsINode> priorNode =
michael@0 5649 mHTMLEditor->GetPriorHTMLNode(node, offset, true);
michael@0 5650
michael@0 5651 while (priorNode && priorNode->GetParentNode() &&
michael@0 5652 mHTMLEditor && !mHTMLEditor->IsVisBreak(priorNode->AsDOMNode()) &&
michael@0 5653 !IsBlockNode(priorNode->AsDOMNode())) {
michael@0 5654 offset = priorNode->GetParentNode()->IndexOf(priorNode);
michael@0 5655 node = priorNode->GetParentNode();
michael@0 5656 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5657 priorNode = mHTMLEditor->GetPriorHTMLNode(node, offset, true);
michael@0 5658 }
michael@0 5659
michael@0 5660 // finding the real start for this point. look up the tree for as long as
michael@0 5661 // we are the first node in the container, and as long as we haven't hit
michael@0 5662 // the body node.
michael@0 5663 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5664 nsCOMPtr<nsIContent> nearNode =
michael@0 5665 mHTMLEditor->GetPriorHTMLNode(node, offset, true);
michael@0 5666 while (!nearNode && node->Tag() != nsGkAtoms::body &&
michael@0 5667 node->GetParentNode()) {
michael@0 5668 // some cutoffs are here: we don't need to also include them in the
michael@0 5669 // aWhere == kEnd case. as long as they are in one or the other it will
michael@0 5670 // work. special case for outdent: don't keep looking up if we have
michael@0 5671 // found a blockquote element to act on
michael@0 5672 if (actionID == EditAction::outdent &&
michael@0 5673 node->Tag() == nsGkAtoms::blockquote) {
michael@0 5674 break;
michael@0 5675 }
michael@0 5676
michael@0 5677 int32_t parentOffset = node->GetParentNode()->IndexOf(node);
michael@0 5678 nsCOMPtr<nsINode> parent = node->GetParentNode();
michael@0 5679
michael@0 5680 // Don't walk past the editable section. Note that we need to check
michael@0 5681 // before walking up to a parent because we need to return the parent
michael@0 5682 // object, so the parent itself might not be in the editable area, but
michael@0 5683 // it's OK if we're not performing a block-level action.
michael@0 5684 bool blockLevelAction = actionID == EditAction::indent ||
michael@0 5685 actionID == EditAction::outdent ||
michael@0 5686 actionID == EditAction::align ||
michael@0 5687 actionID == EditAction::makeBasicBlock;
michael@0 5688 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5689 if (!mHTMLEditor->IsDescendantOfEditorRoot(parent) &&
michael@0 5690 (blockLevelAction || !mHTMLEditor ||
michael@0 5691 !mHTMLEditor->IsDescendantOfEditorRoot(node))) {
michael@0 5692 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5693 break;
michael@0 5694 }
michael@0 5695
michael@0 5696 node = parent;
michael@0 5697 offset = parentOffset;
michael@0 5698 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5699 nearNode = mHTMLEditor->GetPriorHTMLNode(node, offset, true);
michael@0 5700 }
michael@0 5701 *outNode = node->AsDOMNode();
michael@0 5702 *outOffset = offset;
michael@0 5703 return;
michael@0 5704 }
michael@0 5705
michael@0 5706 // aWhere == kEnd
michael@0 5707 // some special casing for text nodes
michael@0 5708 if (node->IsNodeOfType(nsINode::eTEXT)) {
michael@0 5709 if (!node->GetParentNode()) {
michael@0 5710 // Okay, can't promote any further
michael@0 5711 return;
michael@0 5712 }
michael@0 5713 // want to be after the text node
michael@0 5714 offset = 1 + node->GetParentNode()->IndexOf(node);
michael@0 5715 node = node->GetParentNode();
michael@0 5716 }
michael@0 5717
michael@0 5718 // look ahead through any further inline nodes that aren't across a <br> from
michael@0 5719 // us, and that are enclosed in the same block.
michael@0 5720 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5721 nsCOMPtr<nsIContent> nextNode =
michael@0 5722 mHTMLEditor->GetNextHTMLNode(node, offset, true);
michael@0 5723
michael@0 5724 while (nextNode && !IsBlockNode(nextNode->AsDOMNode()) &&
michael@0 5725 nextNode->GetParentNode()) {
michael@0 5726 offset = 1 + nextNode->GetParentNode()->IndexOf(nextNode);
michael@0 5727 node = nextNode->GetParentNode();
michael@0 5728 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5729 if (mHTMLEditor->IsVisBreak(nextNode->AsDOMNode())) {
michael@0 5730 break;
michael@0 5731 }
michael@0 5732 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5733 nextNode = mHTMLEditor->GetNextHTMLNode(node, offset, true);
michael@0 5734 }
michael@0 5735
michael@0 5736 // finding the real end for this point. look up the tree for as long as we
michael@0 5737 // are the last node in the container, and as long as we haven't hit the body
michael@0 5738 // node.
michael@0 5739 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5740 nsCOMPtr<nsIContent> nearNode =
michael@0 5741 mHTMLEditor->GetNextHTMLNode(node, offset, true);
michael@0 5742 while (!nearNode && node->Tag() != nsGkAtoms::body &&
michael@0 5743 node->GetParentNode()) {
michael@0 5744 int32_t parentOffset = node->GetParentNode()->IndexOf(node);
michael@0 5745 nsCOMPtr<nsINode> parent = node->GetParentNode();
michael@0 5746
michael@0 5747 // Don't walk past the editable section. Note that we need to check before
michael@0 5748 // walking up to a parent because we need to return the parent object, so
michael@0 5749 // the parent itself might not be in the editable area, but it's OK.
michael@0 5750 if ((!mHTMLEditor || !mHTMLEditor->IsDescendantOfEditorRoot(node)) &&
michael@0 5751 (!mHTMLEditor || !mHTMLEditor->IsDescendantOfEditorRoot(parent))) {
michael@0 5752 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5753 break;
michael@0 5754 }
michael@0 5755
michael@0 5756 node = parent;
michael@0 5757 // we want to be AFTER nearNode
michael@0 5758 offset = parentOffset + 1;
michael@0 5759 NS_ENSURE_TRUE(mHTMLEditor, /* void */);
michael@0 5760 nearNode = mHTMLEditor->GetNextHTMLNode(node, offset, true);
michael@0 5761 }
michael@0 5762 *outNode = node->AsDOMNode();
michael@0 5763 *outOffset = offset;
michael@0 5764 }
michael@0 5765
michael@0 5766
michael@0 5767 ///////////////////////////////////////////////////////////////////////////
michael@0 5768 // GetPromotedRanges: run all the selection range endpoint through
michael@0 5769 // GetPromotedPoint()
michael@0 5770 //
michael@0 5771 nsresult
michael@0 5772 nsHTMLEditRules::GetPromotedRanges(nsISelection *inSelection,
michael@0 5773 nsCOMArray<nsIDOMRange> &outArrayOfRanges,
michael@0 5774 EditAction inOperationType)
michael@0 5775 {
michael@0 5776 NS_ENSURE_TRUE(inSelection, NS_ERROR_NULL_POINTER);
michael@0 5777
michael@0 5778 int32_t rangeCount;
michael@0 5779 nsresult res = inSelection->GetRangeCount(&rangeCount);
michael@0 5780 NS_ENSURE_SUCCESS(res, res);
michael@0 5781
michael@0 5782 int32_t i;
michael@0 5783 nsCOMPtr<nsIDOMRange> selectionRange;
michael@0 5784 nsCOMPtr<nsIDOMRange> opRange;
michael@0 5785
michael@0 5786 for (i = 0; i < rangeCount; i++)
michael@0 5787 {
michael@0 5788 res = inSelection->GetRangeAt(i, getter_AddRefs(selectionRange));
michael@0 5789 NS_ENSURE_SUCCESS(res, res);
michael@0 5790
michael@0 5791 // clone range so we don't muck with actual selection ranges
michael@0 5792 res = selectionRange->CloneRange(getter_AddRefs(opRange));
michael@0 5793 NS_ENSURE_SUCCESS(res, res);
michael@0 5794
michael@0 5795 // make a new adjusted range to represent the appropriate block content.
michael@0 5796 // The basic idea is to push out the range endpoints
michael@0 5797 // to truly enclose the blocks that we will affect.
michael@0 5798 // This call alters opRange.
michael@0 5799 res = PromoteRange(opRange, inOperationType);
michael@0 5800 NS_ENSURE_SUCCESS(res, res);
michael@0 5801
michael@0 5802 // stuff new opRange into array
michael@0 5803 outArrayOfRanges.AppendObject(opRange);
michael@0 5804 }
michael@0 5805 return res;
michael@0 5806 }
michael@0 5807
michael@0 5808
michael@0 5809 ///////////////////////////////////////////////////////////////////////////
michael@0 5810 // PromoteRange: expand a range to include any parents for which all
michael@0 5811 // editable children are already in range.
michael@0 5812 //
michael@0 5813 nsresult
michael@0 5814 nsHTMLEditRules::PromoteRange(nsIDOMRange *inRange,
michael@0 5815 EditAction inOperationType)
michael@0 5816 {
michael@0 5817 NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER);
michael@0 5818 nsresult res;
michael@0 5819 nsCOMPtr<nsIDOMNode> startNode, endNode;
michael@0 5820 int32_t startOffset, endOffset;
michael@0 5821
michael@0 5822 res = inRange->GetStartContainer(getter_AddRefs(startNode));
michael@0 5823 NS_ENSURE_SUCCESS(res, res);
michael@0 5824 res = inRange->GetStartOffset(&startOffset);
michael@0 5825 NS_ENSURE_SUCCESS(res, res);
michael@0 5826 res = inRange->GetEndContainer(getter_AddRefs(endNode));
michael@0 5827 NS_ENSURE_SUCCESS(res, res);
michael@0 5828 res = inRange->GetEndOffset(&endOffset);
michael@0 5829 NS_ENSURE_SUCCESS(res, res);
michael@0 5830
michael@0 5831 // MOOSE major hack:
michael@0 5832 // GetPromotedPoint doesn't really do the right thing for collapsed ranges
michael@0 5833 // inside block elements that contain nothing but a solo <br>. It's easier
michael@0 5834 // to put a workaround here than to revamp GetPromotedPoint. :-(
michael@0 5835 if ( (startNode == endNode) && (startOffset == endOffset))
michael@0 5836 {
michael@0 5837 nsCOMPtr<nsIDOMNode> block;
michael@0 5838 if (IsBlockNode(startNode)) {
michael@0 5839 block = startNode;
michael@0 5840 } else {
michael@0 5841 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5842 block = mHTMLEditor->GetBlockNodeParent(startNode);
michael@0 5843 }
michael@0 5844 if (block)
michael@0 5845 {
michael@0 5846 bool bIsEmptyNode = false;
michael@0 5847 // check for the editing host
michael@0 5848 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5849 nsIContent *rootContent = mHTMLEditor->GetActiveEditingHost();
michael@0 5850 nsCOMPtr<nsINode> rootNode = do_QueryInterface(rootContent);
michael@0 5851 nsCOMPtr<nsINode> blockNode = do_QueryInterface(block);
michael@0 5852 NS_ENSURE_TRUE(rootNode && blockNode, NS_ERROR_UNEXPECTED);
michael@0 5853 // Make sure we don't go higher than our root element in the content tree
michael@0 5854 if (!nsContentUtils::ContentIsDescendantOf(rootNode, blockNode))
michael@0 5855 {
michael@0 5856 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5857 res = mHTMLEditor->IsEmptyNode(block, &bIsEmptyNode, true, false);
michael@0 5858 }
michael@0 5859 if (bIsEmptyNode)
michael@0 5860 {
michael@0 5861 uint32_t numChildren;
michael@0 5862 nsEditor::GetLengthOfDOMNode(block, numChildren);
michael@0 5863 startNode = block;
michael@0 5864 endNode = block;
michael@0 5865 startOffset = 0;
michael@0 5866 endOffset = numChildren;
michael@0 5867 }
michael@0 5868 }
michael@0 5869 }
michael@0 5870
michael@0 5871 // make a new adjusted range to represent the appropriate block content.
michael@0 5872 // this is tricky. the basic idea is to push out the range endpoints
michael@0 5873 // to truly enclose the blocks that we will affect
michael@0 5874
michael@0 5875 nsCOMPtr<nsIDOMNode> opStartNode;
michael@0 5876 nsCOMPtr<nsIDOMNode> opEndNode;
michael@0 5877 int32_t opStartOffset, opEndOffset;
michael@0 5878 nsCOMPtr<nsIDOMRange> opRange;
michael@0 5879
michael@0 5880 GetPromotedPoint(kStart, startNode, startOffset, inOperationType,
michael@0 5881 address_of(opStartNode), &opStartOffset);
michael@0 5882 GetPromotedPoint(kEnd, endNode, endOffset, inOperationType,
michael@0 5883 address_of(opEndNode), &opEndOffset);
michael@0 5884
michael@0 5885 // Make sure that the new range ends up to be in the editable section.
michael@0 5886 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5887 if (!mHTMLEditor->IsDescendantOfEditorRoot(nsEditor::GetNodeAtRangeOffsetPoint(opStartNode, opStartOffset)) ||
michael@0 5888 !mHTMLEditor || // Check again, since it may have gone away
michael@0 5889 !mHTMLEditor->IsDescendantOfEditorRoot(nsEditor::GetNodeAtRangeOffsetPoint(opEndNode, opEndOffset - 1))) {
michael@0 5890 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5891 return NS_OK;
michael@0 5892 }
michael@0 5893
michael@0 5894 res = inRange->SetStart(opStartNode, opStartOffset);
michael@0 5895 NS_ENSURE_SUCCESS(res, res);
michael@0 5896 res = inRange->SetEnd(opEndNode, opEndOffset);
michael@0 5897 return res;
michael@0 5898 }
michael@0 5899
michael@0 5900 class nsUniqueFunctor : public nsBoolDomIterFunctor
michael@0 5901 {
michael@0 5902 public:
michael@0 5903 nsUniqueFunctor(nsCOMArray<nsIDOMNode> &aArray) : mArray(aArray)
michael@0 5904 {
michael@0 5905 }
michael@0 5906 virtual bool operator()(nsIDOMNode* aNode) // used to build list of all nodes iterator covers
michael@0 5907 {
michael@0 5908 return mArray.IndexOf(aNode) < 0;
michael@0 5909 }
michael@0 5910
michael@0 5911 private:
michael@0 5912 nsCOMArray<nsIDOMNode> &mArray;
michael@0 5913 };
michael@0 5914
michael@0 5915 ///////////////////////////////////////////////////////////////////////////
michael@0 5916 // GetNodesForOperation: run through the ranges in the array and construct
michael@0 5917 // a new array of nodes to be acted on.
michael@0 5918 //
michael@0 5919 nsresult
michael@0 5920 nsHTMLEditRules::GetNodesForOperation(nsCOMArray<nsIDOMRange>& inArrayOfRanges,
michael@0 5921 nsCOMArray<nsIDOMNode>& outArrayOfNodes,
michael@0 5922 EditAction inOperationType,
michael@0 5923 bool aDontTouchContent)
michael@0 5924 {
michael@0 5925 int32_t rangeCount = inArrayOfRanges.Count();
michael@0 5926
michael@0 5927 int32_t i;
michael@0 5928 nsCOMPtr<nsIDOMRange> opRange;
michael@0 5929
michael@0 5930 nsresult res = NS_OK;
michael@0 5931
michael@0 5932 // bust up any inlines that cross our range endpoints,
michael@0 5933 // but only if we are allowed to touch content.
michael@0 5934
michael@0 5935 if (!aDontTouchContent)
michael@0 5936 {
michael@0 5937 nsTArray<nsRefPtr<nsRangeStore> > rangeItemArray;
michael@0 5938 if (!rangeItemArray.AppendElements(rangeCount)) {
michael@0 5939 return NS_ERROR_OUT_OF_MEMORY;
michael@0 5940 }
michael@0 5941
michael@0 5942 NS_ASSERTION(static_cast<uint32_t>(rangeCount) == rangeItemArray.Length(),
michael@0 5943 "How did that happen?");
michael@0 5944
michael@0 5945 // first register ranges for special editor gravity
michael@0 5946 for (i = 0; i < rangeCount; i++)
michael@0 5947 {
michael@0 5948 opRange = inArrayOfRanges[0];
michael@0 5949 rangeItemArray[i] = new nsRangeStore();
michael@0 5950 rangeItemArray[i]->StoreRange(opRange);
michael@0 5951 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5952 mHTMLEditor->mRangeUpdater.RegisterRangeItem(rangeItemArray[i]);
michael@0 5953 inArrayOfRanges.RemoveObjectAt(0);
michael@0 5954 }
michael@0 5955 // now bust up inlines. Safe to start at rangeCount-1, since we
michael@0 5956 // asserted we have enough items above.
michael@0 5957 for (i = rangeCount-1; i >= 0 && NS_SUCCEEDED(res); i--)
michael@0 5958 {
michael@0 5959 res = BustUpInlinesAtRangeEndpoints(*rangeItemArray[i]);
michael@0 5960 }
michael@0 5961 // then unregister the ranges
michael@0 5962 for (i = 0; i < rangeCount; i++)
michael@0 5963 {
michael@0 5964 nsRangeStore* item = rangeItemArray[i];
michael@0 5965 NS_ENSURE_STATE(mHTMLEditor);
michael@0 5966 mHTMLEditor->mRangeUpdater.DropRangeItem(item);
michael@0 5967 nsRefPtr<nsRange> range;
michael@0 5968 nsresult res2 = item->GetRange(getter_AddRefs(range));
michael@0 5969 opRange = range;
michael@0 5970 if (NS_FAILED(res2) && NS_SUCCEEDED(res)) {
michael@0 5971 // Remember the failure, but keep going so we make sure to unregister
michael@0 5972 // all our range items.
michael@0 5973 res = res2;
michael@0 5974 }
michael@0 5975 inArrayOfRanges.AppendObject(opRange);
michael@0 5976 }
michael@0 5977 NS_ENSURE_SUCCESS(res, res);
michael@0 5978 }
michael@0 5979 // gather up a list of all the nodes
michael@0 5980 for (i = 0; i < rangeCount; i++)
michael@0 5981 {
michael@0 5982 opRange = inArrayOfRanges[i];
michael@0 5983
michael@0 5984 nsDOMSubtreeIterator iter;
michael@0 5985 res = iter.Init(opRange);
michael@0 5986 NS_ENSURE_SUCCESS(res, res);
michael@0 5987 if (outArrayOfNodes.Count() == 0) {
michael@0 5988 nsTrivialFunctor functor;
michael@0 5989 res = iter.AppendList(functor, outArrayOfNodes);
michael@0 5990 NS_ENSURE_SUCCESS(res, res);
michael@0 5991 }
michael@0 5992 else {
michael@0 5993 // We don't want duplicates in outArrayOfNodes, so we use an
michael@0 5994 // iterator/functor that only return nodes that are not already in
michael@0 5995 // outArrayOfNodes.
michael@0 5996 nsCOMArray<nsIDOMNode> nodes;
michael@0 5997 nsUniqueFunctor functor(outArrayOfNodes);
michael@0 5998 res = iter.AppendList(functor, nodes);
michael@0 5999 NS_ENSURE_SUCCESS(res, res);
michael@0 6000 if (!outArrayOfNodes.AppendObjects(nodes))
michael@0 6001 return NS_ERROR_OUT_OF_MEMORY;
michael@0 6002 }
michael@0 6003 }
michael@0 6004
michael@0 6005 // certain operations should not act on li's and td's, but rather inside
michael@0 6006 // them. alter the list as needed
michael@0 6007 if (inOperationType == EditAction::makeBasicBlock) {
michael@0 6008 int32_t listCount = outArrayOfNodes.Count();
michael@0 6009 for (i=listCount-1; i>=0; i--)
michael@0 6010 {
michael@0 6011 nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
michael@0 6012 if (nsHTMLEditUtils::IsListItem(node))
michael@0 6013 {
michael@0 6014 int32_t j=i;
michael@0 6015 outArrayOfNodes.RemoveObjectAt(i);
michael@0 6016 res = GetInnerContent(node, outArrayOfNodes, &j);
michael@0 6017 NS_ENSURE_SUCCESS(res, res);
michael@0 6018 }
michael@0 6019 }
michael@0 6020 }
michael@0 6021 // indent/outdent already do something special for list items, but
michael@0 6022 // we still need to make sure we don't act on table elements
michael@0 6023 else if (inOperationType == EditAction::outdent ||
michael@0 6024 inOperationType == EditAction::indent ||
michael@0 6025 inOperationType == EditAction::setAbsolutePosition) {
michael@0 6026 int32_t listCount = outArrayOfNodes.Count();
michael@0 6027 for (i=listCount-1; i>=0; i--)
michael@0 6028 {
michael@0 6029 nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
michael@0 6030 if (nsHTMLEditUtils::IsTableElementButNotTable(node))
michael@0 6031 {
michael@0 6032 int32_t j=i;
michael@0 6033 outArrayOfNodes.RemoveObjectAt(i);
michael@0 6034 res = GetInnerContent(node, outArrayOfNodes, &j);
michael@0 6035 NS_ENSURE_SUCCESS(res, res);
michael@0 6036 }
michael@0 6037 }
michael@0 6038 }
michael@0 6039 // outdent should look inside of divs.
michael@0 6040 if (inOperationType == EditAction::outdent &&
michael@0 6041 (!mHTMLEditor || !mHTMLEditor->IsCSSEnabled())) {
michael@0 6042 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6043 int32_t listCount = outArrayOfNodes.Count();
michael@0 6044 for (i=listCount-1; i>=0; i--)
michael@0 6045 {
michael@0 6046 nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
michael@0 6047 if (nsHTMLEditUtils::IsDiv(node))
michael@0 6048 {
michael@0 6049 int32_t j=i;
michael@0 6050 outArrayOfNodes.RemoveObjectAt(i);
michael@0 6051 res = GetInnerContent(node, outArrayOfNodes, &j, false, false);
michael@0 6052 NS_ENSURE_SUCCESS(res, res);
michael@0 6053 }
michael@0 6054 }
michael@0 6055 }
michael@0 6056
michael@0 6057
michael@0 6058 // post process the list to break up inline containers that contain br's.
michael@0 6059 // but only for operations that might care, like making lists or para's...
michael@0 6060 if (inOperationType == EditAction::makeBasicBlock ||
michael@0 6061 inOperationType == EditAction::makeList ||
michael@0 6062 inOperationType == EditAction::align ||
michael@0 6063 inOperationType == EditAction::setAbsolutePosition ||
michael@0 6064 inOperationType == EditAction::indent ||
michael@0 6065 inOperationType == EditAction::outdent) {
michael@0 6066 int32_t listCount = outArrayOfNodes.Count();
michael@0 6067 for (i=listCount-1; i>=0; i--)
michael@0 6068 {
michael@0 6069 nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
michael@0 6070 if (!aDontTouchContent && IsInlineNode(node) &&
michael@0 6071 (!mHTMLEditor || mHTMLEditor->IsContainer(node)) &&
michael@0 6072 (!mHTMLEditor || !mHTMLEditor->IsTextNode(node)))
michael@0 6073 {
michael@0 6074 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6075 nsCOMArray<nsIDOMNode> arrayOfInlines;
michael@0 6076 res = BustUpInlinesAtBRs(node, arrayOfInlines);
michael@0 6077 NS_ENSURE_SUCCESS(res, res);
michael@0 6078 // put these nodes in outArrayOfNodes, replacing the current node
michael@0 6079 outArrayOfNodes.RemoveObjectAt(i);
michael@0 6080 outArrayOfNodes.InsertObjectsAt(arrayOfInlines, i);
michael@0 6081 }
michael@0 6082 }
michael@0 6083 }
michael@0 6084 return res;
michael@0 6085 }
michael@0 6086
michael@0 6087
michael@0 6088
michael@0 6089 ///////////////////////////////////////////////////////////////////////////
michael@0 6090 // GetChildNodesForOperation:
michael@0 6091 //
michael@0 6092 nsresult
michael@0 6093 nsHTMLEditRules::GetChildNodesForOperation(nsIDOMNode *inNode,
michael@0 6094 nsCOMArray<nsIDOMNode>& outArrayOfNodes)
michael@0 6095 {
michael@0 6096 nsCOMPtr<nsINode> node = do_QueryInterface(inNode);
michael@0 6097 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
michael@0 6098
michael@0 6099 for (nsIContent* child = node->GetFirstChild();
michael@0 6100 child;
michael@0 6101 child = child->GetNextSibling()) {
michael@0 6102 nsIDOMNode* childNode = child->AsDOMNode();
michael@0 6103 if (!outArrayOfNodes.AppendObject(childNode)) {
michael@0 6104 return NS_ERROR_FAILURE;
michael@0 6105 }
michael@0 6106 }
michael@0 6107 return NS_OK;
michael@0 6108 }
michael@0 6109
michael@0 6110
michael@0 6111
michael@0 6112 ///////////////////////////////////////////////////////////////////////////
michael@0 6113 // GetListActionNodes:
michael@0 6114 //
michael@0 6115 nsresult
michael@0 6116 nsHTMLEditRules::GetListActionNodes(nsCOMArray<nsIDOMNode> &outArrayOfNodes,
michael@0 6117 bool aEntireList,
michael@0 6118 bool aDontTouchContent)
michael@0 6119 {
michael@0 6120 nsresult res = NS_OK;
michael@0 6121
michael@0 6122 nsCOMPtr<nsISelection>selection;
michael@0 6123 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6124 res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
michael@0 6125 NS_ENSURE_SUCCESS(res, res);
michael@0 6126 Selection* sel = static_cast<Selection*>(selection.get());
michael@0 6127 NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE);
michael@0 6128 // added this in so that ui code can ask to change an entire list, even if selection
michael@0 6129 // is only in part of it. used by list item dialog.
michael@0 6130 if (aEntireList)
michael@0 6131 {
michael@0 6132 uint32_t rangeCount = sel->GetRangeCount();
michael@0 6133 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
michael@0 6134 nsRefPtr<nsRange> range = sel->GetRangeAt(rangeIdx);
michael@0 6135 nsCOMPtr<nsIDOMNode> commonParent, parent, tmp;
michael@0 6136 range->GetCommonAncestorContainer(getter_AddRefs(commonParent));
michael@0 6137 if (commonParent)
michael@0 6138 {
michael@0 6139 parent = commonParent;
michael@0 6140 while (parent)
michael@0 6141 {
michael@0 6142 if (nsHTMLEditUtils::IsList(parent))
michael@0 6143 {
michael@0 6144 outArrayOfNodes.AppendObject(parent);
michael@0 6145 break;
michael@0 6146 }
michael@0 6147 parent->GetParentNode(getter_AddRefs(tmp));
michael@0 6148 parent = tmp;
michael@0 6149 }
michael@0 6150 }
michael@0 6151 }
michael@0 6152 // if we didn't find any nodes this way, then try the normal way. perhaps the
michael@0 6153 // selection spans multiple lists but with no common list parent.
michael@0 6154 if (outArrayOfNodes.Count()) return NS_OK;
michael@0 6155 }
michael@0 6156
michael@0 6157 {
michael@0 6158 // We don't like other people messing with our selection!
michael@0 6159 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6160 nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
michael@0 6161
michael@0 6162 // contruct a list of nodes to act on.
michael@0 6163 res = GetNodesFromSelection(selection, EditAction::makeList,
michael@0 6164 outArrayOfNodes, aDontTouchContent);
michael@0 6165 NS_ENSURE_SUCCESS(res, res);
michael@0 6166 }
michael@0 6167
michael@0 6168 // pre process our list of nodes...
michael@0 6169 int32_t listCount = outArrayOfNodes.Count();
michael@0 6170 int32_t i;
michael@0 6171 for (i=listCount-1; i>=0; i--)
michael@0 6172 {
michael@0 6173 nsCOMPtr<nsIDOMNode> testNode = outArrayOfNodes[i];
michael@0 6174
michael@0 6175 // Remove all non-editable nodes. Leave them be.
michael@0 6176 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6177 if (!mHTMLEditor->IsEditable(testNode))
michael@0 6178 {
michael@0 6179 outArrayOfNodes.RemoveObjectAt(i);
michael@0 6180 }
michael@0 6181
michael@0 6182 // scan for table elements and divs. If we find table elements other than table,
michael@0 6183 // replace it with a list of any editable non-table content.
michael@0 6184 if (nsHTMLEditUtils::IsTableElementButNotTable(testNode))
michael@0 6185 {
michael@0 6186 int32_t j=i;
michael@0 6187 outArrayOfNodes.RemoveObjectAt(i);
michael@0 6188 res = GetInnerContent(testNode, outArrayOfNodes, &j, false);
michael@0 6189 NS_ENSURE_SUCCESS(res, res);
michael@0 6190 }
michael@0 6191 }
michael@0 6192
michael@0 6193 // if there is only one node in the array, and it is a list, div, or blockquote,
michael@0 6194 // then look inside of it until we find inner list or content.
michael@0 6195 res = LookInsideDivBQandList(outArrayOfNodes);
michael@0 6196 return res;
michael@0 6197 }
michael@0 6198
michael@0 6199
michael@0 6200 ///////////////////////////////////////////////////////////////////////////
michael@0 6201 // LookInsideDivBQandList:
michael@0 6202 //
michael@0 6203 nsresult
michael@0 6204 nsHTMLEditRules::LookInsideDivBQandList(nsCOMArray<nsIDOMNode>& aNodeArray)
michael@0 6205 {
michael@0 6206 // if there is only one node in the array, and it is a list, div, or blockquote,
michael@0 6207 // then look inside of it until we find inner list or content.
michael@0 6208 int32_t listCount = aNodeArray.Count();
michael@0 6209 if (listCount != 1) {
michael@0 6210 return NS_OK;
michael@0 6211 }
michael@0 6212
michael@0 6213 nsCOMPtr<nsINode> curNode = do_QueryInterface(aNodeArray[0]);
michael@0 6214 NS_ENSURE_STATE(curNode);
michael@0 6215
michael@0 6216 while (curNode->IsElement() &&
michael@0 6217 (curNode->AsElement()->IsHTML(nsGkAtoms::div) ||
michael@0 6218 nsHTMLEditUtils::IsList(curNode) ||
michael@0 6219 curNode->AsElement()->IsHTML(nsGkAtoms::blockquote))) {
michael@0 6220 // dive as long as there is only one child, and it is a list, div, blockquote
michael@0 6221 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6222 uint32_t numChildren = mHTMLEditor->CountEditableChildren(curNode);
michael@0 6223 if (numChildren != 1) {
michael@0 6224 break;
michael@0 6225 }
michael@0 6226
michael@0 6227 // keep diving
michael@0 6228 // XXX One would expect to dive into the one editable node.
michael@0 6229 nsIContent* tmp = curNode->GetFirstChild();
michael@0 6230 if (!tmp->IsElement()) {
michael@0 6231 break;
michael@0 6232 }
michael@0 6233
michael@0 6234 dom::Element* element = tmp->AsElement();
michael@0 6235 if (!element->IsHTML(nsGkAtoms::div) &&
michael@0 6236 !nsHTMLEditUtils::IsList(element) &&
michael@0 6237 !element->IsHTML(nsGkAtoms::blockquote)) {
michael@0 6238 break;
michael@0 6239 }
michael@0 6240
michael@0 6241 // check editablility XXX floppy moose
michael@0 6242 curNode = tmp;
michael@0 6243 }
michael@0 6244
michael@0 6245 // we've found innermost list/blockquote/div:
michael@0 6246 // replace the one node in the array with these nodes
michael@0 6247 aNodeArray.RemoveObjectAt(0);
michael@0 6248 if (curNode->IsElement() &&
michael@0 6249 (curNode->AsElement()->IsHTML(nsGkAtoms::div) ||
michael@0 6250 curNode->AsElement()->IsHTML(nsGkAtoms::blockquote))) {
michael@0 6251 int32_t j = 0;
michael@0 6252 return GetInnerContent(curNode->AsDOMNode(), aNodeArray, &j, false, false);
michael@0 6253 }
michael@0 6254
michael@0 6255 aNodeArray.AppendObject(curNode->AsDOMNode());
michael@0 6256 return NS_OK;
michael@0 6257 }
michael@0 6258
michael@0 6259
michael@0 6260 ///////////////////////////////////////////////////////////////////////////
michael@0 6261 // GetDefinitionListItemTypes:
michael@0 6262 //
michael@0 6263 void
michael@0 6264 nsHTMLEditRules::GetDefinitionListItemTypes(dom::Element* aElement, bool* aDT, bool* aDD)
michael@0 6265 {
michael@0 6266 MOZ_ASSERT(aElement);
michael@0 6267 MOZ_ASSERT(aElement->IsHTML(nsGkAtoms::dl));
michael@0 6268 MOZ_ASSERT(aDT);
michael@0 6269 MOZ_ASSERT(aDD);
michael@0 6270
michael@0 6271 *aDT = *aDD = false;
michael@0 6272 for (nsIContent* child = aElement->GetFirstChild();
michael@0 6273 child;
michael@0 6274 child = child->GetNextSibling()) {
michael@0 6275 if (child->IsHTML(nsGkAtoms::dt)) {
michael@0 6276 *aDT = true;
michael@0 6277 } else if (child->IsHTML(nsGkAtoms::dd)) {
michael@0 6278 *aDD = true;
michael@0 6279 }
michael@0 6280 }
michael@0 6281 }
michael@0 6282
michael@0 6283 ///////////////////////////////////////////////////////////////////////////
michael@0 6284 // GetParagraphFormatNodes:
michael@0 6285 //
michael@0 6286 nsresult
michael@0 6287 nsHTMLEditRules::GetParagraphFormatNodes(nsCOMArray<nsIDOMNode>& outArrayOfNodes,
michael@0 6288 bool aDontTouchContent)
michael@0 6289 {
michael@0 6290 nsCOMPtr<nsISelection>selection;
michael@0 6291 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6292 nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
michael@0 6293 NS_ENSURE_SUCCESS(res, res);
michael@0 6294
michael@0 6295 // contruct a list of nodes to act on.
michael@0 6296 res = GetNodesFromSelection(selection, EditAction::makeBasicBlock,
michael@0 6297 outArrayOfNodes, aDontTouchContent);
michael@0 6298 NS_ENSURE_SUCCESS(res, res);
michael@0 6299
michael@0 6300 // pre process our list of nodes...
michael@0 6301 int32_t listCount = outArrayOfNodes.Count();
michael@0 6302 int32_t i;
michael@0 6303 for (i=listCount-1; i>=0; i--)
michael@0 6304 {
michael@0 6305 nsCOMPtr<nsIDOMNode> testNode = outArrayOfNodes[i];
michael@0 6306
michael@0 6307 // Remove all non-editable nodes. Leave them be.
michael@0 6308 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6309 if (!mHTMLEditor->IsEditable(testNode))
michael@0 6310 {
michael@0 6311 outArrayOfNodes.RemoveObjectAt(i);
michael@0 6312 }
michael@0 6313
michael@0 6314 // scan for table elements. If we find table elements other than table,
michael@0 6315 // replace it with a list of any editable non-table content. Ditto for list elements.
michael@0 6316 if (nsHTMLEditUtils::IsTableElement(testNode) ||
michael@0 6317 nsHTMLEditUtils::IsList(testNode) ||
michael@0 6318 nsHTMLEditUtils::IsListItem(testNode) )
michael@0 6319 {
michael@0 6320 int32_t j=i;
michael@0 6321 outArrayOfNodes.RemoveObjectAt(i);
michael@0 6322 res = GetInnerContent(testNode, outArrayOfNodes, &j);
michael@0 6323 NS_ENSURE_SUCCESS(res, res);
michael@0 6324 }
michael@0 6325 }
michael@0 6326 return res;
michael@0 6327 }
michael@0 6328
michael@0 6329
michael@0 6330 ///////////////////////////////////////////////////////////////////////////
michael@0 6331 // BustUpInlinesAtRangeEndpoints:
michael@0 6332 //
michael@0 6333 nsresult
michael@0 6334 nsHTMLEditRules::BustUpInlinesAtRangeEndpoints(nsRangeStore &item)
michael@0 6335 {
michael@0 6336 nsresult res = NS_OK;
michael@0 6337 bool isCollapsed = ((item.startNode == item.endNode) && (item.startOffset == item.endOffset));
michael@0 6338
michael@0 6339 nsCOMPtr<nsIDOMNode> endInline = GetHighestInlineParent(item.endNode);
michael@0 6340
michael@0 6341 // if we have inline parents above range endpoints, split them
michael@0 6342 if (endInline && !isCollapsed)
michael@0 6343 {
michael@0 6344 nsCOMPtr<nsIDOMNode> resultEndNode;
michael@0 6345 int32_t resultEndOffset;
michael@0 6346 endInline->GetParentNode(getter_AddRefs(resultEndNode));
michael@0 6347 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6348 res = mHTMLEditor->SplitNodeDeep(endInline, item.endNode, item.endOffset,
michael@0 6349 &resultEndOffset, true);
michael@0 6350 NS_ENSURE_SUCCESS(res, res);
michael@0 6351 // reset range
michael@0 6352 item.endNode = resultEndNode; item.endOffset = resultEndOffset;
michael@0 6353 }
michael@0 6354
michael@0 6355 nsCOMPtr<nsIDOMNode> startInline = GetHighestInlineParent(item.startNode);
michael@0 6356
michael@0 6357 if (startInline)
michael@0 6358 {
michael@0 6359 nsCOMPtr<nsIDOMNode> resultStartNode;
michael@0 6360 int32_t resultStartOffset;
michael@0 6361 startInline->GetParentNode(getter_AddRefs(resultStartNode));
michael@0 6362 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6363 res = mHTMLEditor->SplitNodeDeep(startInline, item.startNode, item.startOffset,
michael@0 6364 &resultStartOffset, true);
michael@0 6365 NS_ENSURE_SUCCESS(res, res);
michael@0 6366 // reset range
michael@0 6367 item.startNode = resultStartNode; item.startOffset = resultStartOffset;
michael@0 6368 }
michael@0 6369
michael@0 6370 return res;
michael@0 6371 }
michael@0 6372
michael@0 6373
michael@0 6374
michael@0 6375 ///////////////////////////////////////////////////////////////////////////
michael@0 6376 // BustUpInlinesAtBRs:
michael@0 6377 //
michael@0 6378 nsresult
michael@0 6379 nsHTMLEditRules::BustUpInlinesAtBRs(nsIDOMNode *inNode,
michael@0 6380 nsCOMArray<nsIDOMNode>& outArrayOfNodes)
michael@0 6381 {
michael@0 6382 NS_ENSURE_TRUE(inNode, NS_ERROR_NULL_POINTER);
michael@0 6383
michael@0 6384 // first step is to build up a list of all the break nodes inside
michael@0 6385 // the inline container.
michael@0 6386 nsCOMArray<nsIDOMNode> arrayOfBreaks;
michael@0 6387 nsBRNodeFunctor functor;
michael@0 6388 nsDOMIterator iter;
michael@0 6389 nsresult res = iter.Init(inNode);
michael@0 6390 NS_ENSURE_SUCCESS(res, res);
michael@0 6391 res = iter.AppendList(functor, arrayOfBreaks);
michael@0 6392 NS_ENSURE_SUCCESS(res, res);
michael@0 6393
michael@0 6394 // if there aren't any breaks, just put inNode itself in the array
michael@0 6395 int32_t listCount = arrayOfBreaks.Count();
michael@0 6396 if (!listCount)
michael@0 6397 {
michael@0 6398 if (!outArrayOfNodes.AppendObject(inNode))
michael@0 6399 return NS_ERROR_FAILURE;
michael@0 6400 }
michael@0 6401 else
michael@0 6402 {
michael@0 6403 // else we need to bust up inNode along all the breaks
michael@0 6404 nsCOMPtr<nsIDOMNode> breakNode;
michael@0 6405 nsCOMPtr<nsIDOMNode> inlineParentNode;
michael@0 6406 nsCOMPtr<nsIDOMNode> leftNode;
michael@0 6407 nsCOMPtr<nsIDOMNode> rightNode;
michael@0 6408 nsCOMPtr<nsIDOMNode> splitDeepNode = inNode;
michael@0 6409 nsCOMPtr<nsIDOMNode> splitParentNode;
michael@0 6410 int32_t splitOffset, resultOffset, i;
michael@0 6411 inNode->GetParentNode(getter_AddRefs(inlineParentNode));
michael@0 6412
michael@0 6413 for (i=0; i< listCount; i++)
michael@0 6414 {
michael@0 6415 breakNode = arrayOfBreaks[i];
michael@0 6416 NS_ENSURE_TRUE(breakNode, NS_ERROR_NULL_POINTER);
michael@0 6417 NS_ENSURE_TRUE(splitDeepNode, NS_ERROR_NULL_POINTER);
michael@0 6418 splitParentNode = nsEditor::GetNodeLocation(breakNode, &splitOffset);
michael@0 6419 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6420 res = mHTMLEditor->SplitNodeDeep(splitDeepNode, splitParentNode, splitOffset,
michael@0 6421 &resultOffset, false, address_of(leftNode), address_of(rightNode));
michael@0 6422 NS_ENSURE_SUCCESS(res, res);
michael@0 6423 // put left node in node list
michael@0 6424 if (leftNode)
michael@0 6425 {
michael@0 6426 // might not be a left node. a break might have been at the very
michael@0 6427 // beginning of inline container, in which case splitnodedeep
michael@0 6428 // would not actually split anything
michael@0 6429 if (!outArrayOfNodes.AppendObject(leftNode))
michael@0 6430 return NS_ERROR_FAILURE;
michael@0 6431 }
michael@0 6432 // move break outside of container and also put in node list
michael@0 6433 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6434 res = mHTMLEditor->MoveNode(breakNode, inlineParentNode, resultOffset);
michael@0 6435 NS_ENSURE_SUCCESS(res, res);
michael@0 6436 if (!outArrayOfNodes.AppendObject(breakNode))
michael@0 6437 return NS_ERROR_FAILURE;
michael@0 6438 // now rightNode becomes the new node to split
michael@0 6439 splitDeepNode = rightNode;
michael@0 6440 }
michael@0 6441 // now tack on remaining rightNode, if any, to the list
michael@0 6442 if (rightNode)
michael@0 6443 {
michael@0 6444 if (!outArrayOfNodes.AppendObject(rightNode))
michael@0 6445 return NS_ERROR_FAILURE;
michael@0 6446 }
michael@0 6447 }
michael@0 6448 return res;
michael@0 6449 }
michael@0 6450
michael@0 6451
michael@0 6452 nsCOMPtr<nsIDOMNode>
michael@0 6453 nsHTMLEditRules::GetHighestInlineParent(nsIDOMNode* aNode)
michael@0 6454 {
michael@0 6455 NS_ENSURE_TRUE(aNode, nullptr);
michael@0 6456 if (IsBlockNode(aNode)) return nullptr;
michael@0 6457 nsCOMPtr<nsIDOMNode> inlineNode, node=aNode;
michael@0 6458
michael@0 6459 while (node && IsInlineNode(node))
michael@0 6460 {
michael@0 6461 inlineNode = node;
michael@0 6462 inlineNode->GetParentNode(getter_AddRefs(node));
michael@0 6463 }
michael@0 6464 return inlineNode;
michael@0 6465 }
michael@0 6466
michael@0 6467
michael@0 6468 ///////////////////////////////////////////////////////////////////////////
michael@0 6469 // GetNodesFromPoint: given a particular operation, construct a list
michael@0 6470 // of nodes from a point that will be operated on.
michael@0 6471 //
michael@0 6472 nsresult
michael@0 6473 nsHTMLEditRules::GetNodesFromPoint(::DOMPoint point,
michael@0 6474 EditAction operation,
michael@0 6475 nsCOMArray<nsIDOMNode> &arrayOfNodes,
michael@0 6476 bool dontTouchContent)
michael@0 6477 {
michael@0 6478 nsresult res;
michael@0 6479
michael@0 6480 // get our point
michael@0 6481 nsCOMPtr<nsIDOMNode> node;
michael@0 6482 int32_t offset;
michael@0 6483 point.GetPoint(node, offset);
michael@0 6484
michael@0 6485 // use it to make a range
michael@0 6486 nsCOMPtr<nsINode> nativeNode = do_QueryInterface(node);
michael@0 6487 NS_ENSURE_STATE(nativeNode);
michael@0 6488 nsRefPtr<nsRange> range = new nsRange(nativeNode);
michael@0 6489 res = range->SetStart(node, offset);
michael@0 6490 NS_ENSURE_SUCCESS(res, res);
michael@0 6491 /* SetStart() will also set the end for this new range
michael@0 6492 res = range->SetEnd(node, offset);
michael@0 6493 NS_ENSURE_SUCCESS(res, res); */
michael@0 6494
michael@0 6495 // expand the range to include adjacent inlines
michael@0 6496 res = PromoteRange(range, operation);
michael@0 6497 NS_ENSURE_SUCCESS(res, res);
michael@0 6498
michael@0 6499 // make array of ranges
michael@0 6500 nsCOMArray<nsIDOMRange> arrayOfRanges;
michael@0 6501
michael@0 6502 // stuff new opRange into array
michael@0 6503 arrayOfRanges.AppendObject(range);
michael@0 6504
michael@0 6505 // use these ranges to contruct a list of nodes to act on.
michael@0 6506 res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, operation, dontTouchContent);
michael@0 6507 return res;
michael@0 6508 }
michael@0 6509
michael@0 6510
michael@0 6511 ///////////////////////////////////////////////////////////////////////////
michael@0 6512 // GetNodesFromSelection: given a particular operation, construct a list
michael@0 6513 // of nodes from the selection that will be operated on.
michael@0 6514 //
michael@0 6515 nsresult
michael@0 6516 nsHTMLEditRules::GetNodesFromSelection(nsISelection *selection,
michael@0 6517 EditAction operation,
michael@0 6518 nsCOMArray<nsIDOMNode>& arrayOfNodes,
michael@0 6519 bool dontTouchContent)
michael@0 6520 {
michael@0 6521 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
michael@0 6522 nsresult res;
michael@0 6523
michael@0 6524 // promote selection ranges
michael@0 6525 nsCOMArray<nsIDOMRange> arrayOfRanges;
michael@0 6526 res = GetPromotedRanges(selection, arrayOfRanges, operation);
michael@0 6527 NS_ENSURE_SUCCESS(res, res);
michael@0 6528
michael@0 6529 // use these ranges to contruct a list of nodes to act on.
michael@0 6530 res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, operation, dontTouchContent);
michael@0 6531 return res;
michael@0 6532 }
michael@0 6533
michael@0 6534
michael@0 6535 ///////////////////////////////////////////////////////////////////////////
michael@0 6536 // MakeTransitionList: detect all the transitions in the array, where a
michael@0 6537 // transition means that adjacent nodes in the array
michael@0 6538 // don't have the same parent.
michael@0 6539 //
michael@0 6540 nsresult
michael@0 6541 nsHTMLEditRules::MakeTransitionList(nsCOMArray<nsIDOMNode>& inArrayOfNodes,
michael@0 6542 nsTArray<bool> &inTransitionArray)
michael@0 6543 {
michael@0 6544 uint32_t listCount = inArrayOfNodes.Count();
michael@0 6545 inTransitionArray.EnsureLengthAtLeast(listCount);
michael@0 6546 uint32_t i;
michael@0 6547 nsCOMPtr<nsIDOMNode> prevElementParent;
michael@0 6548 nsCOMPtr<nsIDOMNode> curElementParent;
michael@0 6549
michael@0 6550 for (i=0; i<listCount; i++)
michael@0 6551 {
michael@0 6552 nsIDOMNode* transNode = inArrayOfNodes[i];
michael@0 6553 transNode->GetParentNode(getter_AddRefs(curElementParent));
michael@0 6554 if (curElementParent != prevElementParent)
michael@0 6555 {
michael@0 6556 // different parents, or separated by <br>: transition point
michael@0 6557 inTransitionArray[i] = true;
michael@0 6558 }
michael@0 6559 else
michael@0 6560 {
michael@0 6561 // same parents: these nodes grew up together
michael@0 6562 inTransitionArray[i] = false;
michael@0 6563 }
michael@0 6564 prevElementParent = curElementParent;
michael@0 6565 }
michael@0 6566 return NS_OK;
michael@0 6567 }
michael@0 6568
michael@0 6569
michael@0 6570
michael@0 6571 /********************************************************
michael@0 6572 * main implementation methods
michael@0 6573 ********************************************************/
michael@0 6574
michael@0 6575 ///////////////////////////////////////////////////////////////////////////
michael@0 6576 // IsInListItem: if aNode is the descendant of a listitem, return that li.
michael@0 6577 // But table element boundaries are stoppers on the search.
michael@0 6578 // Also stops on the active editor host (contenteditable).
michael@0 6579 // Also test if aNode is an li itself.
michael@0 6580 //
michael@0 6581 already_AddRefed<nsIDOMNode>
michael@0 6582 nsHTMLEditRules::IsInListItem(nsIDOMNode* aNode)
michael@0 6583 {
michael@0 6584 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
michael@0 6585 nsCOMPtr<nsIDOMNode> retval = do_QueryInterface(IsInListItem(node));
michael@0 6586 return retval.forget();
michael@0 6587 }
michael@0 6588
michael@0 6589 nsINode*
michael@0 6590 nsHTMLEditRules::IsInListItem(nsINode* aNode)
michael@0 6591 {
michael@0 6592 NS_ENSURE_TRUE(aNode, nullptr);
michael@0 6593 if (nsHTMLEditUtils::IsListItem(aNode)) {
michael@0 6594 return aNode;
michael@0 6595 }
michael@0 6596
michael@0 6597 nsINode* parent = aNode->GetParentNode();
michael@0 6598 while (parent && mHTMLEditor && mHTMLEditor->IsDescendantOfEditorRoot(parent) &&
michael@0 6599 !nsHTMLEditUtils::IsTableElement(parent)) {
michael@0 6600 if (nsHTMLEditUtils::IsListItem(parent)) {
michael@0 6601 return parent;
michael@0 6602 }
michael@0 6603 parent = parent->GetParentNode();
michael@0 6604 }
michael@0 6605 return nullptr;
michael@0 6606 }
michael@0 6607
michael@0 6608
michael@0 6609 ///////////////////////////////////////////////////////////////////////////
michael@0 6610 // ReturnInHeader: do the right thing for returns pressed in headers
michael@0 6611 //
michael@0 6612 nsresult
michael@0 6613 nsHTMLEditRules::ReturnInHeader(nsISelection *aSelection,
michael@0 6614 nsIDOMNode *aHeader,
michael@0 6615 nsIDOMNode *aNode,
michael@0 6616 int32_t aOffset)
michael@0 6617 {
michael@0 6618 NS_ENSURE_TRUE(aSelection && aHeader && aNode, NS_ERROR_NULL_POINTER);
michael@0 6619
michael@0 6620 // remeber where the header is
michael@0 6621 int32_t offset;
michael@0 6622 nsCOMPtr<nsIDOMNode> headerParent = nsEditor::GetNodeLocation(aHeader, &offset);
michael@0 6623
michael@0 6624 // get ws code to adjust any ws
michael@0 6625 nsCOMPtr<nsIDOMNode> selNode = aNode;
michael@0 6626 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6627 nsresult res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor,
michael@0 6628 address_of(selNode),
michael@0 6629 &aOffset);
michael@0 6630 NS_ENSURE_SUCCESS(res, res);
michael@0 6631
michael@0 6632 // split the header
michael@0 6633 int32_t newOffset;
michael@0 6634 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6635 res = mHTMLEditor->SplitNodeDeep( aHeader, selNode, aOffset, &newOffset);
michael@0 6636 NS_ENSURE_SUCCESS(res, res);
michael@0 6637
michael@0 6638 // if the leftand heading is empty, put a mozbr in it
michael@0 6639 nsCOMPtr<nsIDOMNode> prevItem;
michael@0 6640 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6641 mHTMLEditor->GetPriorHTMLSibling(aHeader, address_of(prevItem));
michael@0 6642 if (prevItem && nsHTMLEditUtils::IsHeader(prevItem))
michael@0 6643 {
michael@0 6644 bool bIsEmptyNode;
michael@0 6645 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6646 res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode);
michael@0 6647 NS_ENSURE_SUCCESS(res, res);
michael@0 6648 if (bIsEmptyNode) {
michael@0 6649 res = CreateMozBR(prevItem, 0);
michael@0 6650 NS_ENSURE_SUCCESS(res, res);
michael@0 6651 }
michael@0 6652 }
michael@0 6653
michael@0 6654 // if the new (righthand) header node is empty, delete it
michael@0 6655 bool isEmpty;
michael@0 6656 res = IsEmptyBlock(aHeader, &isEmpty, true);
michael@0 6657 NS_ENSURE_SUCCESS(res, res);
michael@0 6658 if (isEmpty)
michael@0 6659 {
michael@0 6660 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6661 res = mHTMLEditor->DeleteNode(aHeader);
michael@0 6662 NS_ENSURE_SUCCESS(res, res);
michael@0 6663 // layout tells the caret to blink in a weird place
michael@0 6664 // if we don't place a break after the header.
michael@0 6665 nsCOMPtr<nsIDOMNode> sibling;
michael@0 6666 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6667 res = mHTMLEditor->GetNextHTMLSibling(headerParent, offset+1, address_of(sibling));
michael@0 6668 NS_ENSURE_SUCCESS(res, res);
michael@0 6669 if (!sibling || !nsTextEditUtils::IsBreak(sibling))
michael@0 6670 {
michael@0 6671 ClearCachedStyles();
michael@0 6672 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6673 mHTMLEditor->mTypeInState->ClearAllProps();
michael@0 6674
michael@0 6675 // create a paragraph
michael@0 6676 NS_NAMED_LITERAL_STRING(pType, "p");
michael@0 6677 nsCOMPtr<nsIDOMNode> pNode;
michael@0 6678 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6679 res = mHTMLEditor->CreateNode(pType, headerParent, offset+1, getter_AddRefs(pNode));
michael@0 6680 NS_ENSURE_SUCCESS(res, res);
michael@0 6681
michael@0 6682 // append a <br> to it
michael@0 6683 nsCOMPtr<nsIDOMNode> brNode;
michael@0 6684 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6685 res = mHTMLEditor->CreateBR(pNode, 0, address_of(brNode));
michael@0 6686 NS_ENSURE_SUCCESS(res, res);
michael@0 6687
michael@0 6688 // set selection to before the break
michael@0 6689 res = aSelection->Collapse(pNode, 0);
michael@0 6690 }
michael@0 6691 else
michael@0 6692 {
michael@0 6693 headerParent = nsEditor::GetNodeLocation(sibling, &offset);
michael@0 6694 // put selection after break
michael@0 6695 res = aSelection->Collapse(headerParent,offset+1);
michael@0 6696 }
michael@0 6697 }
michael@0 6698 else
michael@0 6699 {
michael@0 6700 // put selection at front of righthand heading
michael@0 6701 res = aSelection->Collapse(aHeader,0);
michael@0 6702 }
michael@0 6703 return res;
michael@0 6704 }
michael@0 6705
michael@0 6706 ///////////////////////////////////////////////////////////////////////////
michael@0 6707 // ReturnInParagraph: do the right thing for returns pressed in paragraphs
michael@0 6708 //
michael@0 6709 nsresult
michael@0 6710 nsHTMLEditRules::ReturnInParagraph(nsISelection* aSelection,
michael@0 6711 nsIDOMNode* aPara,
michael@0 6712 nsIDOMNode* aNode,
michael@0 6713 int32_t aOffset,
michael@0 6714 bool* aCancel,
michael@0 6715 bool* aHandled)
michael@0 6716 {
michael@0 6717 if (!aSelection || !aPara || !aNode || !aCancel || !aHandled) {
michael@0 6718 return NS_ERROR_NULL_POINTER;
michael@0 6719 }
michael@0 6720 *aCancel = false;
michael@0 6721 *aHandled = false;
michael@0 6722 nsresult res;
michael@0 6723
michael@0 6724 int32_t offset;
michael@0 6725 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(aNode, &offset);
michael@0 6726
michael@0 6727 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6728 bool doesCRCreateNewP = mHTMLEditor->GetReturnInParagraphCreatesNewParagraph();
michael@0 6729
michael@0 6730 bool newBRneeded = false;
michael@0 6731 nsCOMPtr<nsIDOMNode> sibling;
michael@0 6732
michael@0 6733 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6734 if (aNode == aPara && doesCRCreateNewP) {
michael@0 6735 // we are at the edges of the block, newBRneeded not needed!
michael@0 6736 sibling = aNode;
michael@0 6737 } else if (mHTMLEditor->IsTextNode(aNode)) {
michael@0 6738 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(aNode);
michael@0 6739 uint32_t strLength;
michael@0 6740 res = textNode->GetLength(&strLength);
michael@0 6741 NS_ENSURE_SUCCESS(res, res);
michael@0 6742
michael@0 6743 // at beginning of text node?
michael@0 6744 if (!aOffset) {
michael@0 6745 // is there a BR prior to it?
michael@0 6746 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6747 mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling));
michael@0 6748 if (!sibling || !mHTMLEditor || !mHTMLEditor->IsVisBreak(sibling) ||
michael@0 6749 nsTextEditUtils::HasMozAttr(sibling)) {
michael@0 6750 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6751 newBRneeded = true;
michael@0 6752 }
michael@0 6753 } else if (aOffset == (int32_t)strLength) {
michael@0 6754 // we're at the end of text node...
michael@0 6755 // is there a BR after to it?
michael@0 6756 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6757 res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling));
michael@0 6758 if (!sibling || !mHTMLEditor || !mHTMLEditor->IsVisBreak(sibling) ||
michael@0 6759 nsTextEditUtils::HasMozAttr(sibling)) {
michael@0 6760 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6761 newBRneeded = true;
michael@0 6762 offset++;
michael@0 6763 }
michael@0 6764 } else {
michael@0 6765 if (doesCRCreateNewP) {
michael@0 6766 nsCOMPtr<nsIDOMNode> tmp;
michael@0 6767 res = mEditor->SplitNode(aNode, aOffset, getter_AddRefs(tmp));
michael@0 6768 NS_ENSURE_SUCCESS(res, res);
michael@0 6769 aNode = tmp;
michael@0 6770 }
michael@0 6771
michael@0 6772 newBRneeded = true;
michael@0 6773 offset++;
michael@0 6774 }
michael@0 6775 } else {
michael@0 6776 // not in a text node.
michael@0 6777 // is there a BR prior to it?
michael@0 6778 nsCOMPtr<nsIDOMNode> nearNode, selNode = aNode;
michael@0 6779 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6780 res = mHTMLEditor->GetPriorHTMLNode(aNode, aOffset, address_of(nearNode));
michael@0 6781 NS_ENSURE_SUCCESS(res, res);
michael@0 6782 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6783 if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) ||
michael@0 6784 nsTextEditUtils::HasMozAttr(nearNode)) {
michael@0 6785 // is there a BR after it?
michael@0 6786 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6787 res = mHTMLEditor->GetNextHTMLNode(aNode, aOffset, address_of(nearNode));
michael@0 6788 NS_ENSURE_SUCCESS(res, res);
michael@0 6789 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6790 if (!nearNode || !mHTMLEditor->IsVisBreak(nearNode) ||
michael@0 6791 nsTextEditUtils::HasMozAttr(nearNode)) {
michael@0 6792 newBRneeded = true;
michael@0 6793 }
michael@0 6794 }
michael@0 6795 if (!newBRneeded) {
michael@0 6796 sibling = nearNode;
michael@0 6797 }
michael@0 6798 }
michael@0 6799 if (newBRneeded) {
michael@0 6800 // if CR does not create a new P, default to BR creation
michael@0 6801 NS_ENSURE_TRUE(doesCRCreateNewP, NS_OK);
michael@0 6802
michael@0 6803 nsCOMPtr<nsIDOMNode> brNode;
michael@0 6804 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6805 res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode));
michael@0 6806 sibling = brNode;
michael@0 6807 }
michael@0 6808 nsCOMPtr<nsIDOMNode> selNode = aNode;
michael@0 6809 *aHandled = true;
michael@0 6810 return SplitParagraph(aPara, sibling, aSelection, address_of(selNode), &aOffset);
michael@0 6811 }
michael@0 6812
michael@0 6813 ///////////////////////////////////////////////////////////////////////////
michael@0 6814 // SplitParagraph: split a paragraph at selection point, possibly deleting a br
michael@0 6815 //
michael@0 6816 nsresult
michael@0 6817 nsHTMLEditRules::SplitParagraph(nsIDOMNode *aPara,
michael@0 6818 nsIDOMNode *aBRNode,
michael@0 6819 nsISelection *aSelection,
michael@0 6820 nsCOMPtr<nsIDOMNode> *aSelNode,
michael@0 6821 int32_t *aOffset)
michael@0 6822 {
michael@0 6823 NS_ENSURE_TRUE(aPara && aBRNode && aSelNode && *aSelNode && aOffset && aSelection, NS_ERROR_NULL_POINTER);
michael@0 6824 nsresult res = NS_OK;
michael@0 6825
michael@0 6826 // split para
michael@0 6827 int32_t newOffset;
michael@0 6828 // get ws code to adjust any ws
michael@0 6829 nsCOMPtr<nsIDOMNode> leftPara, rightPara;
michael@0 6830 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6831 res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, aSelNode, aOffset);
michael@0 6832 NS_ENSURE_SUCCESS(res, res);
michael@0 6833 // split the paragraph
michael@0 6834 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6835 res = mHTMLEditor->SplitNodeDeep(aPara, *aSelNode, *aOffset, &newOffset, false,
michael@0 6836 address_of(leftPara), address_of(rightPara));
michael@0 6837 NS_ENSURE_SUCCESS(res, res);
michael@0 6838 // get rid of the break, if it is visible (otherwise it may be needed to prevent an empty p)
michael@0 6839 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6840 if (mHTMLEditor->IsVisBreak(aBRNode))
michael@0 6841 {
michael@0 6842 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6843 res = mHTMLEditor->DeleteNode(aBRNode);
michael@0 6844 NS_ENSURE_SUCCESS(res, res);
michael@0 6845 }
michael@0 6846
michael@0 6847 // remove ID attribute on the paragraph we just created
michael@0 6848 nsCOMPtr<nsIDOMElement> rightElt = do_QueryInterface(rightPara);
michael@0 6849 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6850 res = mHTMLEditor->RemoveAttribute(rightElt, NS_LITERAL_STRING("id"));
michael@0 6851 NS_ENSURE_SUCCESS(res, res);
michael@0 6852
michael@0 6853 // check both halves of para to see if we need mozBR
michael@0 6854 res = InsertMozBRIfNeeded(leftPara);
michael@0 6855 NS_ENSURE_SUCCESS(res, res);
michael@0 6856 res = InsertMozBRIfNeeded(rightPara);
michael@0 6857 NS_ENSURE_SUCCESS(res, res);
michael@0 6858
michael@0 6859 // selection to beginning of right hand para;
michael@0 6860 // look inside any containers that are up front.
michael@0 6861 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6862 nsCOMPtr<nsIDOMNode> child = mHTMLEditor->GetLeftmostChild(rightPara, true);
michael@0 6863 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6864 if (mHTMLEditor->IsTextNode(child) || !mHTMLEditor ||
michael@0 6865 mHTMLEditor->IsContainer(child))
michael@0 6866 {
michael@0 6867 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6868 aSelection->Collapse(child,0);
michael@0 6869 }
michael@0 6870 else
michael@0 6871 {
michael@0 6872 int32_t offset;
michael@0 6873 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(child, &offset);
michael@0 6874 aSelection->Collapse(parent,offset);
michael@0 6875 }
michael@0 6876 return res;
michael@0 6877 }
michael@0 6878
michael@0 6879
michael@0 6880 ///////////////////////////////////////////////////////////////////////////
michael@0 6881 // ReturnInListItem: do the right thing for returns pressed in list items
michael@0 6882 //
michael@0 6883 nsresult
michael@0 6884 nsHTMLEditRules::ReturnInListItem(nsISelection *aSelection,
michael@0 6885 nsIDOMNode *aListItem,
michael@0 6886 nsIDOMNode *aNode,
michael@0 6887 int32_t aOffset)
michael@0 6888 {
michael@0 6889 NS_ENSURE_TRUE(aSelection && aListItem && aNode, NS_ERROR_NULL_POINTER);
michael@0 6890 nsCOMPtr<nsISelection> selection(aSelection);
michael@0 6891 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
michael@0 6892 nsresult res = NS_OK;
michael@0 6893
michael@0 6894 nsCOMPtr<nsIDOMNode> listitem;
michael@0 6895
michael@0 6896 // sanity check
michael@0 6897 NS_PRECONDITION(true == nsHTMLEditUtils::IsListItem(aListItem),
michael@0 6898 "expected a list item and didn't get one");
michael@0 6899
michael@0 6900 // get the listitem parent and the active editing host.
michael@0 6901 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6902 nsIContent* rootContent = mHTMLEditor->GetActiveEditingHost();
michael@0 6903 nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(rootContent);
michael@0 6904 int32_t itemOffset;
michael@0 6905 nsCOMPtr<nsIDOMNode> list = nsEditor::GetNodeLocation(aListItem, &itemOffset);
michael@0 6906
michael@0 6907 // if we are in an empty listitem, then we want to pop up out of the list
michael@0 6908 // but only if prefs says it's ok and if the parent isn't the active editing host.
michael@0 6909 bool isEmpty;
michael@0 6910 res = IsEmptyBlock(aListItem, &isEmpty, true, false);
michael@0 6911 NS_ENSURE_SUCCESS(res, res);
michael@0 6912 if (isEmpty && (rootNode != list) && mReturnInEmptyLIKillsList)
michael@0 6913 {
michael@0 6914 // get the list offset now -- before we might eventually split the list
michael@0 6915 int32_t offset;
michael@0 6916 nsCOMPtr<nsIDOMNode> listparent = nsEditor::GetNodeLocation(list, &offset);
michael@0 6917
michael@0 6918 // are we the last list item in the list?
michael@0 6919 bool bIsLast;
michael@0 6920 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6921 res = mHTMLEditor->IsLastEditableChild(aListItem, &bIsLast);
michael@0 6922 NS_ENSURE_SUCCESS(res, res);
michael@0 6923 if (!bIsLast)
michael@0 6924 {
michael@0 6925 // we need to split the list!
michael@0 6926 nsCOMPtr<nsIDOMNode> tempNode;
michael@0 6927 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6928 res = mHTMLEditor->SplitNode(list, itemOffset, getter_AddRefs(tempNode));
michael@0 6929 NS_ENSURE_SUCCESS(res, res);
michael@0 6930 }
michael@0 6931
michael@0 6932 // are we in a sublist?
michael@0 6933 if (nsHTMLEditUtils::IsList(listparent)) //in a sublist
michael@0 6934 {
michael@0 6935 // if so, move this list item out of this list and into the grandparent list
michael@0 6936 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6937 res = mHTMLEditor->MoveNode(aListItem,listparent,offset+1);
michael@0 6938 NS_ENSURE_SUCCESS(res, res);
michael@0 6939 res = aSelection->Collapse(aListItem,0);
michael@0 6940 }
michael@0 6941 else
michael@0 6942 {
michael@0 6943 // otherwise kill this listitem
michael@0 6944 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6945 res = mHTMLEditor->DeleteNode(aListItem);
michael@0 6946 NS_ENSURE_SUCCESS(res, res);
michael@0 6947
michael@0 6948 // time to insert a paragraph
michael@0 6949 NS_NAMED_LITERAL_STRING(pType, "p");
michael@0 6950 nsCOMPtr<nsIDOMNode> pNode;
michael@0 6951 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6952 res = mHTMLEditor->CreateNode(pType, listparent, offset+1, getter_AddRefs(pNode));
michael@0 6953 NS_ENSURE_SUCCESS(res, res);
michael@0 6954
michael@0 6955 // append a <br> to it
michael@0 6956 nsCOMPtr<nsIDOMNode> brNode;
michael@0 6957 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6958 res = mHTMLEditor->CreateBR(pNode, 0, address_of(brNode));
michael@0 6959 NS_ENSURE_SUCCESS(res, res);
michael@0 6960
michael@0 6961 // set selection to before the break
michael@0 6962 res = aSelection->Collapse(pNode, 0);
michael@0 6963 }
michael@0 6964 return res;
michael@0 6965 }
michael@0 6966
michael@0 6967 // else we want a new list item at the same list level.
michael@0 6968 // get ws code to adjust any ws
michael@0 6969 nsCOMPtr<nsIDOMNode> selNode = aNode;
michael@0 6970 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6971 res = nsWSRunObject::PrepareToSplitAcrossBlocks(mHTMLEditor, address_of(selNode), &aOffset);
michael@0 6972 NS_ENSURE_SUCCESS(res, res);
michael@0 6973 // now split list item
michael@0 6974 int32_t newOffset;
michael@0 6975 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6976 res = mHTMLEditor->SplitNodeDeep( aListItem, selNode, aOffset, &newOffset, false);
michael@0 6977 NS_ENSURE_SUCCESS(res, res);
michael@0 6978 // hack: until I can change the damaged doc range code back to being
michael@0 6979 // extra inclusive, I have to manually detect certain list items that
michael@0 6980 // may be left empty.
michael@0 6981 nsCOMPtr<nsIDOMNode> prevItem;
michael@0 6982 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6983 mHTMLEditor->GetPriorHTMLSibling(aListItem, address_of(prevItem));
michael@0 6984
michael@0 6985 if (prevItem && nsHTMLEditUtils::IsListItem(prevItem))
michael@0 6986 {
michael@0 6987 bool bIsEmptyNode;
michael@0 6988 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6989 res = mHTMLEditor->IsEmptyNode(prevItem, &bIsEmptyNode);
michael@0 6990 NS_ENSURE_SUCCESS(res, res);
michael@0 6991 if (bIsEmptyNode) {
michael@0 6992 res = CreateMozBR(prevItem, 0);
michael@0 6993 NS_ENSURE_SUCCESS(res, res);
michael@0 6994 } else {
michael@0 6995 NS_ENSURE_STATE(mHTMLEditor);
michael@0 6996 res = mHTMLEditor->IsEmptyNode(aListItem, &bIsEmptyNode, true);
michael@0 6997 NS_ENSURE_SUCCESS(res, res);
michael@0 6998 if (bIsEmptyNode)
michael@0 6999 {
michael@0 7000 nsCOMPtr<nsIAtom> nodeAtom = nsEditor::GetTag(aListItem);
michael@0 7001 if (nodeAtom == nsEditProperty::dd || nodeAtom == nsEditProperty::dt)
michael@0 7002 {
michael@0 7003 int32_t itemOffset;
michael@0 7004 nsCOMPtr<nsIDOMNode> list = nsEditor::GetNodeLocation(aListItem, &itemOffset);
michael@0 7005
michael@0 7006 nsAutoString listTag((nodeAtom == nsEditProperty::dt) ? NS_LITERAL_STRING("dd") : NS_LITERAL_STRING("dt"));
michael@0 7007 nsCOMPtr<nsIDOMNode> newListItem;
michael@0 7008 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7009 res = mHTMLEditor->CreateNode(listTag, list, itemOffset+1, getter_AddRefs(newListItem));
michael@0 7010 NS_ENSURE_SUCCESS(res, res);
michael@0 7011 res = mEditor->DeleteNode(aListItem);
michael@0 7012 NS_ENSURE_SUCCESS(res, res);
michael@0 7013 return aSelection->Collapse(newListItem, 0);
michael@0 7014 }
michael@0 7015
michael@0 7016 nsCOMPtr<nsIDOMNode> brNode;
michael@0 7017 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7018 res = mHTMLEditor->CopyLastEditableChildStyles(prevItem, aListItem, getter_AddRefs(brNode));
michael@0 7019 NS_ENSURE_SUCCESS(res, res);
michael@0 7020 if (brNode)
michael@0 7021 {
michael@0 7022 int32_t offset;
michael@0 7023 nsCOMPtr<nsIDOMNode> brParent = nsEditor::GetNodeLocation(brNode, &offset);
michael@0 7024 return aSelection->Collapse(brParent, offset);
michael@0 7025 }
michael@0 7026 }
michael@0 7027 else
michael@0 7028 {
michael@0 7029 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7030 nsWSRunObject wsObj(mHTMLEditor, aListItem, 0);
michael@0 7031 nsCOMPtr<nsIDOMNode> visNode;
michael@0 7032 int32_t visOffset = 0;
michael@0 7033 WSType wsType;
michael@0 7034 wsObj.NextVisibleNode(aListItem, 0, address_of(visNode),
michael@0 7035 &visOffset, &wsType);
michael@0 7036 if (wsType == WSType::special || wsType == WSType::br ||
michael@0 7037 nsHTMLEditUtils::IsHR(visNode)) {
michael@0 7038 int32_t offset;
michael@0 7039 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(visNode, &offset);
michael@0 7040 return aSelection->Collapse(parent, offset);
michael@0 7041 }
michael@0 7042 else
michael@0 7043 {
michael@0 7044 return aSelection->Collapse(visNode, visOffset);
michael@0 7045 }
michael@0 7046 }
michael@0 7047 }
michael@0 7048 }
michael@0 7049 res = aSelection->Collapse(aListItem,0);
michael@0 7050 return res;
michael@0 7051 }
michael@0 7052
michael@0 7053
michael@0 7054 ///////////////////////////////////////////////////////////////////////////
michael@0 7055 // MakeBlockquote: put the list of nodes into one or more blockquotes.
michael@0 7056 //
michael@0 7057 nsresult
michael@0 7058 nsHTMLEditRules::MakeBlockquote(nsCOMArray<nsIDOMNode>& arrayOfNodes)
michael@0 7059 {
michael@0 7060 // the idea here is to put the nodes into a minimal number of
michael@0 7061 // blockquotes. When the user blockquotes something, they expect
michael@0 7062 // one blockquote. That may not be possible (for instance, if they
michael@0 7063 // have two table cells selected, you need two blockquotes inside the cells).
michael@0 7064
michael@0 7065 nsresult res = NS_OK;
michael@0 7066
michael@0 7067 nsCOMPtr<nsIDOMNode> curNode, curParent, curBlock, newBlock;
michael@0 7068 int32_t offset;
michael@0 7069 int32_t listCount = arrayOfNodes.Count();
michael@0 7070
michael@0 7071 nsCOMPtr<nsIDOMNode> prevParent;
michael@0 7072
michael@0 7073 int32_t i;
michael@0 7074 for (i=0; i<listCount; i++)
michael@0 7075 {
michael@0 7076 // get the node to act on, and its location
michael@0 7077 curNode = arrayOfNodes[i];
michael@0 7078 curParent = nsEditor::GetNodeLocation(curNode, &offset);
michael@0 7079
michael@0 7080 // if the node is a table element or list item, dive inside
michael@0 7081 if (nsHTMLEditUtils::IsTableElementButNotTable(curNode) ||
michael@0 7082 nsHTMLEditUtils::IsListItem(curNode))
michael@0 7083 {
michael@0 7084 curBlock = 0; // forget any previous block
michael@0 7085 // recursion time
michael@0 7086 nsCOMArray<nsIDOMNode> childArray;
michael@0 7087 res = GetChildNodesForOperation(curNode, childArray);
michael@0 7088 NS_ENSURE_SUCCESS(res, res);
michael@0 7089 res = MakeBlockquote(childArray);
michael@0 7090 NS_ENSURE_SUCCESS(res, res);
michael@0 7091 }
michael@0 7092
michael@0 7093 // if the node has different parent than previous node,
michael@0 7094 // further nodes in a new parent
michael@0 7095 if (prevParent)
michael@0 7096 {
michael@0 7097 nsCOMPtr<nsIDOMNode> temp;
michael@0 7098 curNode->GetParentNode(getter_AddRefs(temp));
michael@0 7099 if (temp != prevParent)
michael@0 7100 {
michael@0 7101 curBlock = 0; // forget any previous blockquote node we were using
michael@0 7102 prevParent = temp;
michael@0 7103 }
michael@0 7104 }
michael@0 7105 else
michael@0 7106
michael@0 7107 {
michael@0 7108 curNode->GetParentNode(getter_AddRefs(prevParent));
michael@0 7109 }
michael@0 7110
michael@0 7111 // if no curBlock, make one
michael@0 7112 if (!curBlock)
michael@0 7113 {
michael@0 7114 NS_NAMED_LITERAL_STRING(quoteType, "blockquote");
michael@0 7115 res = SplitAsNeeded(&quoteType, address_of(curParent), &offset);
michael@0 7116 NS_ENSURE_SUCCESS(res, res);
michael@0 7117 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7118 res = mHTMLEditor->CreateNode(quoteType, curParent, offset, getter_AddRefs(curBlock));
michael@0 7119 NS_ENSURE_SUCCESS(res, res);
michael@0 7120 // remember our new block for postprocessing
michael@0 7121 mNewBlock = curBlock;
michael@0 7122 // note: doesn't matter if we set mNewBlock multiple times.
michael@0 7123 }
michael@0 7124
michael@0 7125 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7126 res = mHTMLEditor->MoveNode(curNode, curBlock, -1);
michael@0 7127 NS_ENSURE_SUCCESS(res, res);
michael@0 7128 }
michael@0 7129 return res;
michael@0 7130 }
michael@0 7131
michael@0 7132
michael@0 7133
michael@0 7134 ///////////////////////////////////////////////////////////////////////////
michael@0 7135 // RemoveBlockStyle: make the nodes have no special block type.
michael@0 7136 //
michael@0 7137 nsresult
michael@0 7138 nsHTMLEditRules::RemoveBlockStyle(nsCOMArray<nsIDOMNode>& arrayOfNodes)
michael@0 7139 {
michael@0 7140 // intent of this routine is to be used for converting to/from
michael@0 7141 // headers, paragraphs, pre, and address. Those blocks
michael@0 7142 // that pretty much just contain inline things...
michael@0 7143
michael@0 7144 nsresult res = NS_OK;
michael@0 7145
michael@0 7146 nsCOMPtr<nsIDOMNode> curBlock, firstNode, lastNode;
michael@0 7147 int32_t listCount = arrayOfNodes.Count();
michael@0 7148 for (int32_t i = 0; i < listCount; ++i) {
michael@0 7149 // get the node to act on, and its location
michael@0 7150 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
michael@0 7151
michael@0 7152 nsCOMPtr<dom::Element> curElement = do_QueryInterface(curNode);
michael@0 7153
michael@0 7154 // if curNode is a address, p, header, address, or pre, remove it
michael@0 7155 if (curElement && nsHTMLEditUtils::IsFormatNode(curElement)) {
michael@0 7156 // process any partial progress saved
michael@0 7157 if (curBlock)
michael@0 7158 {
michael@0 7159 res = RemovePartOfBlock(curBlock, firstNode, lastNode);
michael@0 7160 NS_ENSURE_SUCCESS(res, res);
michael@0 7161 curBlock = 0; firstNode = 0; lastNode = 0;
michael@0 7162 }
michael@0 7163 // remove curent block
michael@0 7164 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7165 res = mHTMLEditor->RemoveBlockContainer(curNode);
michael@0 7166 NS_ENSURE_SUCCESS(res, res);
michael@0 7167 } else if (curElement &&
michael@0 7168 (curElement->IsHTML(nsGkAtoms::table) ||
michael@0 7169 curElement->IsHTML(nsGkAtoms::tr) ||
michael@0 7170 curElement->IsHTML(nsGkAtoms::tbody) ||
michael@0 7171 curElement->IsHTML(nsGkAtoms::td) ||
michael@0 7172 nsHTMLEditUtils::IsList(curElement) ||
michael@0 7173 curElement->IsHTML(nsGkAtoms::li) ||
michael@0 7174 curElement->IsHTML(nsGkAtoms::blockquote) ||
michael@0 7175 curElement->IsHTML(nsGkAtoms::div))) {
michael@0 7176 // process any partial progress saved
michael@0 7177 if (curBlock)
michael@0 7178 {
michael@0 7179 res = RemovePartOfBlock(curBlock, firstNode, lastNode);
michael@0 7180 NS_ENSURE_SUCCESS(res, res);
michael@0 7181 curBlock = 0; firstNode = 0; lastNode = 0;
michael@0 7182 }
michael@0 7183 // recursion time
michael@0 7184 nsCOMArray<nsIDOMNode> childArray;
michael@0 7185 res = GetChildNodesForOperation(curNode, childArray);
michael@0 7186 NS_ENSURE_SUCCESS(res, res);
michael@0 7187 res = RemoveBlockStyle(childArray);
michael@0 7188 NS_ENSURE_SUCCESS(res, res);
michael@0 7189 }
michael@0 7190 else if (IsInlineNode(curNode))
michael@0 7191 {
michael@0 7192 if (curBlock)
michael@0 7193 {
michael@0 7194 // if so, is this node a descendant?
michael@0 7195 if (nsEditorUtils::IsDescendantOf(curNode, curBlock))
michael@0 7196 {
michael@0 7197 lastNode = curNode;
michael@0 7198 continue; // then we don't need to do anything different for this node
michael@0 7199 }
michael@0 7200 else
michael@0 7201 {
michael@0 7202 // otherwise, we have progressed beyond end of curBlock,
michael@0 7203 // so lets handle it now. We need to remove the portion of
michael@0 7204 // curBlock that contains [firstNode - lastNode].
michael@0 7205 res = RemovePartOfBlock(curBlock, firstNode, lastNode);
michael@0 7206 NS_ENSURE_SUCCESS(res, res);
michael@0 7207 curBlock = 0; firstNode = 0; lastNode = 0;
michael@0 7208 // fall out and handle curNode
michael@0 7209 }
michael@0 7210 }
michael@0 7211 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7212 curBlock = mHTMLEditor->GetBlockNodeParent(curNode);
michael@0 7213 if (nsHTMLEditUtils::IsFormatNode(curBlock))
michael@0 7214 {
michael@0 7215 firstNode = curNode;
michael@0 7216 lastNode = curNode;
michael@0 7217 }
michael@0 7218 else
michael@0 7219 curBlock = 0; // not a block kind that we care about.
michael@0 7220 }
michael@0 7221 else
michael@0 7222 { // some node that is already sans block style. skip over it and
michael@0 7223 // process any partial progress saved
michael@0 7224 if (curBlock)
michael@0 7225 {
michael@0 7226 res = RemovePartOfBlock(curBlock, firstNode, lastNode);
michael@0 7227 NS_ENSURE_SUCCESS(res, res);
michael@0 7228 curBlock = 0; firstNode = 0; lastNode = 0;
michael@0 7229 }
michael@0 7230 }
michael@0 7231 }
michael@0 7232 // process any partial progress saved
michael@0 7233 if (curBlock)
michael@0 7234 {
michael@0 7235 res = RemovePartOfBlock(curBlock, firstNode, lastNode);
michael@0 7236 NS_ENSURE_SUCCESS(res, res);
michael@0 7237 curBlock = 0; firstNode = 0; lastNode = 0;
michael@0 7238 }
michael@0 7239 return res;
michael@0 7240 }
michael@0 7241
michael@0 7242
michael@0 7243 ///////////////////////////////////////////////////////////////////////////
michael@0 7244 // ApplyBlockStyle: do whatever it takes to make the list of nodes into
michael@0 7245 // one or more blocks of type blockTag.
michael@0 7246 //
michael@0 7247 nsresult
michael@0 7248 nsHTMLEditRules::ApplyBlockStyle(nsCOMArray<nsIDOMNode>& arrayOfNodes, const nsAString *aBlockTag)
michael@0 7249 {
michael@0 7250 // intent of this routine is to be used for converting to/from
michael@0 7251 // headers, paragraphs, pre, and address. Those blocks
michael@0 7252 // that pretty much just contain inline things...
michael@0 7253
michael@0 7254 NS_ENSURE_TRUE(aBlockTag, NS_ERROR_NULL_POINTER);
michael@0 7255 nsresult res = NS_OK;
michael@0 7256
michael@0 7257 nsCOMPtr<nsIDOMNode> curNode, curParent, curBlock, newBlock;
michael@0 7258 int32_t offset;
michael@0 7259 int32_t listCount = arrayOfNodes.Count();
michael@0 7260 nsString tString(*aBlockTag);////MJUDGE SCC NEED HELP
michael@0 7261
michael@0 7262 // Remove all non-editable nodes. Leave them be.
michael@0 7263 int32_t j;
michael@0 7264 for (j=listCount-1; j>=0; j--)
michael@0 7265 {
michael@0 7266 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7267 if (!mHTMLEditor->IsEditable(arrayOfNodes[j]))
michael@0 7268 {
michael@0 7269 arrayOfNodes.RemoveObjectAt(j);
michael@0 7270 }
michael@0 7271 }
michael@0 7272
michael@0 7273 // reset list count
michael@0 7274 listCount = arrayOfNodes.Count();
michael@0 7275
michael@0 7276 int32_t i;
michael@0 7277 for (i=0; i<listCount; i++)
michael@0 7278 {
michael@0 7279 // get the node to act on, and its location
michael@0 7280 curNode = arrayOfNodes[i];
michael@0 7281 curParent = nsEditor::GetNodeLocation(curNode, &offset);
michael@0 7282 nsAutoString curNodeTag;
michael@0 7283 nsEditor::GetTagString(curNode, curNodeTag);
michael@0 7284 ToLowerCase(curNodeTag);
michael@0 7285
michael@0 7286 // is it already the right kind of block?
michael@0 7287 if (curNodeTag == *aBlockTag)
michael@0 7288 {
michael@0 7289 curBlock = 0; // forget any previous block used for previous inline nodes
michael@0 7290 continue; // do nothing to this block
michael@0 7291 }
michael@0 7292
michael@0 7293 // if curNode is a address, p, header, address, or pre, replace
michael@0 7294 // it with a new block of correct type.
michael@0 7295 // xxx floppy moose: pre can't hold everything the others can
michael@0 7296 if (nsHTMLEditUtils::IsMozDiv(curNode) ||
michael@0 7297 nsHTMLEditUtils::IsFormatNode(curNode))
michael@0 7298 {
michael@0 7299 curBlock = 0; // forget any previous block used for previous inline nodes
michael@0 7300 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7301 res = mHTMLEditor->ReplaceContainer(curNode, address_of(newBlock), *aBlockTag,
michael@0 7302 nullptr, nullptr, true);
michael@0 7303 NS_ENSURE_SUCCESS(res, res);
michael@0 7304 }
michael@0 7305 else if (nsHTMLEditUtils::IsTable(curNode) ||
michael@0 7306 (curNodeTag.EqualsLiteral("tbody")) ||
michael@0 7307 (curNodeTag.EqualsLiteral("tr")) ||
michael@0 7308 (curNodeTag.EqualsLiteral("td")) ||
michael@0 7309 nsHTMLEditUtils::IsList(curNode) ||
michael@0 7310 (curNodeTag.EqualsLiteral("li")) ||
michael@0 7311 nsHTMLEditUtils::IsBlockquote(curNode) ||
michael@0 7312 nsHTMLEditUtils::IsDiv(curNode))
michael@0 7313 {
michael@0 7314 curBlock = 0; // forget any previous block used for previous inline nodes
michael@0 7315 // recursion time
michael@0 7316 nsCOMArray<nsIDOMNode> childArray;
michael@0 7317 res = GetChildNodesForOperation(curNode, childArray);
michael@0 7318 NS_ENSURE_SUCCESS(res, res);
michael@0 7319 int32_t childCount = childArray.Count();
michael@0 7320 if (childCount)
michael@0 7321 {
michael@0 7322 res = ApplyBlockStyle(childArray, aBlockTag);
michael@0 7323 NS_ENSURE_SUCCESS(res, res);
michael@0 7324 }
michael@0 7325 else
michael@0 7326 {
michael@0 7327 // make sure we can put a block here
michael@0 7328 res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset);
michael@0 7329 NS_ENSURE_SUCCESS(res, res);
michael@0 7330 nsCOMPtr<nsIDOMNode> theBlock;
michael@0 7331 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7332 res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(theBlock));
michael@0 7333 NS_ENSURE_SUCCESS(res, res);
michael@0 7334 // remember our new block for postprocessing
michael@0 7335 mNewBlock = theBlock;
michael@0 7336 }
michael@0 7337 }
michael@0 7338
michael@0 7339 // if the node is a break, we honor it by putting further nodes in a new parent
michael@0 7340 else if (curNodeTag.EqualsLiteral("br"))
michael@0 7341 {
michael@0 7342 if (curBlock)
michael@0 7343 {
michael@0 7344 curBlock = 0; // forget any previous block used for previous inline nodes
michael@0 7345 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7346 res = mHTMLEditor->DeleteNode(curNode);
michael@0 7347 NS_ENSURE_SUCCESS(res, res);
michael@0 7348 }
michael@0 7349 else
michael@0 7350 {
michael@0 7351 // the break is the first (or even only) node we encountered. Create a
michael@0 7352 // block for it.
michael@0 7353 res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset);
michael@0 7354 NS_ENSURE_SUCCESS(res, res);
michael@0 7355 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7356 res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock));
michael@0 7357 NS_ENSURE_SUCCESS(res, res);
michael@0 7358 // remember our new block for postprocessing
michael@0 7359 mNewBlock = curBlock;
michael@0 7360 // note: doesn't matter if we set mNewBlock multiple times.
michael@0 7361 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7362 res = mHTMLEditor->MoveNode(curNode, curBlock, -1);
michael@0 7363 NS_ENSURE_SUCCESS(res, res);
michael@0 7364 }
michael@0 7365 }
michael@0 7366
michael@0 7367
michael@0 7368 // if curNode is inline, pull it into curBlock
michael@0 7369 // note: it's assumed that consecutive inline nodes in the
michael@0 7370 // arrayOfNodes are actually members of the same block parent.
michael@0 7371 // this happens to be true now as a side effect of how
michael@0 7372 // arrayOfNodes is contructed, but some additional logic should
michael@0 7373 // be added here if that should change
michael@0 7374
michael@0 7375 else if (IsInlineNode(curNode))
michael@0 7376 {
michael@0 7377 // if curNode is a non editable, drop it if we are going to <pre>
michael@0 7378 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7379 if (tString.LowerCaseEqualsLiteral("pre")
michael@0 7380 && (!mHTMLEditor->IsEditable(curNode)))
michael@0 7381 continue; // do nothing to this block
michael@0 7382
michael@0 7383 // if no curBlock, make one
michael@0 7384 if (!curBlock)
michael@0 7385 {
michael@0 7386 res = SplitAsNeeded(aBlockTag, address_of(curParent), &offset);
michael@0 7387 NS_ENSURE_SUCCESS(res, res);
michael@0 7388 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7389 res = mHTMLEditor->CreateNode(*aBlockTag, curParent, offset, getter_AddRefs(curBlock));
michael@0 7390 NS_ENSURE_SUCCESS(res, res);
michael@0 7391 // remember our new block for postprocessing
michael@0 7392 mNewBlock = curBlock;
michael@0 7393 // note: doesn't matter if we set mNewBlock multiple times.
michael@0 7394 }
michael@0 7395
michael@0 7396 // if curNode is a Break, replace it with a return if we are going to <pre>
michael@0 7397 // xxx floppy moose
michael@0 7398
michael@0 7399 // this is a continuation of some inline nodes that belong together in
michael@0 7400 // the same block item. use curBlock
michael@0 7401 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7402 res = mHTMLEditor->MoveNode(curNode, curBlock, -1);
michael@0 7403 NS_ENSURE_SUCCESS(res, res);
michael@0 7404 }
michael@0 7405 }
michael@0 7406 return res;
michael@0 7407 }
michael@0 7408
michael@0 7409
michael@0 7410 ///////////////////////////////////////////////////////////////////////////
michael@0 7411 // SplitAsNeeded: given a tag name, split inOutParent up to the point
michael@0 7412 // where we can insert the tag. Adjust inOutParent and
michael@0 7413 // inOutOffset to pint to new location for tag.
michael@0 7414 nsresult
michael@0 7415 nsHTMLEditRules::SplitAsNeeded(const nsAString *aTag,
michael@0 7416 nsCOMPtr<nsIDOMNode> *inOutParent,
michael@0 7417 int32_t *inOutOffset)
michael@0 7418 {
michael@0 7419 NS_ENSURE_TRUE(aTag && inOutParent && inOutOffset, NS_ERROR_NULL_POINTER);
michael@0 7420 NS_ENSURE_TRUE(*inOutParent, NS_ERROR_NULL_POINTER);
michael@0 7421 nsCOMPtr<nsIDOMNode> tagParent, temp, splitNode, parent = *inOutParent;
michael@0 7422 nsresult res = NS_OK;
michael@0 7423 nsCOMPtr<nsIAtom> tagAtom = do_GetAtom(*aTag);
michael@0 7424
michael@0 7425 // check that we have a place that can legally contain the tag
michael@0 7426 while (!tagParent)
michael@0 7427 {
michael@0 7428 // sniffing up the parent tree until we find
michael@0 7429 // a legal place for the block
michael@0 7430 if (!parent) break;
michael@0 7431 // Don't leave the active editing host
michael@0 7432 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7433 if (!mHTMLEditor->IsDescendantOfEditorRoot(parent)) {
michael@0 7434 nsCOMPtr<nsIContent> parentContent = do_QueryInterface(parent);
michael@0 7435 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7436 if (parentContent != mHTMLEditor->GetActiveEditingHost()) {
michael@0 7437 break;
michael@0 7438 }
michael@0 7439 }
michael@0 7440 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7441 if (mHTMLEditor->CanContainTag(parent, tagAtom)) {
michael@0 7442 tagParent = parent;
michael@0 7443 break;
michael@0 7444 }
michael@0 7445 splitNode = parent;
michael@0 7446 parent->GetParentNode(getter_AddRefs(temp));
michael@0 7447 parent = temp;
michael@0 7448 }
michael@0 7449 if (!tagParent)
michael@0 7450 {
michael@0 7451 // could not find a place to build tag!
michael@0 7452 return NS_ERROR_FAILURE;
michael@0 7453 }
michael@0 7454 if (splitNode)
michael@0 7455 {
michael@0 7456 // we found a place for block, but above inOutParent. We need to split nodes.
michael@0 7457 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7458 res = mHTMLEditor->SplitNodeDeep(splitNode, *inOutParent, *inOutOffset, inOutOffset);
michael@0 7459 NS_ENSURE_SUCCESS(res, res);
michael@0 7460 *inOutParent = tagParent;
michael@0 7461 }
michael@0 7462 return res;
michael@0 7463 }
michael@0 7464
michael@0 7465 ///////////////////////////////////////////////////////////////////////////
michael@0 7466 // JoinNodesSmart: join two nodes, doing whatever makes sense for their
michael@0 7467 // children (which often means joining them, too).
michael@0 7468 // aNodeLeft & aNodeRight must be same type of node.
michael@0 7469 nsresult
michael@0 7470 nsHTMLEditRules::JoinNodesSmart( nsIDOMNode *aNodeLeft,
michael@0 7471 nsIDOMNode *aNodeRight,
michael@0 7472 nsCOMPtr<nsIDOMNode> *aOutMergeParent,
michael@0 7473 int32_t *aOutMergeOffset)
michael@0 7474 {
michael@0 7475 // check parms
michael@0 7476 NS_ENSURE_TRUE(aNodeLeft &&
michael@0 7477 aNodeRight &&
michael@0 7478 aOutMergeParent &&
michael@0 7479 aOutMergeOffset, NS_ERROR_NULL_POINTER);
michael@0 7480
michael@0 7481 nsresult res = NS_OK;
michael@0 7482 // caller responsible for:
michael@0 7483 // left & right node are same type
michael@0 7484 int32_t parOffset;
michael@0 7485 nsCOMPtr<nsIDOMNode> rightParent;
michael@0 7486 nsCOMPtr<nsIDOMNode> parent = nsEditor::GetNodeLocation(aNodeLeft, &parOffset);
michael@0 7487 aNodeRight->GetParentNode(getter_AddRefs(rightParent));
michael@0 7488
michael@0 7489 // if they don't have the same parent, first move the 'right' node
michael@0 7490 // to after the 'left' one
michael@0 7491 if (parent != rightParent)
michael@0 7492 {
michael@0 7493 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7494 res = mHTMLEditor->MoveNode(aNodeRight, parent, parOffset);
michael@0 7495 NS_ENSURE_SUCCESS(res, res);
michael@0 7496 }
michael@0 7497
michael@0 7498 // defaults for outParams
michael@0 7499 *aOutMergeParent = aNodeRight;
michael@0 7500 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7501 res = mHTMLEditor->GetLengthOfDOMNode(aNodeLeft, *((uint32_t*)aOutMergeOffset));
michael@0 7502 NS_ENSURE_SUCCESS(res, res);
michael@0 7503
michael@0 7504 // separate join rules for differing blocks
michael@0 7505 if (nsHTMLEditUtils::IsList(aNodeLeft) ||
michael@0 7506 !mHTMLEditor ||
michael@0 7507 mHTMLEditor->IsTextNode(aNodeLeft))
michael@0 7508 {
michael@0 7509 // for list's, merge shallow (wouldn't want to combine list items)
michael@0 7510 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7511 res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight, parent);
michael@0 7512 NS_ENSURE_SUCCESS(res, res);
michael@0 7513 return res;
michael@0 7514 }
michael@0 7515 else
michael@0 7516 {
michael@0 7517 // remember the last left child, and firt right child
michael@0 7518 nsCOMPtr<nsIDOMNode> lastLeft, firstRight;
michael@0 7519 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7520 res = mHTMLEditor->GetLastEditableChild(aNodeLeft, address_of(lastLeft));
michael@0 7521 NS_ENSURE_SUCCESS(res, res);
michael@0 7522 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7523 res = mHTMLEditor->GetFirstEditableChild(aNodeRight, address_of(firstRight));
michael@0 7524 NS_ENSURE_SUCCESS(res, res);
michael@0 7525
michael@0 7526 // for list items, divs, etc, merge smart
michael@0 7527 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7528 res = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight, parent);
michael@0 7529 NS_ENSURE_SUCCESS(res, res);
michael@0 7530
michael@0 7531 if (lastLeft && firstRight && mHTMLEditor &&
michael@0 7532 mHTMLEditor->NodesSameType(lastLeft, firstRight) &&
michael@0 7533 (nsEditor::IsTextNode(lastLeft) ||
michael@0 7534 !mHTMLEditor ||
michael@0 7535 mHTMLEditor->mHTMLCSSUtils->ElementsSameStyle(lastLeft, firstRight))) {
michael@0 7536 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7537 return JoinNodesSmart(lastLeft, firstRight, aOutMergeParent, aOutMergeOffset);
michael@0 7538 }
michael@0 7539 }
michael@0 7540 return res;
michael@0 7541 }
michael@0 7542
michael@0 7543
michael@0 7544 nsresult
michael@0 7545 nsHTMLEditRules::GetTopEnclosingMailCite(nsIDOMNode *aNode,
michael@0 7546 nsCOMPtr<nsIDOMNode> *aOutCiteNode,
michael@0 7547 bool aPlainText)
michael@0 7548 {
michael@0 7549 // check parms
michael@0 7550 NS_ENSURE_TRUE(aNode && aOutCiteNode, NS_ERROR_NULL_POINTER);
michael@0 7551
michael@0 7552 nsresult res = NS_OK;
michael@0 7553 nsCOMPtr<nsIDOMNode> node, parentNode;
michael@0 7554 node = do_QueryInterface(aNode);
michael@0 7555
michael@0 7556 while (node)
michael@0 7557 {
michael@0 7558 if ( (aPlainText && nsHTMLEditUtils::IsPre(node)) ||
michael@0 7559 nsHTMLEditUtils::IsMailCite(node) )
michael@0 7560 *aOutCiteNode = node;
michael@0 7561 if (nsTextEditUtils::IsBody(node)) break;
michael@0 7562
michael@0 7563 res = node->GetParentNode(getter_AddRefs(parentNode));
michael@0 7564 NS_ENSURE_SUCCESS(res, res);
michael@0 7565 node = parentNode;
michael@0 7566 }
michael@0 7567
michael@0 7568 return res;
michael@0 7569 }
michael@0 7570
michael@0 7571
michael@0 7572 nsresult
michael@0 7573 nsHTMLEditRules::CacheInlineStyles(nsIDOMNode *aNode)
michael@0 7574 {
michael@0 7575 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
michael@0 7576
michael@0 7577 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7578 bool useCSS = mHTMLEditor->IsCSSEnabled();
michael@0 7579
michael@0 7580 for (int32_t j = 0; j < SIZE_STYLE_TABLE; ++j)
michael@0 7581 {
michael@0 7582 bool isSet = false;
michael@0 7583 nsAutoString outValue;
michael@0 7584 // Don't use CSS for <font size>, we don't support it usefully (bug 780035)
michael@0 7585 if (!useCSS || (mCachedStyles[j].tag == nsGkAtoms::font &&
michael@0 7586 mCachedStyles[j].attr.EqualsLiteral("size"))) {
michael@0 7587 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7588 mHTMLEditor->IsTextPropertySetByContent(aNode, mCachedStyles[j].tag,
michael@0 7589 &(mCachedStyles[j].attr), nullptr,
michael@0 7590 isSet, &outValue);
michael@0 7591 }
michael@0 7592 else
michael@0 7593 {
michael@0 7594 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7595 mHTMLEditor->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(aNode,
michael@0 7596 mCachedStyles[j].tag, &(mCachedStyles[j].attr), isSet, outValue,
michael@0 7597 nsHTMLCSSUtils::eComputed);
michael@0 7598 }
michael@0 7599 if (isSet)
michael@0 7600 {
michael@0 7601 mCachedStyles[j].mPresent = true;
michael@0 7602 mCachedStyles[j].value.Assign(outValue);
michael@0 7603 }
michael@0 7604 }
michael@0 7605 return NS_OK;
michael@0 7606 }
michael@0 7607
michael@0 7608
michael@0 7609 nsresult
michael@0 7610 nsHTMLEditRules::ReapplyCachedStyles()
michael@0 7611 {
michael@0 7612 // The idea here is to examine our cached list of styles and see if any have
michael@0 7613 // been removed. If so, add typeinstate for them, so that they will be
michael@0 7614 // reinserted when new content is added.
michael@0 7615
michael@0 7616 // remember if we are in css mode
michael@0 7617 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7618 bool useCSS = mHTMLEditor->IsCSSEnabled();
michael@0 7619
michael@0 7620 // get selection point; if it doesn't exist, we have nothing to do
michael@0 7621 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7622 nsRefPtr<Selection> selection = mHTMLEditor->GetSelection();
michael@0 7623 MOZ_ASSERT(selection);
michael@0 7624 if (!selection->GetRangeCount()) {
michael@0 7625 // Nothing to do
michael@0 7626 return NS_OK;
michael@0 7627 }
michael@0 7628 nsCOMPtr<nsIContent> selNode =
michael@0 7629 do_QueryInterface(selection->GetRangeAt(0)->GetStartParent());
michael@0 7630 if (!selNode) {
michael@0 7631 // Nothing to do
michael@0 7632 return NS_OK;
michael@0 7633 }
michael@0 7634
michael@0 7635 for (int32_t i = 0; i < SIZE_STYLE_TABLE; ++i) {
michael@0 7636 if (mCachedStyles[i].mPresent) {
michael@0 7637 bool bFirst, bAny, bAll;
michael@0 7638 bFirst = bAny = bAll = false;
michael@0 7639
michael@0 7640 nsAutoString curValue;
michael@0 7641 if (useCSS) {
michael@0 7642 // check computed style first in css case
michael@0 7643 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7644 bAny = mHTMLEditor->mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(
michael@0 7645 selNode, mCachedStyles[i].tag, &(mCachedStyles[i].attr), curValue,
michael@0 7646 nsHTMLCSSUtils::eComputed);
michael@0 7647 }
michael@0 7648 if (!bAny) {
michael@0 7649 // then check typeinstate and html style
michael@0 7650 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7651 nsresult res = mHTMLEditor->GetInlinePropertyBase(mCachedStyles[i].tag,
michael@0 7652 &(mCachedStyles[i].attr),
michael@0 7653 &(mCachedStyles[i].value),
michael@0 7654 &bFirst, &bAny, &bAll,
michael@0 7655 &curValue, false);
michael@0 7656 NS_ENSURE_SUCCESS(res, res);
michael@0 7657 }
michael@0 7658 // this style has disappeared through deletion. Add to our typeinstate:
michael@0 7659 if (!bAny || IsStyleCachePreservingAction(mTheAction)) {
michael@0 7660 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7661 mHTMLEditor->mTypeInState->SetProp(mCachedStyles[i].tag,
michael@0 7662 mCachedStyles[i].attr,
michael@0 7663 mCachedStyles[i].value);
michael@0 7664 }
michael@0 7665 }
michael@0 7666 }
michael@0 7667
michael@0 7668 return NS_OK;
michael@0 7669 }
michael@0 7670
michael@0 7671
michael@0 7672 void
michael@0 7673 nsHTMLEditRules::ClearCachedStyles()
michael@0 7674 {
michael@0 7675 // clear the mPresent bits in mCachedStyles array
michael@0 7676 for (uint32_t j = 0; j < SIZE_STYLE_TABLE; j++) {
michael@0 7677 mCachedStyles[j].mPresent = false;
michael@0 7678 mCachedStyles[j].value.Truncate();
michael@0 7679 }
michael@0 7680 }
michael@0 7681
michael@0 7682
michael@0 7683 nsresult
michael@0 7684 nsHTMLEditRules::AdjustSpecialBreaks(bool aSafeToAskFrames)
michael@0 7685 {
michael@0 7686 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 7687 nsCOMPtr<nsISupports> isupports;
michael@0 7688 int32_t nodeCount,j;
michael@0 7689
michael@0 7690 // gather list of empty nodes
michael@0 7691 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7692 nsEmptyEditableFunctor functor(mHTMLEditor);
michael@0 7693 nsDOMIterator iter;
michael@0 7694 nsresult res = iter.Init(mDocChangeRange);
michael@0 7695 NS_ENSURE_SUCCESS(res, res);
michael@0 7696 res = iter.AppendList(functor, arrayOfNodes);
michael@0 7697 NS_ENSURE_SUCCESS(res, res);
michael@0 7698
michael@0 7699 // put moz-br's into these empty li's and td's
michael@0 7700 nodeCount = arrayOfNodes.Count();
michael@0 7701 for (j = 0; j < nodeCount; j++)
michael@0 7702 {
michael@0 7703 // need to put br at END of node. It may have
michael@0 7704 // empty containers in it and still pass the "IsEmptynode" test,
michael@0 7705 // and we want the br's to be after them. Also, we want the br
michael@0 7706 // to be after the selection if the selection is in this node.
michael@0 7707 uint32_t len;
michael@0 7708 nsCOMPtr<nsIDOMNode> theNode = arrayOfNodes[0];
michael@0 7709 arrayOfNodes.RemoveObjectAt(0);
michael@0 7710 res = nsEditor::GetLengthOfDOMNode(theNode, len);
michael@0 7711 NS_ENSURE_SUCCESS(res, res);
michael@0 7712 res = CreateMozBR(theNode, (int32_t)len);
michael@0 7713 NS_ENSURE_SUCCESS(res, res);
michael@0 7714 }
michael@0 7715
michael@0 7716 return res;
michael@0 7717 }
michael@0 7718
michael@0 7719 nsresult
michael@0 7720 nsHTMLEditRules::AdjustWhitespace(nsISelection *aSelection)
michael@0 7721 {
michael@0 7722 // get selection point
michael@0 7723 nsCOMPtr<nsIDOMNode> selNode;
michael@0 7724 int32_t selOffset;
michael@0 7725 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7726 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
michael@0 7727 NS_ENSURE_SUCCESS(res, res);
michael@0 7728
michael@0 7729 // ask whitespace object to tweak nbsp's
michael@0 7730 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7731 return nsWSRunObject(mHTMLEditor, selNode, selOffset).AdjustWhitespace();
michael@0 7732 }
michael@0 7733
michael@0 7734 nsresult
michael@0 7735 nsHTMLEditRules::PinSelectionToNewBlock(nsISelection *aSelection)
michael@0 7736 {
michael@0 7737 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
michael@0 7738 if (!aSelection->Collapsed()) {
michael@0 7739 return NS_OK;
michael@0 7740 }
michael@0 7741
michael@0 7742 // get the (collapsed) selection location
michael@0 7743 nsCOMPtr<nsIDOMNode> selNode, temp;
michael@0 7744 int32_t selOffset;
michael@0 7745 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7746 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
michael@0 7747 NS_ENSURE_SUCCESS(res, res);
michael@0 7748 temp = selNode;
michael@0 7749
michael@0 7750 // use ranges and sRangeHelper to compare sel point to new block
michael@0 7751 nsCOMPtr<nsINode> node = do_QueryInterface(selNode);
michael@0 7752 NS_ENSURE_STATE(node);
michael@0 7753 nsRefPtr<nsRange> range = new nsRange(node);
michael@0 7754 res = range->SetStart(selNode, selOffset);
michael@0 7755 NS_ENSURE_SUCCESS(res, res);
michael@0 7756 res = range->SetEnd(selNode, selOffset);
michael@0 7757 NS_ENSURE_SUCCESS(res, res);
michael@0 7758 nsCOMPtr<nsIContent> block (do_QueryInterface(mNewBlock));
michael@0 7759 NS_ENSURE_TRUE(block, NS_ERROR_NO_INTERFACE);
michael@0 7760 bool nodeBefore, nodeAfter;
michael@0 7761 res = nsRange::CompareNodeToRange(block, range, &nodeBefore, &nodeAfter);
michael@0 7762 NS_ENSURE_SUCCESS(res, res);
michael@0 7763
michael@0 7764 if (nodeBefore && nodeAfter)
michael@0 7765 return NS_OK; // selection is inside block
michael@0 7766 else if (nodeBefore)
michael@0 7767 {
michael@0 7768 // selection is after block. put at end of block.
michael@0 7769 nsCOMPtr<nsIDOMNode> tmp = mNewBlock;
michael@0 7770 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7771 mHTMLEditor->GetLastEditableChild(mNewBlock, address_of(tmp));
michael@0 7772 uint32_t endPoint;
michael@0 7773 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7774 if (mHTMLEditor->IsTextNode(tmp) || !mHTMLEditor ||
michael@0 7775 mHTMLEditor->IsContainer(tmp))
michael@0 7776 {
michael@0 7777 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7778 res = nsEditor::GetLengthOfDOMNode(tmp, endPoint);
michael@0 7779 NS_ENSURE_SUCCESS(res, res);
michael@0 7780 }
michael@0 7781 else
michael@0 7782 {
michael@0 7783 tmp = nsEditor::GetNodeLocation(tmp, (int32_t*)&endPoint);
michael@0 7784 endPoint++; // want to be after this node
michael@0 7785 }
michael@0 7786 return aSelection->Collapse(tmp, (int32_t)endPoint);
michael@0 7787 }
michael@0 7788 else
michael@0 7789 {
michael@0 7790 // selection is before block. put at start of block.
michael@0 7791 nsCOMPtr<nsIDOMNode> tmp = mNewBlock;
michael@0 7792 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7793 mHTMLEditor->GetFirstEditableChild(mNewBlock, address_of(tmp));
michael@0 7794 int32_t offset;
michael@0 7795 if (!(mHTMLEditor->IsTextNode(tmp) || !mHTMLEditor ||
michael@0 7796 mHTMLEditor->IsContainer(tmp)))
michael@0 7797 {
michael@0 7798 tmp = nsEditor::GetNodeLocation(tmp, &offset);
michael@0 7799 }
michael@0 7800 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7801 return aSelection->Collapse(tmp, 0);
michael@0 7802 }
michael@0 7803 }
michael@0 7804
michael@0 7805 nsresult
michael@0 7806 nsHTMLEditRules::CheckInterlinePosition(nsISelection *aSelection)
michael@0 7807 {
michael@0 7808 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
michael@0 7809 nsCOMPtr<nsISelection> selection(aSelection);
michael@0 7810 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
michael@0 7811
michael@0 7812 // if the selection isn't collapsed, do nothing.
michael@0 7813 if (!aSelection->Collapsed()) {
michael@0 7814 return NS_OK;
michael@0 7815 }
michael@0 7816
michael@0 7817 // get the (collapsed) selection location
michael@0 7818 nsCOMPtr<nsIDOMNode> selNode, node;
michael@0 7819 int32_t selOffset;
michael@0 7820 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7821 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
michael@0 7822 NS_ENSURE_SUCCESS(res, res);
michael@0 7823
michael@0 7824 // First, let's check to see if we are after a <br>. We take care of this
michael@0 7825 // special-case first so that we don't accidentally fall through into one
michael@0 7826 // of the other conditionals.
michael@0 7827 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7828 mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(node), true);
michael@0 7829 if (node && nsTextEditUtils::IsBreak(node))
michael@0 7830 {
michael@0 7831 selPriv->SetInterlinePosition(true);
michael@0 7832 return NS_OK;
michael@0 7833 }
michael@0 7834
michael@0 7835 // are we after a block? If so try set caret to following content
michael@0 7836 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7837 mHTMLEditor->GetPriorHTMLSibling(selNode, selOffset, address_of(node));
michael@0 7838 if (node && IsBlockNode(node))
michael@0 7839 {
michael@0 7840 selPriv->SetInterlinePosition(true);
michael@0 7841 return NS_OK;
michael@0 7842 }
michael@0 7843
michael@0 7844 // are we before a block? If so try set caret to prior content
michael@0 7845 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7846 mHTMLEditor->GetNextHTMLSibling(selNode, selOffset, address_of(node));
michael@0 7847 if (node && IsBlockNode(node))
michael@0 7848 selPriv->SetInterlinePosition(false);
michael@0 7849 return NS_OK;
michael@0 7850 }
michael@0 7851
michael@0 7852 nsresult
michael@0 7853 nsHTMLEditRules::AdjustSelection(nsISelection *aSelection, nsIEditor::EDirection aAction)
michael@0 7854 {
michael@0 7855 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
michael@0 7856 nsCOMPtr<nsISelection> selection(aSelection);
michael@0 7857 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
michael@0 7858
michael@0 7859 // if the selection isn't collapsed, do nothing.
michael@0 7860 // moose: one thing to do instead is check for the case of
michael@0 7861 // only a single break selected, and collapse it. Good thing? Beats me.
michael@0 7862 if (!aSelection->Collapsed()) {
michael@0 7863 return NS_OK;
michael@0 7864 }
michael@0 7865
michael@0 7866 // get the (collapsed) selection location
michael@0 7867 nsCOMPtr<nsIDOMNode> selNode, temp;
michael@0 7868 int32_t selOffset;
michael@0 7869 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7870 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
michael@0 7871 NS_ENSURE_SUCCESS(res, res);
michael@0 7872 temp = selNode;
michael@0 7873
michael@0 7874 // are we in an editable node?
michael@0 7875 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7876 while (!mHTMLEditor->IsEditable(selNode))
michael@0 7877 {
michael@0 7878 // scan up the tree until we find an editable place to be
michael@0 7879 selNode = nsEditor::GetNodeLocation(temp, &selOffset);
michael@0 7880 NS_ENSURE_TRUE(selNode, NS_ERROR_FAILURE);
michael@0 7881 temp = selNode;
michael@0 7882 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7883 }
michael@0 7884
michael@0 7885 // make sure we aren't in an empty block - user will see no cursor. If this
michael@0 7886 // is happening, put a <br> in the block if allowed.
michael@0 7887 nsCOMPtr<nsIDOMNode> theblock;
michael@0 7888 if (IsBlockNode(selNode)) {
michael@0 7889 theblock = selNode;
michael@0 7890 } else {
michael@0 7891 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7892 theblock = mHTMLEditor->GetBlockNodeParent(selNode);
michael@0 7893 }
michael@0 7894 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7895 if (theblock && mHTMLEditor->IsEditable(theblock)) {
michael@0 7896 bool bIsEmptyNode;
michael@0 7897 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7898 res = mHTMLEditor->IsEmptyNode(theblock, &bIsEmptyNode, false, false);
michael@0 7899 NS_ENSURE_SUCCESS(res, res);
michael@0 7900 // check if br can go into the destination node
michael@0 7901 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7902 if (bIsEmptyNode && mHTMLEditor->CanContainTag(selNode, nsGkAtoms::br)) {
michael@0 7903 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7904 nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(mHTMLEditor->GetRoot());
michael@0 7905 NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE);
michael@0 7906 if (selNode == rootNode)
michael@0 7907 {
michael@0 7908 // Our root node is completely empty. Don't add a <br> here.
michael@0 7909 // AfterEditInner() will add one for us when it calls
michael@0 7910 // CreateBogusNodeIfNeeded()!
michael@0 7911 return NS_OK;
michael@0 7912 }
michael@0 7913
michael@0 7914 // we know we can skip the rest of this routine given the cirumstance
michael@0 7915 return CreateMozBR(selNode, selOffset);
michael@0 7916 }
michael@0 7917 }
michael@0 7918
michael@0 7919 // are we in a text node?
michael@0 7920 nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(selNode);
michael@0 7921 if (textNode)
michael@0 7922 return NS_OK; // we LIKE it when we are in a text node. that RULZ
michael@0 7923
michael@0 7924 // do we need to insert a special mozBR? We do if we are:
michael@0 7925 // 1) prior node is in same block where selection is AND
michael@0 7926 // 2) prior node is a br AND
michael@0 7927 // 3) that br is not visible
michael@0 7928
michael@0 7929 nsCOMPtr<nsIDOMNode> nearNode;
michael@0 7930 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7931 res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(nearNode));
michael@0 7932 NS_ENSURE_SUCCESS(res, res);
michael@0 7933 if (nearNode)
michael@0 7934 {
michael@0 7935 // is nearNode also a descendant of same block?
michael@0 7936 nsCOMPtr<nsIDOMNode> block, nearBlock;
michael@0 7937 if (IsBlockNode(selNode)) {
michael@0 7938 block = selNode;
michael@0 7939 } else {
michael@0 7940 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7941 block = mHTMLEditor->GetBlockNodeParent(selNode);
michael@0 7942 }
michael@0 7943 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7944 nearBlock = mHTMLEditor->GetBlockNodeParent(nearNode);
michael@0 7945 if (block == nearBlock)
michael@0 7946 {
michael@0 7947 if (nearNode && nsTextEditUtils::IsBreak(nearNode) )
michael@0 7948 {
michael@0 7949 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7950 if (!mHTMLEditor->IsVisBreak(nearNode))
michael@0 7951 {
michael@0 7952 // need to insert special moz BR. Why? Because if we don't
michael@0 7953 // the user will see no new line for the break. Also, things
michael@0 7954 // like table cells won't grow in height.
michael@0 7955 nsCOMPtr<nsIDOMNode> brNode;
michael@0 7956 res = CreateMozBR(selNode, selOffset, getter_AddRefs(brNode));
michael@0 7957 NS_ENSURE_SUCCESS(res, res);
michael@0 7958 selNode = nsEditor::GetNodeLocation(brNode, &selOffset);
michael@0 7959 // selection stays *before* moz-br, sticking to it
michael@0 7960 selPriv->SetInterlinePosition(true);
michael@0 7961 res = aSelection->Collapse(selNode,selOffset);
michael@0 7962 NS_ENSURE_SUCCESS(res, res);
michael@0 7963 }
michael@0 7964 else
michael@0 7965 {
michael@0 7966 nsCOMPtr<nsIDOMNode> nextNode;
michael@0 7967 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7968 mHTMLEditor->GetNextHTMLNode(nearNode, address_of(nextNode), true);
michael@0 7969 if (nextNode && nsTextEditUtils::IsMozBR(nextNode))
michael@0 7970 {
michael@0 7971 // selection between br and mozbr. make it stick to mozbr
michael@0 7972 // so that it will be on blank line.
michael@0 7973 selPriv->SetInterlinePosition(true);
michael@0 7974 }
michael@0 7975 }
michael@0 7976 }
michael@0 7977 }
michael@0 7978 }
michael@0 7979
michael@0 7980 // we aren't in a textnode: are we adjacent to text or a break or an image?
michael@0 7981 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7982 res = mHTMLEditor->GetPriorHTMLNode(selNode, selOffset, address_of(nearNode), true);
michael@0 7983 NS_ENSURE_SUCCESS(res, res);
michael@0 7984 if (nearNode && (nsTextEditUtils::IsBreak(nearNode)
michael@0 7985 || nsEditor::IsTextNode(nearNode)
michael@0 7986 || nsHTMLEditUtils::IsImage(nearNode)
michael@0 7987 || nsHTMLEditUtils::IsHR(nearNode)))
michael@0 7988 return NS_OK; // this is a good place for the caret to be
michael@0 7989 NS_ENSURE_STATE(mHTMLEditor);
michael@0 7990 res = mHTMLEditor->GetNextHTMLNode(selNode, selOffset, address_of(nearNode), true);
michael@0 7991 NS_ENSURE_SUCCESS(res, res);
michael@0 7992 if (nearNode && (nsTextEditUtils::IsBreak(nearNode)
michael@0 7993 || nsEditor::IsTextNode(nearNode)
michael@0 7994 || nsHTMLEditUtils::IsImage(nearNode)
michael@0 7995 || nsHTMLEditUtils::IsHR(nearNode)))
michael@0 7996 return NS_OK; // this is a good place for the caret to be
michael@0 7997
michael@0 7998 // look for a nearby text node.
michael@0 7999 // prefer the correct direction.
michael@0 8000 res = FindNearSelectableNode(selNode, selOffset, aAction, address_of(nearNode));
michael@0 8001 NS_ENSURE_SUCCESS(res, res);
michael@0 8002
michael@0 8003 if (nearNode)
michael@0 8004 {
michael@0 8005 // is the nearnode a text node?
michael@0 8006 textNode = do_QueryInterface(nearNode);
michael@0 8007 if (textNode)
michael@0 8008 {
michael@0 8009 int32_t offset = 0;
michael@0 8010 // put selection in right place:
michael@0 8011 if (aAction == nsIEditor::ePrevious)
michael@0 8012 textNode->GetLength((uint32_t*)&offset);
michael@0 8013 res = aSelection->Collapse(nearNode,offset);
michael@0 8014 }
michael@0 8015 else // must be break or image
michael@0 8016 {
michael@0 8017 selNode = nsEditor::GetNodeLocation(nearNode, &selOffset);
michael@0 8018 if (aAction == nsIEditor::ePrevious) selOffset++; // want to be beyond it if we backed up to it
michael@0 8019 res = aSelection->Collapse(selNode, selOffset);
michael@0 8020 }
michael@0 8021 }
michael@0 8022 return res;
michael@0 8023 }
michael@0 8024
michael@0 8025
michael@0 8026 nsresult
michael@0 8027 nsHTMLEditRules::FindNearSelectableNode(nsIDOMNode *aSelNode,
michael@0 8028 int32_t aSelOffset,
michael@0 8029 nsIEditor::EDirection &aDirection,
michael@0 8030 nsCOMPtr<nsIDOMNode> *outSelectableNode)
michael@0 8031 {
michael@0 8032 NS_ENSURE_TRUE(aSelNode && outSelectableNode, NS_ERROR_NULL_POINTER);
michael@0 8033 *outSelectableNode = nullptr;
michael@0 8034 nsresult res = NS_OK;
michael@0 8035
michael@0 8036 nsCOMPtr<nsIDOMNode> nearNode, curNode;
michael@0 8037 if (aDirection == nsIEditor::ePrevious) {
michael@0 8038 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8039 res = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode));
michael@0 8040 } else {
michael@0 8041 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8042 res = mHTMLEditor->GetNextHTMLNode(aSelNode, aSelOffset, address_of(nearNode));
michael@0 8043 }
michael@0 8044 NS_ENSURE_SUCCESS(res, res);
michael@0 8045
michael@0 8046 if (!nearNode) // try the other direction then
michael@0 8047 {
michael@0 8048 if (aDirection == nsIEditor::ePrevious)
michael@0 8049 aDirection = nsIEditor::eNext;
michael@0 8050 else
michael@0 8051 aDirection = nsIEditor::ePrevious;
michael@0 8052
michael@0 8053 if (aDirection == nsIEditor::ePrevious) {
michael@0 8054 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8055 res = mHTMLEditor->GetPriorHTMLNode(aSelNode, aSelOffset, address_of(nearNode));
michael@0 8056 } else {
michael@0 8057 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8058 res = mHTMLEditor->GetNextHTMLNode(aSelNode, aSelOffset, address_of(nearNode));
michael@0 8059 }
michael@0 8060 NS_ENSURE_SUCCESS(res, res);
michael@0 8061 }
michael@0 8062
michael@0 8063 // scan in the right direction until we find an eligible text node,
michael@0 8064 // but don't cross any breaks, images, or table elements.
michael@0 8065 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8066 while (nearNode && !(mHTMLEditor->IsTextNode(nearNode)
michael@0 8067 || nsTextEditUtils::IsBreak(nearNode)
michael@0 8068 || nsHTMLEditUtils::IsImage(nearNode)))
michael@0 8069 {
michael@0 8070 curNode = nearNode;
michael@0 8071 if (aDirection == nsIEditor::ePrevious) {
michael@0 8072 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8073 res = mHTMLEditor->GetPriorHTMLNode(curNode, address_of(nearNode));
michael@0 8074 } else {
michael@0 8075 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8076 res = mHTMLEditor->GetNextHTMLNode(curNode, address_of(nearNode));
michael@0 8077 }
michael@0 8078 NS_ENSURE_SUCCESS(res, res);
michael@0 8079 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8080 }
michael@0 8081
michael@0 8082 if (nearNode)
michael@0 8083 {
michael@0 8084 // don't cross any table elements
michael@0 8085 if (InDifferentTableElements(nearNode, aSelNode)) {
michael@0 8086 return NS_OK;
michael@0 8087 }
michael@0 8088
michael@0 8089 // otherwise, ok, we have found a good spot to put the selection
michael@0 8090 *outSelectableNode = do_QueryInterface(nearNode);
michael@0 8091 }
michael@0 8092 return res;
michael@0 8093 }
michael@0 8094
michael@0 8095
michael@0 8096 bool nsHTMLEditRules::InDifferentTableElements(nsIDOMNode* aNode1,
michael@0 8097 nsIDOMNode* aNode2)
michael@0 8098 {
michael@0 8099 nsCOMPtr<nsINode> node1 = do_QueryInterface(aNode1);
michael@0 8100 nsCOMPtr<nsINode> node2 = do_QueryInterface(aNode2);
michael@0 8101 return InDifferentTableElements(node1, node2);
michael@0 8102 }
michael@0 8103
michael@0 8104 bool
michael@0 8105 nsHTMLEditRules::InDifferentTableElements(nsINode* aNode1, nsINode* aNode2)
michael@0 8106 {
michael@0 8107 MOZ_ASSERT(aNode1 && aNode2);
michael@0 8108
michael@0 8109 while (aNode1 && !nsHTMLEditUtils::IsTableElement(aNode1)) {
michael@0 8110 aNode1 = aNode1->GetParentNode();
michael@0 8111 }
michael@0 8112
michael@0 8113 while (aNode2 && !nsHTMLEditUtils::IsTableElement(aNode2)) {
michael@0 8114 aNode2 = aNode2->GetParentNode();
michael@0 8115 }
michael@0 8116
michael@0 8117 return aNode1 != aNode2;
michael@0 8118 }
michael@0 8119
michael@0 8120
michael@0 8121 nsresult
michael@0 8122 nsHTMLEditRules::RemoveEmptyNodes()
michael@0 8123 {
michael@0 8124 // some general notes on the algorithm used here: the goal is to examine all the
michael@0 8125 // nodes in mDocChangeRange, and remove the empty ones. We do this by using a
michael@0 8126 // content iterator to traverse all the nodes in the range, and placing the empty
michael@0 8127 // nodes into an array. After finishing the iteration, we delete the empty nodes
michael@0 8128 // in the array. (they cannot be deleted as we find them becasue that would
michael@0 8129 // invalidate the iterator.)
michael@0 8130 // Since checking to see if a node is empty can be costly for nodes with many
michael@0 8131 // descendants, there are some optimizations made. I rely on the fact that the
michael@0 8132 // iterator is post-order: it will visit children of a node before visiting the
michael@0 8133 // parent node. So if I find that a child node is not empty, I know that its
michael@0 8134 // parent is not empty without even checking. So I put the parent on a "skipList"
michael@0 8135 // which is just a voidArray of nodes I can skip the empty check on. If I
michael@0 8136 // encounter a node on the skiplist, i skip the processing for that node and replace
michael@0 8137 // its slot in the skiplist with that node's parent.
michael@0 8138 // An interseting idea is to go ahead and regard parent nodes that are NOT on the
michael@0 8139 // skiplist as being empty (without even doing the IsEmptyNode check) on the theory
michael@0 8140 // that if they weren't empty, we would have encountered a non-empty child earlier
michael@0 8141 // and thus put this parent node on the skiplist.
michael@0 8142 // Unfortunately I can't use that strategy here, because the range may include
michael@0 8143 // some children of a node while excluding others. Thus I could find all the
michael@0 8144 // _examined_ children empty, but still not have an empty parent.
michael@0 8145
michael@0 8146 // need an iterator
michael@0 8147 nsCOMPtr<nsIContentIterator> iter =
michael@0 8148 do_CreateInstance("@mozilla.org/content/post-content-iterator;1");
michael@0 8149 NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER);
michael@0 8150
michael@0 8151 nsresult res = iter->Init(mDocChangeRange);
michael@0 8152 NS_ENSURE_SUCCESS(res, res);
michael@0 8153
michael@0 8154 nsCOMArray<nsINode> arrayOfEmptyNodes, arrayOfEmptyCites;
michael@0 8155 nsTArray<nsCOMPtr<nsINode> > skipList;
michael@0 8156
michael@0 8157 // check for empty nodes
michael@0 8158 while (!iter->IsDone()) {
michael@0 8159 nsINode* node = iter->GetCurrentNode();
michael@0 8160 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
michael@0 8161
michael@0 8162 nsINode* parent = node->GetParentNode();
michael@0 8163
michael@0 8164 uint32_t idx = skipList.IndexOf(node);
michael@0 8165 if (idx != skipList.NoIndex) {
michael@0 8166 // this node is on our skip list. Skip processing for this node,
michael@0 8167 // and replace its value in the skip list with the value of its parent
michael@0 8168 skipList[idx] = parent;
michael@0 8169 } else {
michael@0 8170 bool bIsCandidate = false;
michael@0 8171 bool bIsEmptyNode = false;
michael@0 8172 bool bIsMailCite = false;
michael@0 8173
michael@0 8174 if (node->IsElement()) {
michael@0 8175 dom::Element* element = node->AsElement();
michael@0 8176 if (element->IsHTML(nsGkAtoms::body)) {
michael@0 8177 // don't delete the body
michael@0 8178 } else if ((bIsMailCite = nsHTMLEditUtils::IsMailCite(element)) ||
michael@0 8179 element->IsHTML(nsGkAtoms::a) ||
michael@0 8180 nsHTMLEditUtils::IsInlineStyle(element) ||
michael@0 8181 nsHTMLEditUtils::IsList(element) ||
michael@0 8182 element->IsHTML(nsGkAtoms::div)) {
michael@0 8183 // only consider certain nodes to be empty for purposes of removal
michael@0 8184 bIsCandidate = true;
michael@0 8185 } else if (nsHTMLEditUtils::IsFormatNode(element) ||
michael@0 8186 nsHTMLEditUtils::IsListItem(element) ||
michael@0 8187 element->IsHTML(nsGkAtoms::blockquote)) {
michael@0 8188 // these node types are candidates if selection is not in them
michael@0 8189 // if it is one of these, don't delete if selection inside.
michael@0 8190 // this is so we can create empty headings, etc, for the
michael@0 8191 // user to type into.
michael@0 8192 bool bIsSelInNode;
michael@0 8193 res = SelectionEndpointInNode(node, &bIsSelInNode);
michael@0 8194 NS_ENSURE_SUCCESS(res, res);
michael@0 8195 if (!bIsSelInNode)
michael@0 8196 {
michael@0 8197 bIsCandidate = true;
michael@0 8198 }
michael@0 8199 }
michael@0 8200 }
michael@0 8201
michael@0 8202 if (bIsCandidate) {
michael@0 8203 // we delete mailcites even if they have a solo br in them
michael@0 8204 // other nodes we require to be empty
michael@0 8205 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8206 res = mHTMLEditor->IsEmptyNode(node->AsDOMNode(), &bIsEmptyNode,
michael@0 8207 bIsMailCite, true);
michael@0 8208 NS_ENSURE_SUCCESS(res, res);
michael@0 8209 if (bIsEmptyNode) {
michael@0 8210 if (bIsMailCite) {
michael@0 8211 // mailcites go on a separate list from other empty nodes
michael@0 8212 arrayOfEmptyCites.AppendObject(node);
michael@0 8213 } else {
michael@0 8214 arrayOfEmptyNodes.AppendObject(node);
michael@0 8215 }
michael@0 8216 }
michael@0 8217 }
michael@0 8218
michael@0 8219 if (!bIsEmptyNode) {
michael@0 8220 // put parent on skip list
michael@0 8221 skipList.AppendElement(parent);
michael@0 8222 }
michael@0 8223 }
michael@0 8224
michael@0 8225 iter->Next();
michael@0 8226 }
michael@0 8227
michael@0 8228 // now delete the empty nodes
michael@0 8229 int32_t nodeCount = arrayOfEmptyNodes.Count();
michael@0 8230 for (int32_t j = 0; j < nodeCount; j++) {
michael@0 8231 nsCOMPtr<nsIDOMNode> delNode = arrayOfEmptyNodes[0]->AsDOMNode();
michael@0 8232 arrayOfEmptyNodes.RemoveObjectAt(0);
michael@0 8233 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8234 if (mHTMLEditor->IsModifiableNode(delNode)) {
michael@0 8235 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8236 res = mHTMLEditor->DeleteNode(delNode);
michael@0 8237 NS_ENSURE_SUCCESS(res, res);
michael@0 8238 }
michael@0 8239 }
michael@0 8240
michael@0 8241 // now delete the empty mailcites
michael@0 8242 // this is a separate step because we want to pull out any br's and preserve them.
michael@0 8243 nodeCount = arrayOfEmptyCites.Count();
michael@0 8244 for (int32_t j = 0; j < nodeCount; j++) {
michael@0 8245 nsCOMPtr<nsIDOMNode> delNode = arrayOfEmptyCites[0]->AsDOMNode();
michael@0 8246 arrayOfEmptyCites.RemoveObjectAt(0);
michael@0 8247 bool bIsEmptyNode;
michael@0 8248 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8249 res = mHTMLEditor->IsEmptyNode(delNode, &bIsEmptyNode, false, true);
michael@0 8250 NS_ENSURE_SUCCESS(res, res);
michael@0 8251 if (!bIsEmptyNode)
michael@0 8252 {
michael@0 8253 // we are deleting a cite that has just a br. We want to delete cite,
michael@0 8254 // but preserve br.
michael@0 8255 nsCOMPtr<nsIDOMNode> parent, brNode;
michael@0 8256 int32_t offset;
michael@0 8257 parent = nsEditor::GetNodeLocation(delNode, &offset);
michael@0 8258 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8259 res = mHTMLEditor->CreateBR(parent, offset, address_of(brNode));
michael@0 8260 NS_ENSURE_SUCCESS(res, res);
michael@0 8261 }
michael@0 8262 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8263 res = mHTMLEditor->DeleteNode(delNode);
michael@0 8264 NS_ENSURE_SUCCESS(res, res);
michael@0 8265 }
michael@0 8266
michael@0 8267 return res;
michael@0 8268 }
michael@0 8269
michael@0 8270 nsresult
michael@0 8271 nsHTMLEditRules::SelectionEndpointInNode(nsINode* aNode, bool* aResult)
michael@0 8272 {
michael@0 8273 NS_ENSURE_TRUE(aNode && aResult, NS_ERROR_NULL_POINTER);
michael@0 8274
michael@0 8275 nsIDOMNode* node = aNode->AsDOMNode();
michael@0 8276
michael@0 8277 *aResult = false;
michael@0 8278
michael@0 8279 nsCOMPtr<nsISelection>selection;
michael@0 8280 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8281 nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
michael@0 8282 NS_ENSURE_SUCCESS(res, res);
michael@0 8283
michael@0 8284 Selection* sel = static_cast<Selection*>(selection.get());
michael@0 8285 uint32_t rangeCount = sel->GetRangeCount();
michael@0 8286 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
michael@0 8287 nsRefPtr<nsRange> range = sel->GetRangeAt(rangeIdx);
michael@0 8288 nsCOMPtr<nsIDOMNode> startParent, endParent;
michael@0 8289 range->GetStartContainer(getter_AddRefs(startParent));
michael@0 8290 if (startParent)
michael@0 8291 {
michael@0 8292 if (node == startParent) {
michael@0 8293 *aResult = true;
michael@0 8294 return NS_OK;
michael@0 8295 }
michael@0 8296 if (nsEditorUtils::IsDescendantOf(startParent, node)) {
michael@0 8297 *aResult = true;
michael@0 8298 return NS_OK;
michael@0 8299 }
michael@0 8300 }
michael@0 8301 range->GetEndContainer(getter_AddRefs(endParent));
michael@0 8302 if (startParent == endParent) continue;
michael@0 8303 if (endParent)
michael@0 8304 {
michael@0 8305 if (node == endParent) {
michael@0 8306 *aResult = true;
michael@0 8307 return NS_OK;
michael@0 8308 }
michael@0 8309 if (nsEditorUtils::IsDescendantOf(endParent, node)) {
michael@0 8310 *aResult = true;
michael@0 8311 return NS_OK;
michael@0 8312 }
michael@0 8313 }
michael@0 8314 }
michael@0 8315 return res;
michael@0 8316 }
michael@0 8317
michael@0 8318 ///////////////////////////////////////////////////////////////////////////
michael@0 8319 // IsEmptyInline: return true if aNode is an empty inline container
michael@0 8320 //
michael@0 8321 //
michael@0 8322 bool
michael@0 8323 nsHTMLEditRules::IsEmptyInline(nsIDOMNode *aNode)
michael@0 8324 {
michael@0 8325 if (aNode && IsInlineNode(aNode) && mHTMLEditor &&
michael@0 8326 mHTMLEditor->IsContainer(aNode))
michael@0 8327 {
michael@0 8328 bool bEmpty;
michael@0 8329 NS_ENSURE_TRUE(mHTMLEditor, false);
michael@0 8330 mHTMLEditor->IsEmptyNode(aNode, &bEmpty);
michael@0 8331 return bEmpty;
michael@0 8332 }
michael@0 8333 return false;
michael@0 8334 }
michael@0 8335
michael@0 8336
michael@0 8337 bool
michael@0 8338 nsHTMLEditRules::ListIsEmptyLine(nsCOMArray<nsIDOMNode> &arrayOfNodes)
michael@0 8339 {
michael@0 8340 // we have a list of nodes which we are candidates for being moved
michael@0 8341 // into a new block. Determine if it's anything more than a blank line.
michael@0 8342 // Look for editable content above and beyond one single BR.
michael@0 8343 int32_t listCount = arrayOfNodes.Count();
michael@0 8344 NS_ENSURE_TRUE(listCount, true);
michael@0 8345 nsCOMPtr<nsIDOMNode> somenode;
michael@0 8346 int32_t j, brCount=0;
michael@0 8347 for (j = 0; j < listCount; j++)
michael@0 8348 {
michael@0 8349 somenode = arrayOfNodes[j];
michael@0 8350 NS_ENSURE_TRUE(mHTMLEditor, false);
michael@0 8351 if (somenode && mHTMLEditor->IsEditable(somenode))
michael@0 8352 {
michael@0 8353 if (nsTextEditUtils::IsBreak(somenode))
michael@0 8354 {
michael@0 8355 // first break doesn't count
michael@0 8356 if (brCount) return false;
michael@0 8357 brCount++;
michael@0 8358 }
michael@0 8359 else if (IsEmptyInline(somenode))
michael@0 8360 {
michael@0 8361 // empty inline, keep looking
michael@0 8362 }
michael@0 8363 else return false;
michael@0 8364 }
michael@0 8365 }
michael@0 8366 return true;
michael@0 8367 }
michael@0 8368
michael@0 8369
michael@0 8370 nsresult
michael@0 8371 nsHTMLEditRules::PopListItem(nsIDOMNode *aListItem, bool *aOutOfList)
michael@0 8372 {
michael@0 8373 // check parms
michael@0 8374 NS_ENSURE_TRUE(aListItem && aOutOfList, NS_ERROR_NULL_POINTER);
michael@0 8375
michael@0 8376 // init out params
michael@0 8377 *aOutOfList = false;
michael@0 8378
michael@0 8379 nsCOMPtr<nsIDOMNode> curNode( do_QueryInterface(aListItem));
michael@0 8380 int32_t offset;
michael@0 8381 nsCOMPtr<nsIDOMNode> curParent = nsEditor::GetNodeLocation(curNode, &offset);
michael@0 8382
michael@0 8383 if (!nsHTMLEditUtils::IsListItem(curNode))
michael@0 8384 return NS_ERROR_FAILURE;
michael@0 8385
michael@0 8386 // if it's first or last list item, don't need to split the list
michael@0 8387 // otherwise we do.
michael@0 8388 int32_t parOffset;
michael@0 8389 nsCOMPtr<nsIDOMNode> curParPar = nsEditor::GetNodeLocation(curParent, &parOffset);
michael@0 8390
michael@0 8391 bool bIsFirstListItem;
michael@0 8392 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8393 nsresult res = mHTMLEditor->IsFirstEditableChild(curNode, &bIsFirstListItem);
michael@0 8394 NS_ENSURE_SUCCESS(res, res);
michael@0 8395
michael@0 8396 bool bIsLastListItem;
michael@0 8397 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8398 res = mHTMLEditor->IsLastEditableChild(curNode, &bIsLastListItem);
michael@0 8399 NS_ENSURE_SUCCESS(res, res);
michael@0 8400
michael@0 8401 if (!bIsFirstListItem && !bIsLastListItem)
michael@0 8402 {
michael@0 8403 // split the list
michael@0 8404 nsCOMPtr<nsIDOMNode> newBlock;
michael@0 8405 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8406 res = mHTMLEditor->SplitNode(curParent, offset, getter_AddRefs(newBlock));
michael@0 8407 NS_ENSURE_SUCCESS(res, res);
michael@0 8408 }
michael@0 8409
michael@0 8410 if (!bIsFirstListItem) parOffset++;
michael@0 8411
michael@0 8412 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8413 res = mHTMLEditor->MoveNode(curNode, curParPar, parOffset);
michael@0 8414 NS_ENSURE_SUCCESS(res, res);
michael@0 8415
michael@0 8416 // unwrap list item contents if they are no longer in a list
michael@0 8417 if (!nsHTMLEditUtils::IsList(curParPar)
michael@0 8418 && nsHTMLEditUtils::IsListItem(curNode))
michael@0 8419 {
michael@0 8420 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8421 res = mHTMLEditor->RemoveBlockContainer(curNode);
michael@0 8422 NS_ENSURE_SUCCESS(res, res);
michael@0 8423 *aOutOfList = true;
michael@0 8424 }
michael@0 8425 return res;
michael@0 8426 }
michael@0 8427
michael@0 8428 nsresult
michael@0 8429 nsHTMLEditRules::RemoveListStructure(nsIDOMNode *aList)
michael@0 8430 {
michael@0 8431 NS_ENSURE_ARG_POINTER(aList);
michael@0 8432
michael@0 8433 nsresult res;
michael@0 8434
michael@0 8435 nsCOMPtr<nsIDOMNode> child;
michael@0 8436 aList->GetFirstChild(getter_AddRefs(child));
michael@0 8437
michael@0 8438 while (child)
michael@0 8439 {
michael@0 8440 if (nsHTMLEditUtils::IsListItem(child))
michael@0 8441 {
michael@0 8442 bool bOutOfList;
michael@0 8443 do
michael@0 8444 {
michael@0 8445 res = PopListItem(child, &bOutOfList);
michael@0 8446 NS_ENSURE_SUCCESS(res, res);
michael@0 8447 } while (!bOutOfList); // keep popping it out until it's not in a list anymore
michael@0 8448 }
michael@0 8449 else if (nsHTMLEditUtils::IsList(child))
michael@0 8450 {
michael@0 8451 res = RemoveListStructure(child);
michael@0 8452 NS_ENSURE_SUCCESS(res, res);
michael@0 8453 }
michael@0 8454 else
michael@0 8455 {
michael@0 8456 // delete any non- list items for now
michael@0 8457 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8458 res = mHTMLEditor->DeleteNode(child);
michael@0 8459 NS_ENSURE_SUCCESS(res, res);
michael@0 8460 }
michael@0 8461 aList->GetFirstChild(getter_AddRefs(child));
michael@0 8462 }
michael@0 8463 // delete the now-empty list
michael@0 8464 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8465 res = mHTMLEditor->RemoveBlockContainer(aList);
michael@0 8466 NS_ENSURE_SUCCESS(res, res);
michael@0 8467
michael@0 8468 return res;
michael@0 8469 }
michael@0 8470
michael@0 8471
michael@0 8472 nsresult
michael@0 8473 nsHTMLEditRules::ConfirmSelectionInBody()
michael@0 8474 {
michael@0 8475 nsresult res = NS_OK;
michael@0 8476
michael@0 8477 // get the body
michael@0 8478 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8479 nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(mHTMLEditor->GetRoot());
michael@0 8480 NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED);
michael@0 8481
michael@0 8482 // get the selection
michael@0 8483 nsCOMPtr<nsISelection>selection;
michael@0 8484 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8485 res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
michael@0 8486 NS_ENSURE_SUCCESS(res, res);
michael@0 8487
michael@0 8488 // get the selection start location
michael@0 8489 nsCOMPtr<nsIDOMNode> selNode, temp, parent;
michael@0 8490 int32_t selOffset;
michael@0 8491 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8492 res = mHTMLEditor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
michael@0 8493 NS_ENSURE_SUCCESS(res, res);
michael@0 8494 temp = selNode;
michael@0 8495
michael@0 8496 // check that selNode is inside body
michael@0 8497 while (temp && !nsTextEditUtils::IsBody(temp))
michael@0 8498 {
michael@0 8499 res = temp->GetParentNode(getter_AddRefs(parent));
michael@0 8500 temp = parent;
michael@0 8501 }
michael@0 8502
michael@0 8503 // if we aren't in the body, force the issue
michael@0 8504 if (!temp)
michael@0 8505 {
michael@0 8506 // uncomment this to see when we get bad selections
michael@0 8507 // NS_NOTREACHED("selection not in body");
michael@0 8508 selection->Collapse(rootElement, 0);
michael@0 8509 }
michael@0 8510
michael@0 8511 // get the selection end location
michael@0 8512 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8513 res = mHTMLEditor->GetEndNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
michael@0 8514 NS_ENSURE_SUCCESS(res, res);
michael@0 8515 temp = selNode;
michael@0 8516
michael@0 8517 // check that selNode is inside body
michael@0 8518 while (temp && !nsTextEditUtils::IsBody(temp))
michael@0 8519 {
michael@0 8520 res = temp->GetParentNode(getter_AddRefs(parent));
michael@0 8521 temp = parent;
michael@0 8522 }
michael@0 8523
michael@0 8524 // if we aren't in the body, force the issue
michael@0 8525 if (!temp)
michael@0 8526 {
michael@0 8527 // uncomment this to see when we get bad selections
michael@0 8528 // NS_NOTREACHED("selection not in body");
michael@0 8529 selection->Collapse(rootElement, 0);
michael@0 8530 }
michael@0 8531
michael@0 8532 return res;
michael@0 8533 }
michael@0 8534
michael@0 8535
michael@0 8536 nsresult
michael@0 8537 nsHTMLEditRules::UpdateDocChangeRange(nsIDOMRange *aRange)
michael@0 8538 {
michael@0 8539 nsresult res = NS_OK;
michael@0 8540
michael@0 8541 // first make sure aRange is in the document. It might not be if
michael@0 8542 // portions of our editting action involved manipulating nodes
michael@0 8543 // prior to placing them in the document (e.g., populating a list item
michael@0 8544 // before placing it in its list)
michael@0 8545 nsCOMPtr<nsIDOMNode> startNode;
michael@0 8546 res = aRange->GetStartContainer(getter_AddRefs(startNode));
michael@0 8547 NS_ENSURE_SUCCESS(res, res);
michael@0 8548 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8549 if (!mHTMLEditor->IsDescendantOfRoot(startNode)) {
michael@0 8550 // just return - we don't need to adjust mDocChangeRange in this case
michael@0 8551 return NS_OK;
michael@0 8552 }
michael@0 8553
michael@0 8554 if (!mDocChangeRange)
michael@0 8555 {
michael@0 8556 // clone aRange.
michael@0 8557 nsCOMPtr<nsIDOMRange> range;
michael@0 8558 res = aRange->CloneRange(getter_AddRefs(range));
michael@0 8559 mDocChangeRange = static_cast<nsRange*>(range.get());
michael@0 8560 }
michael@0 8561 else
michael@0 8562 {
michael@0 8563 int16_t result;
michael@0 8564
michael@0 8565 // compare starts of ranges
michael@0 8566 res = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::START_TO_START, aRange, &result);
michael@0 8567 if (res == NS_ERROR_NOT_INITIALIZED) {
michael@0 8568 // This will happen is mDocChangeRange is non-null, but the range is
michael@0 8569 // uninitialized. In this case we'll set the start to aRange start.
michael@0 8570 // The same test won't be needed further down since after we've set
michael@0 8571 // the start the range will be collapsed to that point.
michael@0 8572 result = 1;
michael@0 8573 res = NS_OK;
michael@0 8574 }
michael@0 8575 NS_ENSURE_SUCCESS(res, res);
michael@0 8576 if (result > 0) // positive result means mDocChangeRange start is after aRange start
michael@0 8577 {
michael@0 8578 int32_t startOffset;
michael@0 8579 res = aRange->GetStartOffset(&startOffset);
michael@0 8580 NS_ENSURE_SUCCESS(res, res);
michael@0 8581 res = mDocChangeRange->SetStart(startNode, startOffset);
michael@0 8582 NS_ENSURE_SUCCESS(res, res);
michael@0 8583 }
michael@0 8584
michael@0 8585 // compare ends of ranges
michael@0 8586 res = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::END_TO_END, aRange, &result);
michael@0 8587 NS_ENSURE_SUCCESS(res, res);
michael@0 8588 if (result < 0) // negative result means mDocChangeRange end is before aRange end
michael@0 8589 {
michael@0 8590 nsCOMPtr<nsIDOMNode> endNode;
michael@0 8591 int32_t endOffset;
michael@0 8592 res = aRange->GetEndContainer(getter_AddRefs(endNode));
michael@0 8593 NS_ENSURE_SUCCESS(res, res);
michael@0 8594 res = aRange->GetEndOffset(&endOffset);
michael@0 8595 NS_ENSURE_SUCCESS(res, res);
michael@0 8596 res = mDocChangeRange->SetEnd(endNode, endOffset);
michael@0 8597 NS_ENSURE_SUCCESS(res, res);
michael@0 8598 }
michael@0 8599 }
michael@0 8600 return res;
michael@0 8601 }
michael@0 8602
michael@0 8603 nsresult
michael@0 8604 nsHTMLEditRules::InsertMozBRIfNeeded(nsIDOMNode *aNode)
michael@0 8605 {
michael@0 8606 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
michael@0 8607 if (!IsBlockNode(aNode)) return NS_OK;
michael@0 8608
michael@0 8609 bool isEmpty;
michael@0 8610 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8611 nsresult res = mHTMLEditor->IsEmptyNode(aNode, &isEmpty);
michael@0 8612 NS_ENSURE_SUCCESS(res, res);
michael@0 8613 if (!isEmpty) {
michael@0 8614 return NS_OK;
michael@0 8615 }
michael@0 8616
michael@0 8617 return CreateMozBR(aNode, 0);
michael@0 8618 }
michael@0 8619
michael@0 8620 NS_IMETHODIMP
michael@0 8621 nsHTMLEditRules::WillCreateNode(const nsAString& aTag, nsIDOMNode *aParent, int32_t aPosition)
michael@0 8622 {
michael@0 8623 return NS_OK;
michael@0 8624 }
michael@0 8625
michael@0 8626 NS_IMETHODIMP
michael@0 8627 nsHTMLEditRules::DidCreateNode(const nsAString& aTag,
michael@0 8628 nsIDOMNode *aNode,
michael@0 8629 nsIDOMNode *aParent,
michael@0 8630 int32_t aPosition,
michael@0 8631 nsresult aResult)
michael@0 8632 {
michael@0 8633 if (!mListenerEnabled) {
michael@0 8634 return NS_OK;
michael@0 8635 }
michael@0 8636 // assumption that Join keeps the righthand node
michael@0 8637 nsresult res = mUtilRange->SelectNode(aNode);
michael@0 8638 NS_ENSURE_SUCCESS(res, res);
michael@0 8639 res = UpdateDocChangeRange(mUtilRange);
michael@0 8640 return res;
michael@0 8641 }
michael@0 8642
michael@0 8643
michael@0 8644 NS_IMETHODIMP
michael@0 8645 nsHTMLEditRules::WillInsertNode(nsIDOMNode *aNode, nsIDOMNode *aParent, int32_t aPosition)
michael@0 8646 {
michael@0 8647 return NS_OK;
michael@0 8648 }
michael@0 8649
michael@0 8650
michael@0 8651 NS_IMETHODIMP
michael@0 8652 nsHTMLEditRules::DidInsertNode(nsIDOMNode *aNode,
michael@0 8653 nsIDOMNode *aParent,
michael@0 8654 int32_t aPosition,
michael@0 8655 nsresult aResult)
michael@0 8656 {
michael@0 8657 if (!mListenerEnabled) {
michael@0 8658 return NS_OK;
michael@0 8659 }
michael@0 8660 nsresult res = mUtilRange->SelectNode(aNode);
michael@0 8661 NS_ENSURE_SUCCESS(res, res);
michael@0 8662 res = UpdateDocChangeRange(mUtilRange);
michael@0 8663 return res;
michael@0 8664 }
michael@0 8665
michael@0 8666
michael@0 8667 NS_IMETHODIMP
michael@0 8668 nsHTMLEditRules::WillDeleteNode(nsIDOMNode *aChild)
michael@0 8669 {
michael@0 8670 if (!mListenerEnabled) {
michael@0 8671 return NS_OK;
michael@0 8672 }
michael@0 8673 nsresult res = mUtilRange->SelectNode(aChild);
michael@0 8674 NS_ENSURE_SUCCESS(res, res);
michael@0 8675 res = UpdateDocChangeRange(mUtilRange);
michael@0 8676 return res;
michael@0 8677 }
michael@0 8678
michael@0 8679
michael@0 8680 NS_IMETHODIMP
michael@0 8681 nsHTMLEditRules::DidDeleteNode(nsIDOMNode *aChild, nsresult aResult)
michael@0 8682 {
michael@0 8683 return NS_OK;
michael@0 8684 }
michael@0 8685
michael@0 8686
michael@0 8687 NS_IMETHODIMP
michael@0 8688 nsHTMLEditRules::WillSplitNode(nsIDOMNode *aExistingRightNode, int32_t aOffset)
michael@0 8689 {
michael@0 8690 return NS_OK;
michael@0 8691 }
michael@0 8692
michael@0 8693
michael@0 8694 NS_IMETHODIMP
michael@0 8695 nsHTMLEditRules::DidSplitNode(nsIDOMNode *aExistingRightNode,
michael@0 8696 int32_t aOffset,
michael@0 8697 nsIDOMNode *aNewLeftNode,
michael@0 8698 nsresult aResult)
michael@0 8699 {
michael@0 8700 if (!mListenerEnabled) {
michael@0 8701 return NS_OK;
michael@0 8702 }
michael@0 8703 nsresult res = mUtilRange->SetStart(aNewLeftNode, 0);
michael@0 8704 NS_ENSURE_SUCCESS(res, res);
michael@0 8705 res = mUtilRange->SetEnd(aExistingRightNode, 0);
michael@0 8706 NS_ENSURE_SUCCESS(res, res);
michael@0 8707 res = UpdateDocChangeRange(mUtilRange);
michael@0 8708 return res;
michael@0 8709 }
michael@0 8710
michael@0 8711
michael@0 8712 NS_IMETHODIMP
michael@0 8713 nsHTMLEditRules::WillJoinNodes(nsIDOMNode *aLeftNode, nsIDOMNode *aRightNode, nsIDOMNode *aParent)
michael@0 8714 {
michael@0 8715 if (!mListenerEnabled) {
michael@0 8716 return NS_OK;
michael@0 8717 }
michael@0 8718 // remember split point
michael@0 8719 nsresult res = nsEditor::GetLengthOfDOMNode(aLeftNode, mJoinOffset);
michael@0 8720 return res;
michael@0 8721 }
michael@0 8722
michael@0 8723
michael@0 8724 NS_IMETHODIMP
michael@0 8725 nsHTMLEditRules::DidJoinNodes(nsIDOMNode *aLeftNode,
michael@0 8726 nsIDOMNode *aRightNode,
michael@0 8727 nsIDOMNode *aParent,
michael@0 8728 nsresult aResult)
michael@0 8729 {
michael@0 8730 if (!mListenerEnabled) {
michael@0 8731 return NS_OK;
michael@0 8732 }
michael@0 8733 // assumption that Join keeps the righthand node
michael@0 8734 nsresult res = mUtilRange->SetStart(aRightNode, mJoinOffset);
michael@0 8735 NS_ENSURE_SUCCESS(res, res);
michael@0 8736 res = mUtilRange->SetEnd(aRightNode, mJoinOffset);
michael@0 8737 NS_ENSURE_SUCCESS(res, res);
michael@0 8738 res = UpdateDocChangeRange(mUtilRange);
michael@0 8739 return res;
michael@0 8740 }
michael@0 8741
michael@0 8742
michael@0 8743 NS_IMETHODIMP
michael@0 8744 nsHTMLEditRules::WillInsertText(nsIDOMCharacterData *aTextNode, int32_t aOffset, const nsAString &aString)
michael@0 8745 {
michael@0 8746 return NS_OK;
michael@0 8747 }
michael@0 8748
michael@0 8749
michael@0 8750 NS_IMETHODIMP
michael@0 8751 nsHTMLEditRules::DidInsertText(nsIDOMCharacterData *aTextNode,
michael@0 8752 int32_t aOffset,
michael@0 8753 const nsAString &aString,
michael@0 8754 nsresult aResult)
michael@0 8755 {
michael@0 8756 if (!mListenerEnabled) {
michael@0 8757 return NS_OK;
michael@0 8758 }
michael@0 8759 int32_t length = aString.Length();
michael@0 8760 nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(aTextNode);
michael@0 8761 nsresult res = mUtilRange->SetStart(theNode, aOffset);
michael@0 8762 NS_ENSURE_SUCCESS(res, res);
michael@0 8763 res = mUtilRange->SetEnd(theNode, aOffset+length);
michael@0 8764 NS_ENSURE_SUCCESS(res, res);
michael@0 8765 res = UpdateDocChangeRange(mUtilRange);
michael@0 8766 return res;
michael@0 8767 }
michael@0 8768
michael@0 8769
michael@0 8770 NS_IMETHODIMP
michael@0 8771 nsHTMLEditRules::WillDeleteText(nsIDOMCharacterData *aTextNode, int32_t aOffset, int32_t aLength)
michael@0 8772 {
michael@0 8773 return NS_OK;
michael@0 8774 }
michael@0 8775
michael@0 8776
michael@0 8777 NS_IMETHODIMP
michael@0 8778 nsHTMLEditRules::DidDeleteText(nsIDOMCharacterData *aTextNode,
michael@0 8779 int32_t aOffset,
michael@0 8780 int32_t aLength,
michael@0 8781 nsresult aResult)
michael@0 8782 {
michael@0 8783 if (!mListenerEnabled) {
michael@0 8784 return NS_OK;
michael@0 8785 }
michael@0 8786 nsCOMPtr<nsIDOMNode> theNode = do_QueryInterface(aTextNode);
michael@0 8787 nsresult res = mUtilRange->SetStart(theNode, aOffset);
michael@0 8788 NS_ENSURE_SUCCESS(res, res);
michael@0 8789 res = mUtilRange->SetEnd(theNode, aOffset);
michael@0 8790 NS_ENSURE_SUCCESS(res, res);
michael@0 8791 res = UpdateDocChangeRange(mUtilRange);
michael@0 8792 return res;
michael@0 8793 }
michael@0 8794
michael@0 8795 NS_IMETHODIMP
michael@0 8796 nsHTMLEditRules::WillDeleteSelection(nsISelection *aSelection)
michael@0 8797 {
michael@0 8798 if (!mListenerEnabled) {
michael@0 8799 return NS_OK;
michael@0 8800 }
michael@0 8801 // get the (collapsed) selection location
michael@0 8802 nsCOMPtr<nsIDOMNode> selNode;
michael@0 8803 int32_t selOffset;
michael@0 8804
michael@0 8805 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8806 nsresult res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
michael@0 8807 NS_ENSURE_SUCCESS(res, res);
michael@0 8808 res = mUtilRange->SetStart(selNode, selOffset);
michael@0 8809 NS_ENSURE_SUCCESS(res, res);
michael@0 8810 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8811 res = mHTMLEditor->GetEndNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
michael@0 8812 NS_ENSURE_SUCCESS(res, res);
michael@0 8813 res = mUtilRange->SetEnd(selNode, selOffset);
michael@0 8814 NS_ENSURE_SUCCESS(res, res);
michael@0 8815 res = UpdateDocChangeRange(mUtilRange);
michael@0 8816 return res;
michael@0 8817 }
michael@0 8818
michael@0 8819 NS_IMETHODIMP
michael@0 8820 nsHTMLEditRules::DidDeleteSelection(nsISelection *aSelection)
michael@0 8821 {
michael@0 8822 return NS_OK;
michael@0 8823 }
michael@0 8824
michael@0 8825 // Let's remove all alignment hints in the children of aNode; it can
michael@0 8826 // be an ALIGN attribute (in case we just remove it) or a CENTER
michael@0 8827 // element (here we have to remove the container and keep its
michael@0 8828 // children). We break on tables and don't look at their children.
michael@0 8829 nsresult
michael@0 8830 nsHTMLEditRules::RemoveAlignment(nsIDOMNode * aNode, const nsAString & aAlignType, bool aChildrenOnly)
michael@0 8831 {
michael@0 8832 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
michael@0 8833
michael@0 8834 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8835 if (mHTMLEditor->IsTextNode(aNode) || nsHTMLEditUtils::IsTable(aNode)) return NS_OK;
michael@0 8836 nsresult res = NS_OK;
michael@0 8837
michael@0 8838 nsCOMPtr<nsIDOMNode> child = aNode,tmp;
michael@0 8839 if (aChildrenOnly)
michael@0 8840 {
michael@0 8841 aNode->GetFirstChild(getter_AddRefs(child));
michael@0 8842 }
michael@0 8843 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8844 bool useCSS = mHTMLEditor->IsCSSEnabled();
michael@0 8845
michael@0 8846 while (child)
michael@0 8847 {
michael@0 8848 if (aChildrenOnly) {
michael@0 8849 // get the next sibling right now because we could have to remove child
michael@0 8850 child->GetNextSibling(getter_AddRefs(tmp));
michael@0 8851 }
michael@0 8852 else
michael@0 8853 {
michael@0 8854 tmp = nullptr;
michael@0 8855 }
michael@0 8856 bool isBlock;
michael@0 8857 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8858 res = mHTMLEditor->NodeIsBlockStatic(child, &isBlock);
michael@0 8859 NS_ENSURE_SUCCESS(res, res);
michael@0 8860
michael@0 8861 if (nsEditor::NodeIsType(child, nsEditProperty::center))
michael@0 8862 {
michael@0 8863 // the current node is a CENTER element
michael@0 8864 // first remove children's alignment
michael@0 8865 res = RemoveAlignment(child, aAlignType, true);
michael@0 8866 NS_ENSURE_SUCCESS(res, res);
michael@0 8867
michael@0 8868 // we may have to insert BRs in first and last position of element's children
michael@0 8869 // if the nodes before/after are not blocks and not BRs
michael@0 8870 res = MakeSureElemStartsOrEndsOnCR(child);
michael@0 8871 NS_ENSURE_SUCCESS(res, res);
michael@0 8872
michael@0 8873 // now remove the CENTER container
michael@0 8874 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8875 res = mHTMLEditor->RemoveContainer(child);
michael@0 8876 NS_ENSURE_SUCCESS(res, res);
michael@0 8877 }
michael@0 8878 else if (isBlock || nsHTMLEditUtils::IsHR(child))
michael@0 8879 {
michael@0 8880 // the current node is a block element
michael@0 8881 nsCOMPtr<nsIDOMElement> curElem = do_QueryInterface(child);
michael@0 8882 if (nsHTMLEditUtils::SupportsAlignAttr(child))
michael@0 8883 {
michael@0 8884 // remove the ALIGN attribute if this element can have it
michael@0 8885 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8886 res = mHTMLEditor->RemoveAttribute(curElem, NS_LITERAL_STRING("align"));
michael@0 8887 NS_ENSURE_SUCCESS(res, res);
michael@0 8888 }
michael@0 8889 if (useCSS)
michael@0 8890 {
michael@0 8891 if (nsHTMLEditUtils::IsTable(child) || nsHTMLEditUtils::IsHR(child))
michael@0 8892 {
michael@0 8893 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8894 res = mHTMLEditor->SetAttributeOrEquivalent(curElem, NS_LITERAL_STRING("align"), aAlignType, false);
michael@0 8895 }
michael@0 8896 else
michael@0 8897 {
michael@0 8898 nsAutoString dummyCssValue;
michael@0 8899 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8900 res = mHTMLEditor->mHTMLCSSUtils->RemoveCSSInlineStyle(child, nsEditProperty::cssTextAlign, dummyCssValue);
michael@0 8901 }
michael@0 8902 NS_ENSURE_SUCCESS(res, res);
michael@0 8903 }
michael@0 8904 if (!nsHTMLEditUtils::IsTable(child))
michael@0 8905 {
michael@0 8906 // unless this is a table, look at children
michael@0 8907 res = RemoveAlignment(child, aAlignType, true);
michael@0 8908 NS_ENSURE_SUCCESS(res, res);
michael@0 8909 }
michael@0 8910 }
michael@0 8911 child = tmp;
michael@0 8912 }
michael@0 8913 return NS_OK;
michael@0 8914 }
michael@0 8915
michael@0 8916 // Let's insert a BR as first (resp. last) child of aNode if its
michael@0 8917 // first (resp. last) child is not a block nor a BR, and if the
michael@0 8918 // previous (resp. next) sibling is not a block nor a BR
michael@0 8919 nsresult
michael@0 8920 nsHTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode *aNode, bool aStarts)
michael@0 8921 {
michael@0 8922 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
michael@0 8923
michael@0 8924 nsCOMPtr<nsIDOMNode> child;
michael@0 8925 nsresult res;
michael@0 8926 if (aStarts)
michael@0 8927 {
michael@0 8928 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8929 res = mHTMLEditor->GetFirstEditableChild(aNode, address_of(child));
michael@0 8930 }
michael@0 8931 else
michael@0 8932 {
michael@0 8933 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8934 res = mHTMLEditor->GetLastEditableChild(aNode, address_of(child));
michael@0 8935 }
michael@0 8936 NS_ENSURE_SUCCESS(res, res);
michael@0 8937 NS_ENSURE_TRUE(child, NS_OK);
michael@0 8938 bool isChildBlock;
michael@0 8939 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8940 res = mHTMLEditor->NodeIsBlockStatic(child, &isChildBlock);
michael@0 8941 NS_ENSURE_SUCCESS(res, res);
michael@0 8942 bool foundCR = false;
michael@0 8943 if (isChildBlock || nsTextEditUtils::IsBreak(child))
michael@0 8944 {
michael@0 8945 foundCR = true;
michael@0 8946 }
michael@0 8947 else
michael@0 8948 {
michael@0 8949 nsCOMPtr<nsIDOMNode> sibling;
michael@0 8950 if (aStarts)
michael@0 8951 {
michael@0 8952 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8953 res = mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling));
michael@0 8954 }
michael@0 8955 else
michael@0 8956 {
michael@0 8957 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8958 res = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling));
michael@0 8959 }
michael@0 8960 NS_ENSURE_SUCCESS(res, res);
michael@0 8961 if (sibling)
michael@0 8962 {
michael@0 8963 bool isBlock;
michael@0 8964 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8965 res = mHTMLEditor->NodeIsBlockStatic(sibling, &isBlock);
michael@0 8966 NS_ENSURE_SUCCESS(res, res);
michael@0 8967 if (isBlock || nsTextEditUtils::IsBreak(sibling))
michael@0 8968 {
michael@0 8969 foundCR = true;
michael@0 8970 }
michael@0 8971 }
michael@0 8972 else
michael@0 8973 {
michael@0 8974 foundCR = true;
michael@0 8975 }
michael@0 8976 }
michael@0 8977 if (!foundCR)
michael@0 8978 {
michael@0 8979 int32_t offset = 0;
michael@0 8980 if (!aStarts) {
michael@0 8981 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
michael@0 8982 NS_ENSURE_STATE(node);
michael@0 8983 offset = node->GetChildCount();
michael@0 8984 }
michael@0 8985 nsCOMPtr<nsIDOMNode> brNode;
michael@0 8986 NS_ENSURE_STATE(mHTMLEditor);
michael@0 8987 res = mHTMLEditor->CreateBR(aNode, offset, address_of(brNode));
michael@0 8988 NS_ENSURE_SUCCESS(res, res);
michael@0 8989 }
michael@0 8990 return NS_OK;
michael@0 8991 }
michael@0 8992
michael@0 8993 nsresult
michael@0 8994 nsHTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode *aNode)
michael@0 8995 {
michael@0 8996 nsresult res = MakeSureElemStartsOrEndsOnCR(aNode, false);
michael@0 8997 NS_ENSURE_SUCCESS(res, res);
michael@0 8998 res = MakeSureElemStartsOrEndsOnCR(aNode, true);
michael@0 8999 return res;
michael@0 9000 }
michael@0 9001
michael@0 9002 nsresult
michael@0 9003 nsHTMLEditRules::AlignBlock(nsIDOMElement * aElement, const nsAString * aAlignType, bool aContentsOnly)
michael@0 9004 {
michael@0 9005 NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
michael@0 9006
michael@0 9007 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
michael@0 9008 bool isBlock = IsBlockNode(node);
michael@0 9009 if (!isBlock && !nsHTMLEditUtils::IsHR(node)) {
michael@0 9010 // we deal only with blocks; early way out
michael@0 9011 return NS_OK;
michael@0 9012 }
michael@0 9013
michael@0 9014 nsresult res = RemoveAlignment(node, *aAlignType, aContentsOnly);
michael@0 9015 NS_ENSURE_SUCCESS(res, res);
michael@0 9016 NS_NAMED_LITERAL_STRING(attr, "align");
michael@0 9017 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9018 if (mHTMLEditor->IsCSSEnabled()) {
michael@0 9019 // let's use CSS alignment; we use margin-left and margin-right for tables
michael@0 9020 // and text-align for other block-level elements
michael@0 9021 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9022 res = mHTMLEditor->SetAttributeOrEquivalent(aElement, attr, *aAlignType, false);
michael@0 9023 NS_ENSURE_SUCCESS(res, res);
michael@0 9024 }
michael@0 9025 else {
michael@0 9026 // HTML case; this code is supposed to be called ONLY if the element
michael@0 9027 // supports the align attribute but we'll never know...
michael@0 9028 if (nsHTMLEditUtils::SupportsAlignAttr(node)) {
michael@0 9029 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9030 res = mHTMLEditor->SetAttribute(aElement, attr, *aAlignType);
michael@0 9031 NS_ENSURE_SUCCESS(res, res);
michael@0 9032 }
michael@0 9033 }
michael@0 9034 return NS_OK;
michael@0 9035 }
michael@0 9036
michael@0 9037 nsresult
michael@0 9038 nsHTMLEditRules::RelativeChangeIndentationOfElementNode(nsIDOMNode *aNode, int8_t aRelativeChange)
michael@0 9039 {
michael@0 9040 NS_ENSURE_ARG_POINTER(aNode);
michael@0 9041
michael@0 9042 if (aRelativeChange != 1 && aRelativeChange != -1) {
michael@0 9043 return NS_ERROR_ILLEGAL_VALUE;
michael@0 9044 }
michael@0 9045
michael@0 9046 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
michael@0 9047 if (!element) {
michael@0 9048 return NS_OK;
michael@0 9049 }
michael@0 9050
michael@0 9051 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9052 nsIAtom* marginProperty = MarginPropertyAtomForIndent(mHTMLEditor->mHTMLCSSUtils, element);
michael@0 9053 nsAutoString value;
michael@0 9054 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9055 mHTMLEditor->mHTMLCSSUtils->GetSpecifiedProperty(aNode, marginProperty, value);
michael@0 9056 float f;
michael@0 9057 nsCOMPtr<nsIAtom> unit;
michael@0 9058 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9059 mHTMLEditor->mHTMLCSSUtils->ParseLength(value, &f, getter_AddRefs(unit));
michael@0 9060 if (0 == f) {
michael@0 9061 nsAutoString defaultLengthUnit;
michael@0 9062 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9063 mHTMLEditor->mHTMLCSSUtils->GetDefaultLengthUnit(defaultLengthUnit);
michael@0 9064 unit = do_GetAtom(defaultLengthUnit);
michael@0 9065 }
michael@0 9066 if (nsEditProperty::cssInUnit == unit)
michael@0 9067 f += NS_EDITOR_INDENT_INCREMENT_IN * aRelativeChange;
michael@0 9068 else if (nsEditProperty::cssCmUnit == unit)
michael@0 9069 f += NS_EDITOR_INDENT_INCREMENT_CM * aRelativeChange;
michael@0 9070 else if (nsEditProperty::cssMmUnit == unit)
michael@0 9071 f += NS_EDITOR_INDENT_INCREMENT_MM * aRelativeChange;
michael@0 9072 else if (nsEditProperty::cssPtUnit == unit)
michael@0 9073 f += NS_EDITOR_INDENT_INCREMENT_PT * aRelativeChange;
michael@0 9074 else if (nsEditProperty::cssPcUnit == unit)
michael@0 9075 f += NS_EDITOR_INDENT_INCREMENT_PC * aRelativeChange;
michael@0 9076 else if (nsEditProperty::cssEmUnit == unit)
michael@0 9077 f += NS_EDITOR_INDENT_INCREMENT_EM * aRelativeChange;
michael@0 9078 else if (nsEditProperty::cssExUnit == unit)
michael@0 9079 f += NS_EDITOR_INDENT_INCREMENT_EX * aRelativeChange;
michael@0 9080 else if (nsEditProperty::cssPxUnit == unit)
michael@0 9081 f += NS_EDITOR_INDENT_INCREMENT_PX * aRelativeChange;
michael@0 9082 else if (nsEditProperty::cssPercentUnit == unit)
michael@0 9083 f += NS_EDITOR_INDENT_INCREMENT_PERCENT * aRelativeChange;
michael@0 9084
michael@0 9085 if (0 < f) {
michael@0 9086 nsAutoString newValue;
michael@0 9087 newValue.AppendFloat(f);
michael@0 9088 newValue.Append(nsDependentAtomString(unit));
michael@0 9089 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9090 mHTMLEditor->mHTMLCSSUtils->SetCSSProperty(element, marginProperty, newValue, false);
michael@0 9091 return NS_OK;
michael@0 9092 }
michael@0 9093
michael@0 9094 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9095 mHTMLEditor->mHTMLCSSUtils->RemoveCSSProperty(element, marginProperty, value, false);
michael@0 9096
michael@0 9097 // remove unnecessary DIV blocks:
michael@0 9098 // we could skip this section but that would cause a FAIL in
michael@0 9099 // editor/libeditor/html/tests/browserscope/richtext.html, which expects
michael@0 9100 // to unapply a CSS "indent" (<div style="margin-left: 40px;">) by
michael@0 9101 // removing the DIV container instead of just removing the CSS property.
michael@0 9102 nsCOMPtr<dom::Element> node = do_QueryInterface(aNode);
michael@0 9103 if (!node || !node->IsHTML(nsGkAtoms::div) ||
michael@0 9104 !mHTMLEditor ||
michael@0 9105 node == mHTMLEditor->GetActiveEditingHost() ||
michael@0 9106 !mHTMLEditor->IsDescendantOfEditorRoot(node) ||
michael@0 9107 nsHTMLEditor::HasAttributes(node)) {
michael@0 9108 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9109 return NS_OK;
michael@0 9110 }
michael@0 9111
michael@0 9112 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9113 return mHTMLEditor->RemoveContainer(element);
michael@0 9114 }
michael@0 9115
michael@0 9116 //
michael@0 9117 // Support for Absolute Positioning
michael@0 9118 //
michael@0 9119
michael@0 9120 nsresult
michael@0 9121 nsHTMLEditRules::WillAbsolutePosition(Selection* aSelection,
michael@0 9122 bool* aCancel, bool* aHandled)
michael@0 9123 {
michael@0 9124 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 9125 nsresult res = WillInsert(aSelection, aCancel);
michael@0 9126 NS_ENSURE_SUCCESS(res, res);
michael@0 9127
michael@0 9128 // initialize out param
michael@0 9129 // we want to ignore result of WillInsert()
michael@0 9130 *aCancel = false;
michael@0 9131 *aHandled = true;
michael@0 9132
michael@0 9133 nsCOMPtr<nsIDOMElement> focusElement;
michael@0 9134 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9135 res = mHTMLEditor->GetSelectionContainer(getter_AddRefs(focusElement));
michael@0 9136 if (focusElement) {
michael@0 9137 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(focusElement);
michael@0 9138 if (nsHTMLEditUtils::IsImage(node)) {
michael@0 9139 mNewBlock = node;
michael@0 9140 return NS_OK;
michael@0 9141 }
michael@0 9142 }
michael@0 9143
michael@0 9144 res = NormalizeSelection(aSelection);
michael@0 9145 NS_ENSURE_SUCCESS(res, res);
michael@0 9146 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9147 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
michael@0 9148
michael@0 9149 // convert the selection ranges into "promoted" selection ranges:
michael@0 9150 // this basically just expands the range to include the immediate
michael@0 9151 // block parent, and then further expands to include any ancestors
michael@0 9152 // whose children are all in the range
michael@0 9153
michael@0 9154 nsCOMArray<nsIDOMRange> arrayOfRanges;
michael@0 9155 res = GetPromotedRanges(aSelection, arrayOfRanges,
michael@0 9156 EditAction::setAbsolutePosition);
michael@0 9157 NS_ENSURE_SUCCESS(res, res);
michael@0 9158
michael@0 9159 // use these ranges to contruct a list of nodes to act on.
michael@0 9160 nsCOMArray<nsIDOMNode> arrayOfNodes;
michael@0 9161 res = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
michael@0 9162 EditAction::setAbsolutePosition);
michael@0 9163 NS_ENSURE_SUCCESS(res, res);
michael@0 9164
michael@0 9165 NS_NAMED_LITERAL_STRING(divType, "div");
michael@0 9166
michael@0 9167
michael@0 9168 // if nothing visible in list, make an empty block
michael@0 9169 if (ListIsEmptyLine(arrayOfNodes))
michael@0 9170 {
michael@0 9171 nsCOMPtr<nsIDOMNode> parent, thePositionedDiv;
michael@0 9172 int32_t offset;
michael@0 9173
michael@0 9174 // get selection location
michael@0 9175 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9176 res = mHTMLEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(parent), &offset);
michael@0 9177 NS_ENSURE_SUCCESS(res, res);
michael@0 9178 // make sure we can put a block here
michael@0 9179 res = SplitAsNeeded(&divType, address_of(parent), &offset);
michael@0 9180 NS_ENSURE_SUCCESS(res, res);
michael@0 9181 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9182 res = mHTMLEditor->CreateNode(divType, parent, offset, getter_AddRefs(thePositionedDiv));
michael@0 9183 NS_ENSURE_SUCCESS(res, res);
michael@0 9184 // remember our new block for postprocessing
michael@0 9185 mNewBlock = thePositionedDiv;
michael@0 9186 // delete anything that was in the list of nodes
michael@0 9187 for (int32_t j = arrayOfNodes.Count() - 1; j >= 0; --j)
michael@0 9188 {
michael@0 9189 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[0];
michael@0 9190 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9191 res = mHTMLEditor->DeleteNode(curNode);
michael@0 9192 NS_ENSURE_SUCCESS(res, res);
michael@0 9193 arrayOfNodes.RemoveObjectAt(0);
michael@0 9194 }
michael@0 9195 // put selection in new block
michael@0 9196 res = aSelection->Collapse(thePositionedDiv,0);
michael@0 9197 selectionResetter.Abort(); // to prevent selection reseter from overriding us.
michael@0 9198 *aHandled = true;
michael@0 9199 return res;
michael@0 9200 }
michael@0 9201
michael@0 9202 // Ok, now go through all the nodes and put them in a blockquote,
michael@0 9203 // or whatever is appropriate. Wohoo!
michael@0 9204 int32_t i;
michael@0 9205 nsCOMPtr<nsIDOMNode> curParent, curPositionedDiv, curList, indentedLI, sibling;
michael@0 9206 int32_t listCount = arrayOfNodes.Count();
michael@0 9207 for (i=0; i<listCount; i++)
michael@0 9208 {
michael@0 9209 // here's where we actually figure out what to do
michael@0 9210 nsCOMPtr<nsIDOMNode> curNode = arrayOfNodes[i];
michael@0 9211
michael@0 9212 // Ignore all non-editable nodes. Leave them be.
michael@0 9213 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9214 if (!mHTMLEditor->IsEditable(curNode)) continue;
michael@0 9215
michael@0 9216 int32_t offset;
michael@0 9217 curParent = nsEditor::GetNodeLocation(curNode, &offset);
michael@0 9218
michael@0 9219 // some logic for putting list items into nested lists...
michael@0 9220 if (nsHTMLEditUtils::IsList(curParent))
michael@0 9221 {
michael@0 9222 // check to see if curList is still appropriate. Which it is if
michael@0 9223 // curNode is still right after it in the same list.
michael@0 9224 if (curList)
michael@0 9225 {
michael@0 9226 sibling = nullptr;
michael@0 9227 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9228 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
michael@0 9229 }
michael@0 9230
michael@0 9231 if (!curList || (sibling && sibling != curList) )
michael@0 9232 {
michael@0 9233 nsAutoString listTag;
michael@0 9234 nsEditor::GetTagString(curParent,listTag);
michael@0 9235 ToLowerCase(listTag);
michael@0 9236 // create a new nested list of correct type
michael@0 9237 res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
michael@0 9238 NS_ENSURE_SUCCESS(res, res);
michael@0 9239 if (!curPositionedDiv) {
michael@0 9240 int32_t parentOffset;
michael@0 9241 nsCOMPtr<nsIDOMNode> curParentParent = nsEditor::GetNodeLocation(curParent, &parentOffset);
michael@0 9242 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9243 res = mHTMLEditor->CreateNode(divType, curParentParent, parentOffset, getter_AddRefs(curPositionedDiv));
michael@0 9244 mNewBlock = curPositionedDiv;
michael@0 9245 }
michael@0 9246 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9247 res = mHTMLEditor->CreateNode(listTag, curPositionedDiv, -1, getter_AddRefs(curList));
michael@0 9248 NS_ENSURE_SUCCESS(res, res);
michael@0 9249 // curList is now the correct thing to put curNode in
michael@0 9250 // remember our new block for postprocessing
michael@0 9251 // mNewBlock = curList;
michael@0 9252 }
michael@0 9253 // tuck the node into the end of the active list
michael@0 9254 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9255 res = mHTMLEditor->MoveNode(curNode, curList, -1);
michael@0 9256 NS_ENSURE_SUCCESS(res, res);
michael@0 9257 // forget curPositionedDiv, if any
michael@0 9258 // curPositionedDiv = nullptr;
michael@0 9259 }
michael@0 9260
michael@0 9261 else // not a list item, use blockquote?
michael@0 9262 {
michael@0 9263 // if we are inside a list item, we don't want to blockquote, we want
michael@0 9264 // to sublist the list item. We may have several nodes listed in the
michael@0 9265 // array of nodes to act on, that are in the same list item. Since
michael@0 9266 // we only want to indent that li once, we must keep track of the most
michael@0 9267 // recent indented list item, and not indent it if we find another node
michael@0 9268 // to act on that is still inside the same li.
michael@0 9269 nsCOMPtr<nsIDOMNode> listitem=IsInListItem(curNode);
michael@0 9270 if (listitem)
michael@0 9271 {
michael@0 9272 if (indentedLI == listitem) continue; // already indented this list item
michael@0 9273 curParent = nsEditor::GetNodeLocation(listitem, &offset);
michael@0 9274 // check to see if curList is still appropriate. Which it is if
michael@0 9275 // curNode is still right after it in the same list.
michael@0 9276 if (curList)
michael@0 9277 {
michael@0 9278 sibling = nullptr;
michael@0 9279 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9280 mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
michael@0 9281 }
michael@0 9282
michael@0 9283 if (!curList || (sibling && sibling != curList) )
michael@0 9284 {
michael@0 9285 nsAutoString listTag;
michael@0 9286 nsEditor::GetTagString(curParent,listTag);
michael@0 9287 ToLowerCase(listTag);
michael@0 9288 // create a new nested list of correct type
michael@0 9289 res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
michael@0 9290 NS_ENSURE_SUCCESS(res, res);
michael@0 9291 if (!curPositionedDiv) {
michael@0 9292 int32_t parentOffset;
michael@0 9293 nsCOMPtr<nsIDOMNode> curParentParent = nsEditor::GetNodeLocation(curParent, &parentOffset);
michael@0 9294 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9295 res = mHTMLEditor->CreateNode(divType, curParentParent, parentOffset, getter_AddRefs(curPositionedDiv));
michael@0 9296 mNewBlock = curPositionedDiv;
michael@0 9297 }
michael@0 9298 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9299 res = mHTMLEditor->CreateNode(listTag, curPositionedDiv, -1, getter_AddRefs(curList));
michael@0 9300 NS_ENSURE_SUCCESS(res, res);
michael@0 9301 }
michael@0 9302 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9303 res = mHTMLEditor->MoveNode(listitem, curList, -1);
michael@0 9304 NS_ENSURE_SUCCESS(res, res);
michael@0 9305 // remember we indented this li
michael@0 9306 indentedLI = listitem;
michael@0 9307 }
michael@0 9308
michael@0 9309 else
michael@0 9310 {
michael@0 9311 // need to make a div to put things in if we haven't already
michael@0 9312
michael@0 9313 if (!curPositionedDiv)
michael@0 9314 {
michael@0 9315 if (nsHTMLEditUtils::IsDiv(curNode))
michael@0 9316 {
michael@0 9317 curPositionedDiv = curNode;
michael@0 9318 mNewBlock = curPositionedDiv;
michael@0 9319 curList = nullptr;
michael@0 9320 continue;
michael@0 9321 }
michael@0 9322 res = SplitAsNeeded(&divType, address_of(curParent), &offset);
michael@0 9323 NS_ENSURE_SUCCESS(res, res);
michael@0 9324 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9325 res = mHTMLEditor->CreateNode(divType, curParent, offset, getter_AddRefs(curPositionedDiv));
michael@0 9326 NS_ENSURE_SUCCESS(res, res);
michael@0 9327 // remember our new block for postprocessing
michael@0 9328 mNewBlock = curPositionedDiv;
michael@0 9329 // curPositionedDiv is now the correct thing to put curNode in
michael@0 9330 }
michael@0 9331
michael@0 9332 // tuck the node into the end of the active blockquote
michael@0 9333 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9334 res = mHTMLEditor->MoveNode(curNode, curPositionedDiv, -1);
michael@0 9335 NS_ENSURE_SUCCESS(res, res);
michael@0 9336 // forget curList, if any
michael@0 9337 curList = nullptr;
michael@0 9338 }
michael@0 9339 }
michael@0 9340 }
michael@0 9341 return res;
michael@0 9342 }
michael@0 9343
michael@0 9344 nsresult
michael@0 9345 nsHTMLEditRules::DidAbsolutePosition()
michael@0 9346 {
michael@0 9347 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9348 nsCOMPtr<nsIHTMLAbsPosEditor> absPosHTMLEditor = mHTMLEditor;
michael@0 9349 nsCOMPtr<nsIDOMElement> elt = do_QueryInterface(mNewBlock);
michael@0 9350 return absPosHTMLEditor->AbsolutelyPositionElement(elt, true);
michael@0 9351 }
michael@0 9352
michael@0 9353 nsresult
michael@0 9354 nsHTMLEditRules::WillRemoveAbsolutePosition(Selection* aSelection,
michael@0 9355 bool* aCancel, bool* aHandled) {
michael@0 9356 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 9357 nsresult res = WillInsert(aSelection, aCancel);
michael@0 9358 NS_ENSURE_SUCCESS(res, res);
michael@0 9359
michael@0 9360 // initialize out param
michael@0 9361 // we want to ignore aCancel from WillInsert()
michael@0 9362 *aCancel = false;
michael@0 9363 *aHandled = true;
michael@0 9364
michael@0 9365 nsCOMPtr<nsIDOMElement> elt;
michael@0 9366 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9367 res = mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt));
michael@0 9368 NS_ENSURE_SUCCESS(res, res);
michael@0 9369
michael@0 9370 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9371 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
michael@0 9372
michael@0 9373 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9374 nsCOMPtr<nsIHTMLAbsPosEditor> absPosHTMLEditor = mHTMLEditor;
michael@0 9375 return absPosHTMLEditor->AbsolutelyPositionElement(elt, false);
michael@0 9376 }
michael@0 9377
michael@0 9378 nsresult
michael@0 9379 nsHTMLEditRules::WillRelativeChangeZIndex(Selection* aSelection,
michael@0 9380 int32_t aChange,
michael@0 9381 bool *aCancel,
michael@0 9382 bool * aHandled)
michael@0 9383 {
michael@0 9384 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 9385 nsresult res = WillInsert(aSelection, aCancel);
michael@0 9386 NS_ENSURE_SUCCESS(res, res);
michael@0 9387
michael@0 9388 // initialize out param
michael@0 9389 // we want to ignore aCancel from WillInsert()
michael@0 9390 *aCancel = false;
michael@0 9391 *aHandled = true;
michael@0 9392
michael@0 9393 nsCOMPtr<nsIDOMElement> elt;
michael@0 9394 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9395 res = mHTMLEditor->GetAbsolutelyPositionedSelectionContainer(getter_AddRefs(elt));
michael@0 9396 NS_ENSURE_SUCCESS(res, res);
michael@0 9397
michael@0 9398 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9399 nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
michael@0 9400
michael@0 9401 NS_ENSURE_STATE(mHTMLEditor);
michael@0 9402 nsCOMPtr<nsIHTMLAbsPosEditor> absPosHTMLEditor = mHTMLEditor;
michael@0 9403 int32_t zIndex;
michael@0 9404 return absPosHTMLEditor->RelativeChangeElementZIndex(elt, aChange, &zIndex);
michael@0 9405 }
michael@0 9406
michael@0 9407 NS_IMETHODIMP
michael@0 9408 nsHTMLEditRules::DocumentModified()
michael@0 9409 {
michael@0 9410 nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, &nsHTMLEditRules::DocumentModifiedWorker));
michael@0 9411 return NS_OK;
michael@0 9412 }
michael@0 9413
michael@0 9414 void
michael@0 9415 nsHTMLEditRules::DocumentModifiedWorker()
michael@0 9416 {
michael@0 9417 if (!mHTMLEditor) {
michael@0 9418 return;
michael@0 9419 }
michael@0 9420
michael@0 9421 // DeleteNode below may cause a flush, which could destroy the editor
michael@0 9422 nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
michael@0 9423
michael@0 9424 nsCOMPtr<nsIHTMLEditor> kungFuDeathGrip(mHTMLEditor);
michael@0 9425 nsCOMPtr<nsISelection> selection;
michael@0 9426 nsresult rv = mHTMLEditor->GetSelection(getter_AddRefs(selection));
michael@0 9427 if (NS_FAILED(rv)) {
michael@0 9428 return;
michael@0 9429 }
michael@0 9430
michael@0 9431 // Delete our bogus node, if we have one, since the document might not be
michael@0 9432 // empty any more.
michael@0 9433 if (mBogusNode) {
michael@0 9434 mEditor->DeleteNode(mBogusNode);
michael@0 9435 mBogusNode = nullptr;
michael@0 9436 }
michael@0 9437
michael@0 9438 // Try to recreate the bogus node if needed.
michael@0 9439 CreateBogusNodeIfNeeded(selection);
michael@0 9440 }

mercurial