Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
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("eType, 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("eType, 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("eType, 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("eType, 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 | } |