editor/libeditor/html/nsHTMLDataTransfer.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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=78: */
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 <string.h>
michael@0 8
michael@0 9 #include "mozilla/dom/DocumentFragment.h"
michael@0 10 #include "mozilla/ArrayUtils.h"
michael@0 11 #include "mozilla/Base64.h"
michael@0 12 #include "mozilla/BasicEvents.h"
michael@0 13 #include "mozilla/Preferences.h"
michael@0 14 #include "mozilla/dom/Selection.h"
michael@0 15 #include "nsAString.h"
michael@0 16 #include "nsAutoPtr.h"
michael@0 17 #include "nsCOMArray.h"
michael@0 18 #include "nsCOMPtr.h"
michael@0 19 #include "nsCRT.h"
michael@0 20 #include "nsCRTGlue.h"
michael@0 21 #include "nsComponentManagerUtils.h"
michael@0 22 #include "nsContentUtils.h"
michael@0 23 #include "nsDebug.h"
michael@0 24 #include "nsDependentSubstring.h"
michael@0 25 #include "nsEditProperty.h"
michael@0 26 #include "nsEditRules.h"
michael@0 27 #include "nsEditor.h"
michael@0 28 #include "nsEditorUtils.h"
michael@0 29 #include "nsError.h"
michael@0 30 #include "nsGkAtoms.h"
michael@0 31 #include "nsHTMLEditUtils.h"
michael@0 32 #include "nsHTMLEditor.h"
michael@0 33 #include "nsIClipboard.h"
michael@0 34 #include "nsIContent.h"
michael@0 35 #include "nsIContentFilter.h"
michael@0 36 #include "nsIDOMComment.h"
michael@0 37 #include "mozilla/dom/DOMStringList.h"
michael@0 38 #include "mozilla/dom/DataTransfer.h"
michael@0 39 #include "nsIDOMDocument.h"
michael@0 40 #include "nsIDOMDocumentFragment.h"
michael@0 41 #include "nsIDOMElement.h"
michael@0 42 #include "nsIDOMHTMLAnchorElement.h"
michael@0 43 #include "nsIDOMHTMLEmbedElement.h"
michael@0 44 #include "nsIDOMHTMLFrameElement.h"
michael@0 45 #include "nsIDOMHTMLIFrameElement.h"
michael@0 46 #include "nsIDOMHTMLImageElement.h"
michael@0 47 #include "nsIDOMHTMLInputElement.h"
michael@0 48 #include "nsIDOMHTMLLinkElement.h"
michael@0 49 #include "nsIDOMHTMLObjectElement.h"
michael@0 50 #include "nsIDOMHTMLScriptElement.h"
michael@0 51 #include "nsIDOMNode.h"
michael@0 52 #include "nsIDOMRange.h"
michael@0 53 #include "nsIDocument.h"
michael@0 54 #include "nsIEditor.h"
michael@0 55 #include "nsIEditorIMESupport.h"
michael@0 56 #include "nsIEditorMailSupport.h"
michael@0 57 #include "nsIFile.h"
michael@0 58 #include "nsIInputStream.h"
michael@0 59 #include "nsIMIMEService.h"
michael@0 60 #include "nsNameSpaceManager.h"
michael@0 61 #include "nsINode.h"
michael@0 62 #include "nsIParserUtils.h"
michael@0 63 #include "nsIPlaintextEditor.h"
michael@0 64 #include "nsISelection.h"
michael@0 65 #include "nsISupportsImpl.h"
michael@0 66 #include "nsISupportsPrimitives.h"
michael@0 67 #include "nsISupportsUtils.h"
michael@0 68 #include "nsITransferable.h"
michael@0 69 #include "nsIURI.h"
michael@0 70 #include "nsIVariant.h"
michael@0 71 #include "nsLinebreakConverter.h"
michael@0 72 #include "nsLiteralString.h"
michael@0 73 #include "nsNetUtil.h"
michael@0 74 #include "nsPlaintextEditor.h"
michael@0 75 #include "nsRange.h"
michael@0 76 #include "nsReadableUtils.h"
michael@0 77 #include "nsSelectionState.h"
michael@0 78 #include "nsServiceManagerUtils.h"
michael@0 79 #include "nsStreamUtils.h"
michael@0 80 #include "nsString.h"
michael@0 81 #include "nsStringFwd.h"
michael@0 82 #include "nsStringIterator.h"
michael@0 83 #include "nsSubstringTuple.h"
michael@0 84 #include "nsTextEditRules.h"
michael@0 85 #include "nsTextEditUtils.h"
michael@0 86 #include "nsTreeSanitizer.h"
michael@0 87 #include "nsWSRunObject.h"
michael@0 88 #include "nsXPCOM.h"
michael@0 89 #include "nscore.h"
michael@0 90
michael@0 91 class nsIAtom;
michael@0 92 class nsILoadContext;
michael@0 93 class nsISupports;
michael@0 94
michael@0 95 using namespace mozilla;
michael@0 96 using namespace mozilla::dom;
michael@0 97
michael@0 98 #define kInsertCookie "_moz_Insert Here_moz_"
michael@0 99
michael@0 100 // some little helpers
michael@0 101 static bool FindIntegerAfterString(const char *aLeadingString,
michael@0 102 nsCString &aCStr, int32_t &foundNumber);
michael@0 103 static nsresult RemoveFragComments(nsCString &theStr);
michael@0 104 static void RemoveBodyAndHead(nsIDOMNode *aNode);
michael@0 105 static nsresult FindTargetNode(nsIDOMNode *aStart, nsCOMPtr<nsIDOMNode> &aResult);
michael@0 106
michael@0 107 static nsCOMPtr<nsIDOMNode> GetListParent(nsIDOMNode* aNode)
michael@0 108 {
michael@0 109 NS_ENSURE_TRUE(aNode, nullptr);
michael@0 110 nsCOMPtr<nsIDOMNode> parent, tmp;
michael@0 111 aNode->GetParentNode(getter_AddRefs(parent));
michael@0 112 while (parent)
michael@0 113 {
michael@0 114 if (nsHTMLEditUtils::IsList(parent)) {
michael@0 115 return parent;
michael@0 116 }
michael@0 117 parent->GetParentNode(getter_AddRefs(tmp));
michael@0 118 parent = tmp;
michael@0 119 }
michael@0 120 return nullptr;
michael@0 121 }
michael@0 122
michael@0 123 static nsCOMPtr<nsIDOMNode> GetTableParent(nsIDOMNode* aNode)
michael@0 124 {
michael@0 125 NS_ENSURE_TRUE(aNode, nullptr);
michael@0 126 nsCOMPtr<nsIDOMNode> parent, tmp;
michael@0 127 aNode->GetParentNode(getter_AddRefs(parent));
michael@0 128 while (parent)
michael@0 129 {
michael@0 130 if (nsHTMLEditUtils::IsTable(parent)) {
michael@0 131 return parent;
michael@0 132 }
michael@0 133 parent->GetParentNode(getter_AddRefs(tmp));
michael@0 134 parent = tmp;
michael@0 135 }
michael@0 136 return nullptr;
michael@0 137 }
michael@0 138
michael@0 139
michael@0 140 NS_IMETHODIMP nsHTMLEditor::LoadHTML(const nsAString & aInputString)
michael@0 141 {
michael@0 142 NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
michael@0 143
michael@0 144 // force IME commit; set up rules sniffing and batching
michael@0 145 ForceCompositionEnd();
michael@0 146 nsAutoEditBatch beginBatching(this);
michael@0 147 nsAutoRules beginRulesSniffing(this, EditAction::loadHTML, nsIEditor::eNext);
michael@0 148
michael@0 149 // Get selection
michael@0 150 nsRefPtr<Selection> selection = GetSelection();
michael@0 151 NS_ENSURE_STATE(selection);
michael@0 152
michael@0 153 nsTextRulesInfo ruleInfo(EditAction::loadHTML);
michael@0 154 bool cancel, handled;
michael@0 155 // Protect the edit rules object from dying
michael@0 156 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
michael@0 157 nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
michael@0 158 NS_ENSURE_SUCCESS(rv, rv);
michael@0 159 if (cancel) {
michael@0 160 return NS_OK; // rules canceled the operation
michael@0 161 }
michael@0 162
michael@0 163 if (!handled)
michael@0 164 {
michael@0 165 // Delete Selection, but only if it isn't collapsed, see bug #106269
michael@0 166 if (!selection->Collapsed()) {
michael@0 167 rv = DeleteSelection(eNone, eStrip);
michael@0 168 NS_ENSURE_SUCCESS(rv, rv);
michael@0 169 }
michael@0 170
michael@0 171 // Get the first range in the selection, for context:
michael@0 172 nsCOMPtr<nsIDOMRange> range;
michael@0 173 rv = selection->GetRangeAt(0, getter_AddRefs(range));
michael@0 174 NS_ENSURE_SUCCESS(rv, rv);
michael@0 175 NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
michael@0 176
michael@0 177 // create fragment for pasted html
michael@0 178 nsCOMPtr<nsIDOMDocumentFragment> docfrag;
michael@0 179 {
michael@0 180 rv = range->CreateContextualFragment(aInputString, getter_AddRefs(docfrag));
michael@0 181 NS_ENSURE_SUCCESS(rv, rv);
michael@0 182 }
michael@0 183 // put the fragment into the document
michael@0 184 nsCOMPtr<nsIDOMNode> parent, junk;
michael@0 185 rv = range->GetStartContainer(getter_AddRefs(parent));
michael@0 186 NS_ENSURE_SUCCESS(rv, rv);
michael@0 187 NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
michael@0 188 int32_t childOffset;
michael@0 189 rv = range->GetStartOffset(&childOffset);
michael@0 190 NS_ENSURE_SUCCESS(rv, rv);
michael@0 191
michael@0 192 nsCOMPtr<nsIDOMNode> nodeToInsert;
michael@0 193 docfrag->GetFirstChild(getter_AddRefs(nodeToInsert));
michael@0 194 while (nodeToInsert)
michael@0 195 {
michael@0 196 rv = InsertNode(nodeToInsert, parent, childOffset++);
michael@0 197 NS_ENSURE_SUCCESS(rv, rv);
michael@0 198 docfrag->GetFirstChild(getter_AddRefs(nodeToInsert));
michael@0 199 }
michael@0 200 }
michael@0 201
michael@0 202 return mRules->DidDoAction(selection, &ruleInfo, rv);
michael@0 203 }
michael@0 204
michael@0 205
michael@0 206 NS_IMETHODIMP nsHTMLEditor::InsertHTML(const nsAString & aInString)
michael@0 207 {
michael@0 208 const nsAFlatString& empty = EmptyString();
michael@0 209
michael@0 210 return InsertHTMLWithContext(aInString, empty, empty, empty,
michael@0 211 nullptr, nullptr, 0, true);
michael@0 212 }
michael@0 213
michael@0 214
michael@0 215 nsresult
michael@0 216 nsHTMLEditor::InsertHTMLWithContext(const nsAString & aInputString,
michael@0 217 const nsAString & aContextStr,
michael@0 218 const nsAString & aInfoStr,
michael@0 219 const nsAString & aFlavor,
michael@0 220 nsIDOMDocument *aSourceDoc,
michael@0 221 nsIDOMNode *aDestNode,
michael@0 222 int32_t aDestOffset,
michael@0 223 bool aDeleteSelection)
michael@0 224 {
michael@0 225 return DoInsertHTMLWithContext(aInputString, aContextStr, aInfoStr,
michael@0 226 aFlavor, aSourceDoc, aDestNode, aDestOffset, aDeleteSelection,
michael@0 227 /* trusted input */ true, /* clear style */ false);
michael@0 228 }
michael@0 229
michael@0 230 nsresult
michael@0 231 nsHTMLEditor::DoInsertHTMLWithContext(const nsAString & aInputString,
michael@0 232 const nsAString & aContextStr,
michael@0 233 const nsAString & aInfoStr,
michael@0 234 const nsAString & aFlavor,
michael@0 235 nsIDOMDocument *aSourceDoc,
michael@0 236 nsIDOMNode *aDestNode,
michael@0 237 int32_t aDestOffset,
michael@0 238 bool aDeleteSelection,
michael@0 239 bool aTrustedInput,
michael@0 240 bool aClearStyle)
michael@0 241 {
michael@0 242 NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
michael@0 243
michael@0 244 // Prevent the edit rules object from dying
michael@0 245 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
michael@0 246
michael@0 247 // force IME commit; set up rules sniffing and batching
michael@0 248 ForceCompositionEnd();
michael@0 249 nsAutoEditBatch beginBatching(this);
michael@0 250 nsAutoRules beginRulesSniffing(this, EditAction::htmlPaste, nsIEditor::eNext);
michael@0 251
michael@0 252 // Get selection
michael@0 253 nsRefPtr<Selection> selection = GetSelection();
michael@0 254 NS_ENSURE_STATE(selection);
michael@0 255
michael@0 256 // create a dom document fragment that represents the structure to paste
michael@0 257 nsCOMPtr<nsIDOMNode> fragmentAsNode, streamStartParent, streamEndParent;
michael@0 258 int32_t streamStartOffset = 0, streamEndOffset = 0;
michael@0 259
michael@0 260 nsresult rv = CreateDOMFragmentFromPaste(aInputString, aContextStr, aInfoStr,
michael@0 261 address_of(fragmentAsNode),
michael@0 262 address_of(streamStartParent),
michael@0 263 address_of(streamEndParent),
michael@0 264 &streamStartOffset,
michael@0 265 &streamEndOffset,
michael@0 266 aTrustedInput);
michael@0 267 NS_ENSURE_SUCCESS(rv, rv);
michael@0 268
michael@0 269 nsCOMPtr<nsIDOMNode> targetNode, tempNode;
michael@0 270 int32_t targetOffset=0;
michael@0 271
michael@0 272 if (!aDestNode)
michael@0 273 {
michael@0 274 // if caller didn't provide the destination/target node,
michael@0 275 // fetch the paste insertion point from our selection
michael@0 276 rv = GetStartNodeAndOffset(selection, getter_AddRefs(targetNode), &targetOffset);
michael@0 277 NS_ENSURE_SUCCESS(rv, rv);
michael@0 278 if (!targetNode || !IsEditable(targetNode)) {
michael@0 279 return NS_ERROR_FAILURE;
michael@0 280 }
michael@0 281 }
michael@0 282 else
michael@0 283 {
michael@0 284 targetNode = aDestNode;
michael@0 285 targetOffset = aDestOffset;
michael@0 286 }
michael@0 287
michael@0 288 bool doContinue = true;
michael@0 289
michael@0 290 rv = DoContentFilterCallback(aFlavor, aSourceDoc, aDeleteSelection,
michael@0 291 (nsIDOMNode **)address_of(fragmentAsNode),
michael@0 292 (nsIDOMNode **)address_of(streamStartParent),
michael@0 293 &streamStartOffset,
michael@0 294 (nsIDOMNode **)address_of(streamEndParent),
michael@0 295 &streamEndOffset,
michael@0 296 (nsIDOMNode **)address_of(targetNode),
michael@0 297 &targetOffset, &doContinue);
michael@0 298
michael@0 299 NS_ENSURE_SUCCESS(rv, rv);
michael@0 300 NS_ENSURE_TRUE(doContinue, NS_OK);
michael@0 301
michael@0 302 // if we have a destination / target node, we want to insert there
michael@0 303 // rather than in place of the selection
michael@0 304 // ignore aDeleteSelection here if no aDestNode since deletion will
michael@0 305 // also occur later; this block is intended to cover the various
michael@0 306 // scenarios where we are dropping in an editor (and may want to delete
michael@0 307 // the selection before collapsing the selection in the new destination)
michael@0 308 if (aDestNode)
michael@0 309 {
michael@0 310 if (aDeleteSelection)
michael@0 311 {
michael@0 312 // Use an auto tracker so that our drop point is correctly
michael@0 313 // positioned after the delete.
michael@0 314 nsAutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
michael@0 315 rv = DeleteSelection(eNone, eStrip);
michael@0 316 NS_ENSURE_SUCCESS(rv, rv);
michael@0 317 }
michael@0 318
michael@0 319 rv = selection->Collapse(targetNode, targetOffset);
michael@0 320 NS_ENSURE_SUCCESS(rv, rv);
michael@0 321 }
michael@0 322
michael@0 323 // we need to recalculate various things based on potentially new offsets
michael@0 324 // this is work to be completed at a later date (probably by jfrancis)
michael@0 325
michael@0 326 // make a list of what nodes in docFrag we need to move
michael@0 327 nsCOMArray<nsIDOMNode> nodeList;
michael@0 328 rv = CreateListOfNodesToPaste(fragmentAsNode, nodeList,
michael@0 329 streamStartParent, streamStartOffset,
michael@0 330 streamEndParent, streamEndOffset);
michael@0 331 NS_ENSURE_SUCCESS(rv, rv);
michael@0 332
michael@0 333 if (nodeList.Count() == 0) {
michael@0 334 return NS_OK;
michael@0 335 }
michael@0 336
michael@0 337 // Are there any table elements in the list?
michael@0 338 // node and offset for insertion
michael@0 339 nsCOMPtr<nsIDOMNode> parentNode;
michael@0 340 int32_t offsetOfNewNode;
michael@0 341
michael@0 342 // check for table cell selection mode
michael@0 343 bool cellSelectionMode = false;
michael@0 344 nsCOMPtr<nsIDOMElement> cell;
michael@0 345 rv = GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
michael@0 346 if (NS_SUCCEEDED(rv) && cell)
michael@0 347 {
michael@0 348 cellSelectionMode = true;
michael@0 349 }
michael@0 350
michael@0 351 if (cellSelectionMode)
michael@0 352 {
michael@0 353 // do we have table content to paste? If so, we want to delete
michael@0 354 // the selected table cells and replace with new table elements;
michael@0 355 // but if not we want to delete _contents_ of cells and replace
michael@0 356 // with non-table elements. Use cellSelectionMode bool to
michael@0 357 // indicate results.
michael@0 358 nsIDOMNode* firstNode = nodeList[0];
michael@0 359 if (!nsHTMLEditUtils::IsTableElement(firstNode))
michael@0 360 cellSelectionMode = false;
michael@0 361 }
michael@0 362
michael@0 363 if (!cellSelectionMode)
michael@0 364 {
michael@0 365 rv = DeleteSelectionAndPrepareToCreateNode();
michael@0 366 NS_ENSURE_SUCCESS(rv, rv);
michael@0 367
michael@0 368 if (aClearStyle) {
michael@0 369 // pasting does not inherit local inline styles
michael@0 370 nsCOMPtr<nsIDOMNode> tmpNode =
michael@0 371 do_QueryInterface(selection->GetAnchorNode());
michael@0 372 int32_t tmpOffset = static_cast<int32_t>(selection->AnchorOffset());
michael@0 373 rv = ClearStyle(address_of(tmpNode), &tmpOffset, nullptr, nullptr);
michael@0 374 NS_ENSURE_SUCCESS(rv, rv);
michael@0 375 }
michael@0 376 }
michael@0 377 else
michael@0 378 {
michael@0 379 // delete whole cells: we will replace with new table content
michael@0 380 { // Braces for artificial block to scope nsAutoSelectionReset.
michael@0 381 // Save current selection since DeleteTableCell perturbs it
michael@0 382 nsAutoSelectionReset selectionResetter(selection, this);
michael@0 383 rv = DeleteTableCell(1);
michael@0 384 NS_ENSURE_SUCCESS(rv, rv);
michael@0 385 }
michael@0 386 // collapse selection to beginning of deleted table content
michael@0 387 selection->CollapseToStart();
michael@0 388 }
michael@0 389
michael@0 390 // give rules a chance to handle or cancel
michael@0 391 nsTextRulesInfo ruleInfo(EditAction::insertElement);
michael@0 392 bool cancel, handled;
michael@0 393 rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
michael@0 394 NS_ENSURE_SUCCESS(rv, rv);
michael@0 395 if (cancel) {
michael@0 396 return NS_OK; // rules canceled the operation
michael@0 397 }
michael@0 398
michael@0 399 if (!handled)
michael@0 400 {
michael@0 401 // The rules code (WillDoAction above) might have changed the selection.
michael@0 402 // refresh our memory...
michael@0 403 rv = GetStartNodeAndOffset(selection, getter_AddRefs(parentNode), &offsetOfNewNode);
michael@0 404 NS_ENSURE_SUCCESS(rv, rv);
michael@0 405 NS_ENSURE_TRUE(parentNode, NS_ERROR_FAILURE);
michael@0 406
michael@0 407 // Adjust position based on the first node we are going to insert.
michael@0 408 NormalizeEOLInsertPosition(nodeList[0], address_of(parentNode), &offsetOfNewNode);
michael@0 409
michael@0 410 // if there are any invisible br's after our insertion point, remove them.
michael@0 411 // this is because if there is a br at end of what we paste, it will make
michael@0 412 // the invisible br visible.
michael@0 413 nsWSRunObject wsObj(this, parentNode, offsetOfNewNode);
michael@0 414 if (nsTextEditUtils::IsBreak(wsObj.mEndReasonNode) &&
michael@0 415 !IsVisBreak(wsObj.mEndReasonNode) )
michael@0 416 {
michael@0 417 rv = DeleteNode(wsObj.mEndReasonNode);
michael@0 418 NS_ENSURE_SUCCESS(rv, rv);
michael@0 419 }
michael@0 420
michael@0 421 // Remember if we are in a link.
michael@0 422 bool bStartedInLink = IsInLink(parentNode);
michael@0 423
michael@0 424 // Are we in a text node? If so, split it.
michael@0 425 if (IsTextNode(parentNode))
michael@0 426 {
michael@0 427 nsCOMPtr<nsIDOMNode> temp;
michael@0 428 rv = SplitNodeDeep(parentNode, parentNode, offsetOfNewNode, &offsetOfNewNode);
michael@0 429 NS_ENSURE_SUCCESS(rv, rv);
michael@0 430 rv = parentNode->GetParentNode(getter_AddRefs(temp));
michael@0 431 NS_ENSURE_SUCCESS(rv, rv);
michael@0 432 parentNode = temp;
michael@0 433 }
michael@0 434
michael@0 435 // build up list of parents of first node in list that are either
michael@0 436 // lists or tables. First examine front of paste node list.
michael@0 437 nsCOMArray<nsIDOMNode> startListAndTableArray;
michael@0 438 rv = GetListAndTableParents(false, nodeList, startListAndTableArray);
michael@0 439 NS_ENSURE_SUCCESS(rv, rv);
michael@0 440
michael@0 441 // remember number of lists and tables above us
michael@0 442 int32_t highWaterMark = -1;
michael@0 443 if (startListAndTableArray.Count() > 0)
michael@0 444 {
michael@0 445 rv = DiscoverPartialListsAndTables(nodeList, startListAndTableArray, &highWaterMark);
michael@0 446 NS_ENSURE_SUCCESS(rv, rv);
michael@0 447 }
michael@0 448
michael@0 449 // if we have pieces of tables or lists to be inserted, let's force the paste
michael@0 450 // to deal with table elements right away, so that it doesn't orphan some
michael@0 451 // table or list contents outside the table or list.
michael@0 452 if (highWaterMark >= 0)
michael@0 453 {
michael@0 454 rv = ReplaceOrphanedStructure(false, nodeList, startListAndTableArray, highWaterMark);
michael@0 455 NS_ENSURE_SUCCESS(rv, rv);
michael@0 456 }
michael@0 457
michael@0 458 // Now go through the same process again for the end of the paste node list.
michael@0 459 nsCOMArray<nsIDOMNode> endListAndTableArray;
michael@0 460 rv = GetListAndTableParents(true, nodeList, endListAndTableArray);
michael@0 461 NS_ENSURE_SUCCESS(rv, rv);
michael@0 462 highWaterMark = -1;
michael@0 463
michael@0 464 // remember number of lists and tables above us
michael@0 465 if (endListAndTableArray.Count() > 0)
michael@0 466 {
michael@0 467 rv = DiscoverPartialListsAndTables(nodeList, endListAndTableArray, &highWaterMark);
michael@0 468 NS_ENSURE_SUCCESS(rv, rv);
michael@0 469 }
michael@0 470
michael@0 471 // don't orphan partial list or table structure
michael@0 472 if (highWaterMark >= 0)
michael@0 473 {
michael@0 474 rv = ReplaceOrphanedStructure(true, nodeList, endListAndTableArray, highWaterMark);
michael@0 475 NS_ENSURE_SUCCESS(rv, rv);
michael@0 476 }
michael@0 477
michael@0 478 // Loop over the node list and paste the nodes:
michael@0 479 nsCOMPtr<nsIDOMNode> parentBlock, lastInsertNode, insertedContextParent;
michael@0 480 int32_t listCount = nodeList.Count();
michael@0 481 int32_t j;
michael@0 482 if (IsBlockNode(parentNode))
michael@0 483 parentBlock = parentNode;
michael@0 484 else
michael@0 485 parentBlock = GetBlockNodeParent(parentNode);
michael@0 486
michael@0 487 for (j=0; j<listCount; j++)
michael@0 488 {
michael@0 489 bool bDidInsert = false;
michael@0 490 nsCOMPtr<nsIDOMNode> curNode = nodeList[j];
michael@0 491
michael@0 492 NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE);
michael@0 493 NS_ENSURE_TRUE(curNode != fragmentAsNode, NS_ERROR_FAILURE);
michael@0 494 NS_ENSURE_TRUE(!nsTextEditUtils::IsBody(curNode), NS_ERROR_FAILURE);
michael@0 495
michael@0 496 if (insertedContextParent)
michael@0 497 {
michael@0 498 // if we had to insert something higher up in the paste hierarchy, we want to
michael@0 499 // skip any further paste nodes that descend from that. Else we will paste twice.
michael@0 500 if (nsEditorUtils::IsDescendantOf(curNode, insertedContextParent))
michael@0 501 continue;
michael@0 502 }
michael@0 503
michael@0 504 // give the user a hand on table element insertion. if they have
michael@0 505 // a table or table row on the clipboard, and are trying to insert
michael@0 506 // into a table or table row, insert the appropriate children instead.
michael@0 507 if ( (nsHTMLEditUtils::IsTableRow(curNode) && nsHTMLEditUtils::IsTableRow(parentNode))
michael@0 508 && (nsHTMLEditUtils::IsTable(curNode) || nsHTMLEditUtils::IsTable(parentNode)) )
michael@0 509 {
michael@0 510 nsCOMPtr<nsIDOMNode> child;
michael@0 511 curNode->GetFirstChild(getter_AddRefs(child));
michael@0 512 while (child)
michael@0 513 {
michael@0 514 rv = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, true);
michael@0 515 if (NS_FAILED(rv))
michael@0 516 break;
michael@0 517
michael@0 518 bDidInsert = true;
michael@0 519 lastInsertNode = child;
michael@0 520 offsetOfNewNode++;
michael@0 521
michael@0 522 curNode->GetFirstChild(getter_AddRefs(child));
michael@0 523 }
michael@0 524 }
michael@0 525 // give the user a hand on list insertion. if they have
michael@0 526 // a list on the clipboard, and are trying to insert
michael@0 527 // into a list or list item, insert the appropriate children instead,
michael@0 528 // ie, merge the lists instead of pasting in a sublist.
michael@0 529 else if (nsHTMLEditUtils::IsList(curNode) &&
michael@0 530 (nsHTMLEditUtils::IsList(parentNode) || nsHTMLEditUtils::IsListItem(parentNode)) )
michael@0 531 {
michael@0 532 nsCOMPtr<nsIDOMNode> child, tmp;
michael@0 533 curNode->GetFirstChild(getter_AddRefs(child));
michael@0 534 while (child)
michael@0 535 {
michael@0 536 if (nsHTMLEditUtils::IsListItem(child) || nsHTMLEditUtils::IsList(child))
michael@0 537 {
michael@0 538 // Check if we are pasting into empty list item. If so
michael@0 539 // delete it and paste into parent list instead.
michael@0 540 if (nsHTMLEditUtils::IsListItem(parentNode))
michael@0 541 {
michael@0 542 bool isEmpty;
michael@0 543 rv = IsEmptyNode(parentNode, &isEmpty, true);
michael@0 544 if (NS_SUCCEEDED(rv) && isEmpty)
michael@0 545 {
michael@0 546 int32_t newOffset;
michael@0 547 nsCOMPtr<nsIDOMNode> listNode = GetNodeLocation(parentNode, &newOffset);
michael@0 548 if (listNode)
michael@0 549 {
michael@0 550 DeleteNode(parentNode);
michael@0 551 parentNode = listNode;
michael@0 552 offsetOfNewNode = newOffset;
michael@0 553 }
michael@0 554 }
michael@0 555 }
michael@0 556 rv = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, true);
michael@0 557 if (NS_FAILED(rv))
michael@0 558 break;
michael@0 559
michael@0 560 bDidInsert = true;
michael@0 561 lastInsertNode = child;
michael@0 562 offsetOfNewNode++;
michael@0 563 }
michael@0 564 else
michael@0 565 {
michael@0 566 curNode->RemoveChild(child, getter_AddRefs(tmp));
michael@0 567 }
michael@0 568 curNode->GetFirstChild(getter_AddRefs(child));
michael@0 569 }
michael@0 570
michael@0 571 }
michael@0 572 // Check for pre's going into pre's.
michael@0 573 else if (nsHTMLEditUtils::IsPre(parentBlock) && nsHTMLEditUtils::IsPre(curNode))
michael@0 574 {
michael@0 575 nsCOMPtr<nsIDOMNode> child, tmp;
michael@0 576 curNode->GetFirstChild(getter_AddRefs(child));
michael@0 577 while (child)
michael@0 578 {
michael@0 579 rv = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, true);
michael@0 580 if (NS_FAILED(rv))
michael@0 581 break;
michael@0 582
michael@0 583 bDidInsert = true;
michael@0 584 lastInsertNode = child;
michael@0 585 offsetOfNewNode++;
michael@0 586
michael@0 587 curNode->GetFirstChild(getter_AddRefs(child));
michael@0 588 }
michael@0 589 }
michael@0 590
michael@0 591 if (!bDidInsert || NS_FAILED(rv))
michael@0 592 {
michael@0 593 // try to insert
michael@0 594 rv = InsertNodeAtPoint(curNode, address_of(parentNode), &offsetOfNewNode, true);
michael@0 595 if (NS_SUCCEEDED(rv))
michael@0 596 {
michael@0 597 bDidInsert = true;
michael@0 598 lastInsertNode = curNode;
michael@0 599 }
michael@0 600
michael@0 601 // Assume failure means no legal parent in the document hierarchy,
michael@0 602 // try again with the parent of curNode in the paste hierarchy.
michael@0 603 nsCOMPtr<nsIDOMNode> parent;
michael@0 604 while (NS_FAILED(rv) && curNode)
michael@0 605 {
michael@0 606 curNode->GetParentNode(getter_AddRefs(parent));
michael@0 607 if (parent && !nsTextEditUtils::IsBody(parent))
michael@0 608 {
michael@0 609 rv = InsertNodeAtPoint(parent, address_of(parentNode), &offsetOfNewNode, true);
michael@0 610 if (NS_SUCCEEDED(rv))
michael@0 611 {
michael@0 612 bDidInsert = true;
michael@0 613 insertedContextParent = parent;
michael@0 614 lastInsertNode = GetChildAt(parentNode, offsetOfNewNode);
michael@0 615 }
michael@0 616 }
michael@0 617 curNode = parent;
michael@0 618 }
michael@0 619 }
michael@0 620 if (lastInsertNode)
michael@0 621 {
michael@0 622 parentNode = GetNodeLocation(lastInsertNode, &offsetOfNewNode);
michael@0 623 offsetOfNewNode++;
michael@0 624 }
michael@0 625 }
michael@0 626
michael@0 627 // Now collapse the selection to the end of what we just inserted:
michael@0 628 if (lastInsertNode)
michael@0 629 {
michael@0 630 // set selection to the end of what we just pasted.
michael@0 631 nsCOMPtr<nsIDOMNode> selNode, tmp, visNode, highTable;
michael@0 632 int32_t selOffset;
michael@0 633
michael@0 634 // but don't cross tables
michael@0 635 if (!nsHTMLEditUtils::IsTable(lastInsertNode))
michael@0 636 {
michael@0 637 rv = GetLastEditableLeaf(lastInsertNode, address_of(selNode));
michael@0 638 NS_ENSURE_SUCCESS(rv, rv);
michael@0 639 tmp = selNode;
michael@0 640 while (tmp && (tmp != lastInsertNode))
michael@0 641 {
michael@0 642 if (nsHTMLEditUtils::IsTable(tmp))
michael@0 643 highTable = tmp;
michael@0 644 nsCOMPtr<nsIDOMNode> parent = tmp;
michael@0 645 tmp->GetParentNode(getter_AddRefs(parent));
michael@0 646 tmp = parent;
michael@0 647 }
michael@0 648 if (highTable)
michael@0 649 selNode = highTable;
michael@0 650 }
michael@0 651 if (!selNode)
michael@0 652 selNode = lastInsertNode;
michael@0 653 if (IsTextNode(selNode) || (IsContainer(selNode) && !nsHTMLEditUtils::IsTable(selNode)))
michael@0 654 {
michael@0 655 rv = GetLengthOfDOMNode(selNode, (uint32_t&)selOffset);
michael@0 656 NS_ENSURE_SUCCESS(rv, rv);
michael@0 657 }
michael@0 658 else // we need to find a container for selection. Look up.
michael@0 659 {
michael@0 660 tmp = selNode;
michael@0 661 selNode = GetNodeLocation(tmp, &selOffset);
michael@0 662 ++selOffset; // want to be *after* last leaf node in paste
michael@0 663 }
michael@0 664
michael@0 665 // make sure we don't end up with selection collapsed after an invisible break node
michael@0 666 nsWSRunObject wsRunObj(this, selNode, selOffset);
michael@0 667 int32_t outVisOffset=0;
michael@0 668 WSType visType;
michael@0 669 wsRunObj.PriorVisibleNode(selNode, selOffset, address_of(visNode),
michael@0 670 &outVisOffset, &visType);
michael@0 671 if (visType == WSType::br) {
michael@0 672 // we are after a break. Is it visible? Despite the name,
michael@0 673 // PriorVisibleNode does not make that determination for breaks.
michael@0 674 // It also may not return the break in visNode. We have to pull it
michael@0 675 // out of the nsWSRunObject's state.
michael@0 676 if (!IsVisBreak(wsRunObj.mStartReasonNode))
michael@0 677 {
michael@0 678 // don't leave selection past an invisible break;
michael@0 679 // reset {selNode,selOffset} to point before break
michael@0 680 selNode = GetNodeLocation(wsRunObj.mStartReasonNode, &selOffset);
michael@0 681 // we want to be inside any inline style prior to break
michael@0 682 nsWSRunObject wsRunObj(this, selNode, selOffset);
michael@0 683 wsRunObj.PriorVisibleNode(selNode, selOffset, address_of(visNode),
michael@0 684 &outVisOffset, &visType);
michael@0 685 if (visType == WSType::text || visType == WSType::normalWS) {
michael@0 686 selNode = visNode;
michael@0 687 selOffset = outVisOffset; // PriorVisibleNode already set offset to _after_ the text or ws
michael@0 688 } else if (visType == WSType::special) {
michael@0 689 // prior visible thing is an image or some other non-text thingy.
michael@0 690 // We want to be right after it.
michael@0 691 selNode = GetNodeLocation(wsRunObj.mStartReasonNode, &selOffset);
michael@0 692 ++selOffset;
michael@0 693 }
michael@0 694 }
michael@0 695 }
michael@0 696 selection->Collapse(selNode, selOffset);
michael@0 697
michael@0 698 // if we just pasted a link, discontinue link style
michael@0 699 nsCOMPtr<nsIDOMNode> link;
michael@0 700 if (!bStartedInLink && IsInLink(selNode, address_of(link)))
michael@0 701 {
michael@0 702 // so, if we just pasted a link, I split it. Why do that instead of just
michael@0 703 // nudging selection point beyond it? Because it might have ended in a BR
michael@0 704 // that is not visible. If so, the code above just placed selection
michael@0 705 // inside that. So I split it instead.
michael@0 706 nsCOMPtr<nsIDOMNode> leftLink;
michael@0 707 int32_t linkOffset;
michael@0 708 rv = SplitNodeDeep(link, selNode, selOffset, &linkOffset, true, address_of(leftLink));
michael@0 709 NS_ENSURE_SUCCESS(rv, rv);
michael@0 710 selNode = GetNodeLocation(leftLink, &selOffset);
michael@0 711 selection->Collapse(selNode, selOffset+1);
michael@0 712 }
michael@0 713 }
michael@0 714 }
michael@0 715
michael@0 716 return mRules->DidDoAction(selection, &ruleInfo, rv);
michael@0 717 }
michael@0 718
michael@0 719 nsresult
michael@0 720 nsHTMLEditor::AddInsertionListener(nsIContentFilter *aListener)
michael@0 721 {
michael@0 722 NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
michael@0 723
michael@0 724 // don't let a listener be added more than once
michael@0 725 if (mContentFilters.IndexOfObject(aListener) == -1)
michael@0 726 {
michael@0 727 NS_ENSURE_TRUE(mContentFilters.AppendObject(aListener), NS_ERROR_FAILURE);
michael@0 728 }
michael@0 729
michael@0 730 return NS_OK;
michael@0 731 }
michael@0 732
michael@0 733 nsresult
michael@0 734 nsHTMLEditor::RemoveInsertionListener(nsIContentFilter *aListener)
michael@0 735 {
michael@0 736 NS_ENSURE_TRUE(aListener, NS_ERROR_FAILURE);
michael@0 737
michael@0 738 NS_ENSURE_TRUE(mContentFilters.RemoveObject(aListener), NS_ERROR_FAILURE);
michael@0 739
michael@0 740 return NS_OK;
michael@0 741 }
michael@0 742
michael@0 743 nsresult
michael@0 744 nsHTMLEditor::DoContentFilterCallback(const nsAString &aFlavor,
michael@0 745 nsIDOMDocument *sourceDoc,
michael@0 746 bool aWillDeleteSelection,
michael@0 747 nsIDOMNode **aFragmentAsNode,
michael@0 748 nsIDOMNode **aFragStartNode,
michael@0 749 int32_t *aFragStartOffset,
michael@0 750 nsIDOMNode **aFragEndNode,
michael@0 751 int32_t *aFragEndOffset,
michael@0 752 nsIDOMNode **aTargetNode,
michael@0 753 int32_t *aTargetOffset,
michael@0 754 bool *aDoContinue)
michael@0 755 {
michael@0 756 *aDoContinue = true;
michael@0 757
michael@0 758 int32_t i;
michael@0 759 nsIContentFilter *listener;
michael@0 760 for (i=0; i < mContentFilters.Count() && *aDoContinue; i++)
michael@0 761 {
michael@0 762 listener = (nsIContentFilter *)mContentFilters[i];
michael@0 763 if (listener)
michael@0 764 listener->NotifyOfInsertion(aFlavor, nullptr, sourceDoc,
michael@0 765 aWillDeleteSelection, aFragmentAsNode,
michael@0 766 aFragStartNode, aFragStartOffset,
michael@0 767 aFragEndNode, aFragEndOffset,
michael@0 768 aTargetNode, aTargetOffset, aDoContinue);
michael@0 769 }
michael@0 770
michael@0 771 return NS_OK;
michael@0 772 }
michael@0 773
michael@0 774 bool
michael@0 775 nsHTMLEditor::IsInLink(nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *outLink)
michael@0 776 {
michael@0 777 NS_ENSURE_TRUE(aNode, false);
michael@0 778 if (outLink)
michael@0 779 *outLink = nullptr;
michael@0 780 nsCOMPtr<nsIDOMNode> tmp, node = aNode;
michael@0 781 while (node)
michael@0 782 {
michael@0 783 if (nsHTMLEditUtils::IsLink(node))
michael@0 784 {
michael@0 785 if (outLink)
michael@0 786 *outLink = node;
michael@0 787 return true;
michael@0 788 }
michael@0 789 tmp = node;
michael@0 790 tmp->GetParentNode(getter_AddRefs(node));
michael@0 791 }
michael@0 792 return false;
michael@0 793 }
michael@0 794
michael@0 795
michael@0 796 nsresult
michael@0 797 nsHTMLEditor::StripFormattingNodes(nsIDOMNode *aNode, bool aListOnly)
michael@0 798 {
michael@0 799 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
michael@0 800
michael@0 801 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
michael@0 802 if (content->TextIsOnlyWhitespace())
michael@0 803 {
michael@0 804 nsCOMPtr<nsIDOMNode> parent, ignored;
michael@0 805 aNode->GetParentNode(getter_AddRefs(parent));
michael@0 806 if (parent)
michael@0 807 {
michael@0 808 if (!aListOnly || nsHTMLEditUtils::IsList(parent)) {
michael@0 809 return parent->RemoveChild(aNode, getter_AddRefs(ignored));
michael@0 810 }
michael@0 811 return NS_OK;
michael@0 812 }
michael@0 813 }
michael@0 814
michael@0 815 if (!nsHTMLEditUtils::IsPre(aNode))
michael@0 816 {
michael@0 817 nsCOMPtr<nsIDOMNode> child;
michael@0 818 aNode->GetLastChild(getter_AddRefs(child));
michael@0 819
michael@0 820 while (child)
michael@0 821 {
michael@0 822 nsCOMPtr<nsIDOMNode> tmp;
michael@0 823 child->GetPreviousSibling(getter_AddRefs(tmp));
michael@0 824 nsresult rv = StripFormattingNodes(child, aListOnly);
michael@0 825 NS_ENSURE_SUCCESS(rv, rv);
michael@0 826 child = tmp;
michael@0 827 }
michael@0 828 }
michael@0 829 return NS_OK;
michael@0 830 }
michael@0 831
michael@0 832 NS_IMETHODIMP nsHTMLEditor::PrepareTransferable(nsITransferable **transferable)
michael@0 833 {
michael@0 834 return NS_OK;
michael@0 835 }
michael@0 836
michael@0 837 NS_IMETHODIMP nsHTMLEditor::PrepareHTMLTransferable(nsITransferable **aTransferable,
michael@0 838 bool aHavePrivFlavor)
michael@0 839 {
michael@0 840 // Create generic Transferable for getting the data
michael@0 841 nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", aTransferable);
michael@0 842 NS_ENSURE_SUCCESS(rv, rv);
michael@0 843
michael@0 844 // Get the nsITransferable interface for getting the data from the clipboard
michael@0 845 if (aTransferable)
michael@0 846 {
michael@0 847 nsCOMPtr<nsIDocument> destdoc = GetDocument();
michael@0 848 nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
michael@0 849 (*aTransferable)->Init(loadContext);
michael@0 850
michael@0 851 // Create the desired DataFlavor for the type of data
michael@0 852 // we want to get out of the transferable
michael@0 853 // This should only happen in html editors, not plaintext
michael@0 854 if (!IsPlaintextEditor())
michael@0 855 {
michael@0 856 if (!aHavePrivFlavor)
michael@0 857 {
michael@0 858 (*aTransferable)->AddDataFlavor(kNativeHTMLMime);
michael@0 859 }
michael@0 860 (*aTransferable)->AddDataFlavor(kHTMLMime);
michael@0 861 (*aTransferable)->AddDataFlavor(kFileMime);
michael@0 862
michael@0 863 switch (Preferences::GetInt("clipboard.paste_image_type", 1))
michael@0 864 {
michael@0 865 case 0: // prefer JPEG over PNG over GIF encoding
michael@0 866 (*aTransferable)->AddDataFlavor(kJPEGImageMime);
michael@0 867 (*aTransferable)->AddDataFlavor(kJPGImageMime);
michael@0 868 (*aTransferable)->AddDataFlavor(kPNGImageMime);
michael@0 869 (*aTransferable)->AddDataFlavor(kGIFImageMime);
michael@0 870 break;
michael@0 871 case 1: // prefer PNG over JPEG over GIF encoding (default)
michael@0 872 default:
michael@0 873 (*aTransferable)->AddDataFlavor(kPNGImageMime);
michael@0 874 (*aTransferable)->AddDataFlavor(kJPEGImageMime);
michael@0 875 (*aTransferable)->AddDataFlavor(kJPGImageMime);
michael@0 876 (*aTransferable)->AddDataFlavor(kGIFImageMime);
michael@0 877 break;
michael@0 878 case 2: // prefer GIF over JPEG over PNG encoding
michael@0 879 (*aTransferable)->AddDataFlavor(kGIFImageMime);
michael@0 880 (*aTransferable)->AddDataFlavor(kJPEGImageMime);
michael@0 881 (*aTransferable)->AddDataFlavor(kJPGImageMime);
michael@0 882 (*aTransferable)->AddDataFlavor(kPNGImageMime);
michael@0 883 break;
michael@0 884 }
michael@0 885 }
michael@0 886 (*aTransferable)->AddDataFlavor(kUnicodeMime);
michael@0 887 (*aTransferable)->AddDataFlavor(kMozTextInternal);
michael@0 888 }
michael@0 889
michael@0 890 return NS_OK;
michael@0 891 }
michael@0 892
michael@0 893 bool
michael@0 894 FindIntegerAfterString(const char *aLeadingString,
michael@0 895 nsCString &aCStr, int32_t &foundNumber)
michael@0 896 {
michael@0 897 // first obtain offsets from cfhtml str
michael@0 898 int32_t numFront = aCStr.Find(aLeadingString);
michael@0 899 if (numFront == -1)
michael@0 900 return false;
michael@0 901 numFront += strlen(aLeadingString);
michael@0 902
michael@0 903 int32_t numBack = aCStr.FindCharInSet(CRLF, numFront);
michael@0 904 if (numBack == -1)
michael@0 905 return false;
michael@0 906
michael@0 907 nsAutoCString numStr(Substring(aCStr, numFront, numBack-numFront));
michael@0 908 nsresult errorCode;
michael@0 909 foundNumber = numStr.ToInteger(&errorCode);
michael@0 910 return true;
michael@0 911 }
michael@0 912
michael@0 913 nsresult
michael@0 914 RemoveFragComments(nsCString & aStr)
michael@0 915 {
michael@0 916 // remove the StartFragment/EndFragment comments from the str, if present
michael@0 917 int32_t startCommentIndx = aStr.Find("<!--StartFragment");
michael@0 918 if (startCommentIndx >= 0)
michael@0 919 {
michael@0 920 int32_t startCommentEnd = aStr.Find("-->", false, startCommentIndx);
michael@0 921 if (startCommentEnd > startCommentIndx)
michael@0 922 aStr.Cut(startCommentIndx, (startCommentEnd+3)-startCommentIndx);
michael@0 923 }
michael@0 924 int32_t endCommentIndx = aStr.Find("<!--EndFragment");
michael@0 925 if (endCommentIndx >= 0)
michael@0 926 {
michael@0 927 int32_t endCommentEnd = aStr.Find("-->", false, endCommentIndx);
michael@0 928 if (endCommentEnd > endCommentIndx)
michael@0 929 aStr.Cut(endCommentIndx, (endCommentEnd+3)-endCommentIndx);
michael@0 930 }
michael@0 931 return NS_OK;
michael@0 932 }
michael@0 933
michael@0 934 nsresult
michael@0 935 nsHTMLEditor::ParseCFHTML(nsCString & aCfhtml, char16_t **aStuffToPaste, char16_t **aCfcontext)
michael@0 936 {
michael@0 937 // First obtain offsets from cfhtml str.
michael@0 938 int32_t startHTML, endHTML, startFragment, endFragment;
michael@0 939 if (!FindIntegerAfterString("StartHTML:", aCfhtml, startHTML) ||
michael@0 940 startHTML < -1)
michael@0 941 return NS_ERROR_FAILURE;
michael@0 942 if (!FindIntegerAfterString("EndHTML:", aCfhtml, endHTML) ||
michael@0 943 endHTML < -1)
michael@0 944 return NS_ERROR_FAILURE;
michael@0 945 if (!FindIntegerAfterString("StartFragment:", aCfhtml, startFragment) ||
michael@0 946 startFragment < 0)
michael@0 947 return NS_ERROR_FAILURE;
michael@0 948 if (!FindIntegerAfterString("EndFragment:", aCfhtml, endFragment) ||
michael@0 949 startFragment < 0)
michael@0 950 return NS_ERROR_FAILURE;
michael@0 951
michael@0 952 // The StartHTML and EndHTML markers are allowed to be -1 to include everything.
michael@0 953 // See Reference: MSDN doc entitled "HTML Clipboard Format"
michael@0 954 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
michael@0 955 if (startHTML == -1) {
michael@0 956 startHTML = aCfhtml.Find("<!--StartFragment-->");
michael@0 957 if (startHTML == -1)
michael@0 958 return NS_OK;
michael@0 959 }
michael@0 960 if (endHTML == -1) {
michael@0 961 const char endFragmentMarker[] = "<!--EndFragment-->";
michael@0 962 endHTML = aCfhtml.Find(endFragmentMarker);
michael@0 963 if (endHTML == -1)
michael@0 964 return NS_OK;
michael@0 965 endHTML += ArrayLength(endFragmentMarker) - 1;
michael@0 966 }
michael@0 967
michael@0 968 // create context string
michael@0 969 nsAutoCString contextUTF8(Substring(aCfhtml, startHTML, startFragment - startHTML) +
michael@0 970 NS_LITERAL_CSTRING("<!--" kInsertCookie "-->") +
michael@0 971 Substring(aCfhtml, endFragment, endHTML - endFragment));
michael@0 972
michael@0 973 // validate startFragment
michael@0 974 // make sure it's not in the middle of a HTML tag
michael@0 975 // see bug #228879 for more details
michael@0 976 int32_t curPos = startFragment;
michael@0 977 while (curPos > startHTML)
michael@0 978 {
michael@0 979 if (aCfhtml[curPos] == '>')
michael@0 980 {
michael@0 981 // working backwards, the first thing we see is the end of a tag
michael@0 982 // so StartFragment is good, so do nothing.
michael@0 983 break;
michael@0 984 }
michael@0 985 else if (aCfhtml[curPos] == '<')
michael@0 986 {
michael@0 987 // if we are at the start, then we want to see the '<'
michael@0 988 if (curPos != startFragment)
michael@0 989 {
michael@0 990 // working backwards, the first thing we see is the start of a tag
michael@0 991 // so StartFragment is bad, so we need to update it.
michael@0 992 NS_ERROR("StartFragment byte count in the clipboard looks bad, see bug #228879");
michael@0 993 startFragment = curPos - 1;
michael@0 994 }
michael@0 995 break;
michael@0 996 }
michael@0 997 else
michael@0 998 {
michael@0 999 curPos--;
michael@0 1000 }
michael@0 1001 }
michael@0 1002
michael@0 1003 // create fragment string
michael@0 1004 nsAutoCString fragmentUTF8(Substring(aCfhtml, startFragment, endFragment-startFragment));
michael@0 1005
michael@0 1006 // remove the StartFragment/EndFragment comments from the fragment, if present
michael@0 1007 RemoveFragComments(fragmentUTF8);
michael@0 1008
michael@0 1009 // remove the StartFragment/EndFragment comments from the context, if present
michael@0 1010 RemoveFragComments(contextUTF8);
michael@0 1011
michael@0 1012 // convert both strings to usc2
michael@0 1013 const nsAFlatString& fragUcs2Str = NS_ConvertUTF8toUTF16(fragmentUTF8);
michael@0 1014 const nsAFlatString& cntxtUcs2Str = NS_ConvertUTF8toUTF16(contextUTF8);
michael@0 1015
michael@0 1016 // translate platform linebreaks for fragment
michael@0 1017 int32_t oldLengthInChars = fragUcs2Str.Length() + 1; // +1 to include null terminator
michael@0 1018 int32_t newLengthInChars = 0;
michael@0 1019 *aStuffToPaste = nsLinebreakConverter::ConvertUnicharLineBreaks(fragUcs2Str.get(),
michael@0 1020 nsLinebreakConverter::eLinebreakAny,
michael@0 1021 nsLinebreakConverter::eLinebreakContent,
michael@0 1022 oldLengthInChars, &newLengthInChars);
michael@0 1023 NS_ENSURE_TRUE(*aStuffToPaste, NS_ERROR_FAILURE);
michael@0 1024
michael@0 1025 // translate platform linebreaks for context
michael@0 1026 oldLengthInChars = cntxtUcs2Str.Length() + 1; // +1 to include null terminator
michael@0 1027 newLengthInChars = 0;
michael@0 1028 *aCfcontext = nsLinebreakConverter::ConvertUnicharLineBreaks(cntxtUcs2Str.get(),
michael@0 1029 nsLinebreakConverter::eLinebreakAny,
michael@0 1030 nsLinebreakConverter::eLinebreakContent,
michael@0 1031 oldLengthInChars, &newLengthInChars);
michael@0 1032 // it's ok for context to be empty. frag might be whole doc and contain all its context.
michael@0 1033
michael@0 1034 // we're done!
michael@0 1035 return NS_OK;
michael@0 1036 }
michael@0 1037
michael@0 1038 nsresult nsHTMLEditor::InsertObject(const char* aType, nsISupports* aObject, bool aIsSafe,
michael@0 1039 nsIDOMDocument *aSourceDoc,
michael@0 1040 nsIDOMNode *aDestinationNode,
michael@0 1041 int32_t aDestOffset,
michael@0 1042 bool aDoDeleteSelection)
michael@0 1043 {
michael@0 1044 nsresult rv;
michael@0 1045
michael@0 1046 const char* type = aType;
michael@0 1047
michael@0 1048 // Check to see if we can insert an image file
michael@0 1049 bool insertAsImage = false;
michael@0 1050 nsCOMPtr<nsIURI> fileURI;
michael@0 1051 if (0 == nsCRT::strcmp(type, kFileMime))
michael@0 1052 {
michael@0 1053 nsCOMPtr<nsIFile> fileObj = do_QueryInterface(aObject);
michael@0 1054 if (fileObj)
michael@0 1055 {
michael@0 1056 rv = NS_NewFileURI(getter_AddRefs(fileURI), fileObj);
michael@0 1057 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1058
michael@0 1059 nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1");
michael@0 1060 NS_ENSURE_TRUE(mime, NS_ERROR_FAILURE);
michael@0 1061 nsAutoCString contentType;
michael@0 1062 rv = mime->GetTypeFromFile(fileObj, contentType);
michael@0 1063 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1064
michael@0 1065 // Accept any image type fed to us
michael@0 1066 if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) {
michael@0 1067 insertAsImage = true;
michael@0 1068 type = contentType.get();
michael@0 1069 }
michael@0 1070 }
michael@0 1071 }
michael@0 1072
michael@0 1073 if (0 == nsCRT::strcmp(type, kJPEGImageMime) ||
michael@0 1074 0 == nsCRT::strcmp(type, kJPGImageMime) ||
michael@0 1075 0 == nsCRT::strcmp(type, kPNGImageMime) ||
michael@0 1076 0 == nsCRT::strcmp(type, kGIFImageMime) ||
michael@0 1077 insertAsImage)
michael@0 1078 {
michael@0 1079 nsCOMPtr<nsIInputStream> imageStream;
michael@0 1080 if (insertAsImage) {
michael@0 1081 NS_ASSERTION(fileURI, "The file URI should be retrieved earlier");
michael@0 1082 rv = NS_OpenURI(getter_AddRefs(imageStream), fileURI);
michael@0 1083 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1084 } else {
michael@0 1085 imageStream = do_QueryInterface(aObject);
michael@0 1086 NS_ENSURE_TRUE(imageStream, NS_ERROR_FAILURE);
michael@0 1087 }
michael@0 1088
michael@0 1089 nsCString imageData;
michael@0 1090 rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData);
michael@0 1091 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1092
michael@0 1093 rv = imageStream->Close();
michael@0 1094 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1095
michael@0 1096 nsAutoCString data64;
michael@0 1097 rv = Base64Encode(imageData, data64);
michael@0 1098 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1099
michael@0 1100 nsAutoString stuffToPaste;
michael@0 1101 stuffToPaste.AssignLiteral("<IMG src=\"data:");
michael@0 1102 AppendUTF8toUTF16(type, stuffToPaste);
michael@0 1103 stuffToPaste.AppendLiteral(";base64,");
michael@0 1104 AppendUTF8toUTF16(data64, stuffToPaste);
michael@0 1105 stuffToPaste.AppendLiteral("\" alt=\"\" >");
michael@0 1106 nsAutoEditBatch beginBatching(this);
michael@0 1107 rv = DoInsertHTMLWithContext(stuffToPaste, EmptyString(), EmptyString(),
michael@0 1108 NS_LITERAL_STRING(kFileMime),
michael@0 1109 aSourceDoc,
michael@0 1110 aDestinationNode, aDestOffset,
michael@0 1111 aDoDeleteSelection,
michael@0 1112 aIsSafe);
michael@0 1113 }
michael@0 1114
michael@0 1115 return NS_OK;
michael@0 1116 }
michael@0 1117
michael@0 1118 NS_IMETHODIMP nsHTMLEditor::InsertFromTransferable(nsITransferable *transferable,
michael@0 1119 nsIDOMDocument *aSourceDoc,
michael@0 1120 const nsAString & aContextStr,
michael@0 1121 const nsAString & aInfoStr,
michael@0 1122 nsIDOMNode *aDestinationNode,
michael@0 1123 int32_t aDestOffset,
michael@0 1124 bool aDoDeleteSelection)
michael@0 1125 {
michael@0 1126 nsresult rv = NS_OK;
michael@0 1127 nsXPIDLCString bestFlavor;
michael@0 1128 nsCOMPtr<nsISupports> genericDataObj;
michael@0 1129 uint32_t len = 0;
michael@0 1130 if (NS_SUCCEEDED(transferable->GetAnyTransferData(getter_Copies(bestFlavor), getter_AddRefs(genericDataObj), &len)))
michael@0 1131 {
michael@0 1132 nsAutoTxnsConserveSelection dontSpazMySelection(this);
michael@0 1133 nsAutoString flavor;
michael@0 1134 flavor.AssignWithConversion(bestFlavor);
michael@0 1135 nsAutoString stuffToPaste;
michael@0 1136 #ifdef DEBUG_clipboard
michael@0 1137 printf("Got flavor [%s]\n", bestFlavor.get());
michael@0 1138 #endif
michael@0 1139
michael@0 1140 bool isSafe = IsSafeToInsertData(aSourceDoc);
michael@0 1141
michael@0 1142 if (0 == nsCRT::strcmp(bestFlavor, kFileMime) ||
michael@0 1143 0 == nsCRT::strcmp(bestFlavor, kJPEGImageMime) ||
michael@0 1144 0 == nsCRT::strcmp(bestFlavor, kJPGImageMime) ||
michael@0 1145 0 == nsCRT::strcmp(bestFlavor, kPNGImageMime) ||
michael@0 1146 0 == nsCRT::strcmp(bestFlavor, kGIFImageMime)) {
michael@0 1147 rv = InsertObject(bestFlavor, genericDataObj, isSafe,
michael@0 1148 aSourceDoc, aDestinationNode, aDestOffset, aDoDeleteSelection);
michael@0 1149 }
michael@0 1150 else if (0 == nsCRT::strcmp(bestFlavor, kNativeHTMLMime))
michael@0 1151 {
michael@0 1152 // note cf_html uses utf8, hence use length = len, not len/2 as in flavors below
michael@0 1153 nsCOMPtr<nsISupportsCString> textDataObj = do_QueryInterface(genericDataObj);
michael@0 1154 if (textDataObj && len > 0)
michael@0 1155 {
michael@0 1156 nsAutoCString cfhtml;
michael@0 1157 textDataObj->GetData(cfhtml);
michael@0 1158 NS_ASSERTION(cfhtml.Length() <= (len), "Invalid length!");
michael@0 1159 nsXPIDLString cfcontext, cffragment, cfselection; // cfselection left emtpy for now
michael@0 1160
michael@0 1161 rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), getter_Copies(cfcontext));
michael@0 1162 if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty())
michael@0 1163 {
michael@0 1164 nsAutoEditBatch beginBatching(this);
michael@0 1165 rv = DoInsertHTMLWithContext(cffragment,
michael@0 1166 cfcontext, cfselection, flavor,
michael@0 1167 aSourceDoc,
michael@0 1168 aDestinationNode, aDestOffset,
michael@0 1169 aDoDeleteSelection,
michael@0 1170 isSafe);
michael@0 1171 } else {
michael@0 1172 // In some platforms (like Linux), the clipboard might return data
michael@0 1173 // requested for unknown flavors (for example:
michael@0 1174 // application/x-moz-nativehtml). In this case, treat the data
michael@0 1175 // to be pasted as mere HTML to get the best chance of pasting it
michael@0 1176 // correctly.
michael@0 1177 bestFlavor.AssignLiteral(kHTMLMime);
michael@0 1178 // Fall through the next case
michael@0 1179 }
michael@0 1180 }
michael@0 1181 }
michael@0 1182 if (0 == nsCRT::strcmp(bestFlavor, kHTMLMime) ||
michael@0 1183 0 == nsCRT::strcmp(bestFlavor, kUnicodeMime) ||
michael@0 1184 0 == nsCRT::strcmp(bestFlavor, kMozTextInternal)) {
michael@0 1185 nsCOMPtr<nsISupportsString> textDataObj = do_QueryInterface(genericDataObj);
michael@0 1186 if (textDataObj && len > 0) {
michael@0 1187 nsAutoString text;
michael@0 1188 textDataObj->GetData(text);
michael@0 1189 NS_ASSERTION(text.Length() <= (len/2), "Invalid length!");
michael@0 1190 stuffToPaste.Assign(text.get(), len / 2);
michael@0 1191 } else {
michael@0 1192 nsCOMPtr<nsISupportsCString> textDataObj(do_QueryInterface(genericDataObj));
michael@0 1193 if (textDataObj && len > 0) {
michael@0 1194 nsAutoCString text;
michael@0 1195 textDataObj->GetData(text);
michael@0 1196 NS_ASSERTION(text.Length() <= len, "Invalid length!");
michael@0 1197 stuffToPaste.Assign(NS_ConvertUTF8toUTF16(Substring(text, 0, len)));
michael@0 1198 }
michael@0 1199 }
michael@0 1200
michael@0 1201 if (!stuffToPaste.IsEmpty()) {
michael@0 1202 nsAutoEditBatch beginBatching(this);
michael@0 1203 if (0 == nsCRT::strcmp(bestFlavor, kHTMLMime)) {
michael@0 1204 rv = DoInsertHTMLWithContext(stuffToPaste,
michael@0 1205 aContextStr, aInfoStr, flavor,
michael@0 1206 aSourceDoc,
michael@0 1207 aDestinationNode, aDestOffset,
michael@0 1208 aDoDeleteSelection,
michael@0 1209 isSafe);
michael@0 1210 } else {
michael@0 1211 rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection);
michael@0 1212 }
michael@0 1213 }
michael@0 1214 }
michael@0 1215 }
michael@0 1216
michael@0 1217 // Try to scroll the selection into view if the paste succeeded
michael@0 1218 if (NS_SUCCEEDED(rv))
michael@0 1219 ScrollSelectionIntoView(false);
michael@0 1220
michael@0 1221 return rv;
michael@0 1222 }
michael@0 1223
michael@0 1224 static void
michael@0 1225 GetStringFromDataTransfer(nsIDOMDataTransfer *aDataTransfer, const nsAString& aType,
michael@0 1226 int32_t aIndex, nsAString& aOutputString)
michael@0 1227 {
michael@0 1228 nsCOMPtr<nsIVariant> variant;
michael@0 1229 aDataTransfer->MozGetDataAt(aType, aIndex, getter_AddRefs(variant));
michael@0 1230 if (variant)
michael@0 1231 variant->GetAsAString(aOutputString);
michael@0 1232 }
michael@0 1233
michael@0 1234 nsresult nsHTMLEditor::InsertFromDataTransfer(DataTransfer *aDataTransfer,
michael@0 1235 int32_t aIndex,
michael@0 1236 nsIDOMDocument *aSourceDoc,
michael@0 1237 nsIDOMNode *aDestinationNode,
michael@0 1238 int32_t aDestOffset,
michael@0 1239 bool aDoDeleteSelection)
michael@0 1240 {
michael@0 1241 ErrorResult rv;
michael@0 1242 nsRefPtr<DOMStringList> types = aDataTransfer->MozTypesAt(aIndex, rv);
michael@0 1243 if (rv.Failed()) {
michael@0 1244 return rv.ErrorCode();
michael@0 1245 }
michael@0 1246
michael@0 1247 bool hasPrivateHTMLFlavor = types->Contains(NS_LITERAL_STRING(kHTMLContext));
michael@0 1248
michael@0 1249 bool isText = IsPlaintextEditor();
michael@0 1250 bool isSafe = IsSafeToInsertData(aSourceDoc);
michael@0 1251
michael@0 1252 uint32_t length = types->Length();
michael@0 1253 for (uint32_t t = 0; t < length; t++) {
michael@0 1254 nsAutoString type;
michael@0 1255 types->Item(t, type);
michael@0 1256
michael@0 1257 if (!isText) {
michael@0 1258 if (type.EqualsLiteral(kFileMime) ||
michael@0 1259 type.EqualsLiteral(kJPEGImageMime) ||
michael@0 1260 type.EqualsLiteral(kJPGImageMime) ||
michael@0 1261 type.EqualsLiteral(kPNGImageMime) ||
michael@0 1262 type.EqualsLiteral(kGIFImageMime)) {
michael@0 1263 nsCOMPtr<nsIVariant> variant;
michael@0 1264 aDataTransfer->MozGetDataAt(type, aIndex, getter_AddRefs(variant));
michael@0 1265 if (variant) {
michael@0 1266 nsCOMPtr<nsISupports> object;
michael@0 1267 variant->GetAsISupports(getter_AddRefs(object));
michael@0 1268 return InsertObject(NS_ConvertUTF16toUTF8(type).get(), object, isSafe,
michael@0 1269 aSourceDoc, aDestinationNode, aDestOffset, aDoDeleteSelection);
michael@0 1270 }
michael@0 1271 }
michael@0 1272 else if (!hasPrivateHTMLFlavor && type.EqualsLiteral(kNativeHTMLMime)) {
michael@0 1273 nsAutoString text;
michael@0 1274 GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kNativeHTMLMime), aIndex, text);
michael@0 1275 NS_ConvertUTF16toUTF8 cfhtml(text);
michael@0 1276
michael@0 1277 nsXPIDLString cfcontext, cffragment, cfselection; // cfselection left emtpy for now
michael@0 1278
michael@0 1279 nsresult rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), getter_Copies(cfcontext));
michael@0 1280 if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty())
michael@0 1281 {
michael@0 1282 nsAutoEditBatch beginBatching(this);
michael@0 1283 return DoInsertHTMLWithContext(cffragment,
michael@0 1284 cfcontext, cfselection, type,
michael@0 1285 aSourceDoc,
michael@0 1286 aDestinationNode, aDestOffset,
michael@0 1287 aDoDeleteSelection,
michael@0 1288 isSafe);
michael@0 1289 }
michael@0 1290 }
michael@0 1291 else if (type.EqualsLiteral(kHTMLMime)) {
michael@0 1292 nsAutoString text, contextString, infoString;
michael@0 1293 GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
michael@0 1294 GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kHTMLContext), aIndex, contextString);
michael@0 1295 GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kHTMLInfo), aIndex, infoString);
michael@0 1296
michael@0 1297 nsAutoEditBatch beginBatching(this);
michael@0 1298 if (type.EqualsLiteral(kHTMLMime)) {
michael@0 1299 return DoInsertHTMLWithContext(text,
michael@0 1300 contextString, infoString, type,
michael@0 1301 aSourceDoc,
michael@0 1302 aDestinationNode, aDestOffset,
michael@0 1303 aDoDeleteSelection,
michael@0 1304 isSafe);
michael@0 1305 }
michael@0 1306 }
michael@0 1307 }
michael@0 1308
michael@0 1309 if (type.EqualsLiteral(kTextMime) ||
michael@0 1310 type.EqualsLiteral(kMozTextInternal)) {
michael@0 1311 nsAutoString text;
michael@0 1312 GetStringFromDataTransfer(aDataTransfer, type, aIndex, text);
michael@0 1313
michael@0 1314 nsAutoEditBatch beginBatching(this);
michael@0 1315 return InsertTextAt(text, aDestinationNode, aDestOffset, aDoDeleteSelection);
michael@0 1316 }
michael@0 1317 }
michael@0 1318
michael@0 1319 return NS_OK;
michael@0 1320 }
michael@0 1321
michael@0 1322 bool nsHTMLEditor::HavePrivateHTMLFlavor(nsIClipboard *aClipboard)
michael@0 1323 {
michael@0 1324 // check the clipboard for our special kHTMLContext flavor. If that is there, we know
michael@0 1325 // we have our own internal html format on clipboard.
michael@0 1326
michael@0 1327 NS_ENSURE_TRUE(aClipboard, false);
michael@0 1328 bool bHavePrivateHTMLFlavor = false;
michael@0 1329
michael@0 1330 const char* flavArray[] = { kHTMLContext };
michael@0 1331
michael@0 1332 if (NS_SUCCEEDED(aClipboard->HasDataMatchingFlavors(flavArray,
michael@0 1333 ArrayLength(flavArray), nsIClipboard::kGlobalClipboard,
michael@0 1334 &bHavePrivateHTMLFlavor)))
michael@0 1335 return bHavePrivateHTMLFlavor;
michael@0 1336
michael@0 1337 return false;
michael@0 1338 }
michael@0 1339
michael@0 1340
michael@0 1341 NS_IMETHODIMP nsHTMLEditor::Paste(int32_t aSelectionType)
michael@0 1342 {
michael@0 1343 if (!FireClipboardEvent(NS_PASTE, aSelectionType))
michael@0 1344 return NS_OK;
michael@0 1345
michael@0 1346 // Get Clipboard Service
michael@0 1347 nsresult rv;
michael@0 1348 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
michael@0 1349 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1350
michael@0 1351 // find out if we have our internal html flavor on the clipboard. We don't want to mess
michael@0 1352 // around with cfhtml if we do.
michael@0 1353 bool bHavePrivateHTMLFlavor = HavePrivateHTMLFlavor(clipboard);
michael@0 1354
michael@0 1355 // Get the nsITransferable interface for getting the data from the clipboard
michael@0 1356 nsCOMPtr<nsITransferable> trans;
michael@0 1357 rv = PrepareHTMLTransferable(getter_AddRefs(trans), bHavePrivateHTMLFlavor);
michael@0 1358 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1359 NS_ENSURE_TRUE(trans, NS_ERROR_FAILURE);
michael@0 1360 // Get the Data from the clipboard
michael@0 1361 rv = clipboard->GetData(trans, aSelectionType);
michael@0 1362 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1363 if (!IsModifiable()) {
michael@0 1364 return NS_OK;
michael@0 1365 }
michael@0 1366
michael@0 1367 // also get additional html copy hints, if present
michael@0 1368 nsAutoString contextStr, infoStr;
michael@0 1369
michael@0 1370 // also get additional html copy hints, if present
michael@0 1371 if (bHavePrivateHTMLFlavor)
michael@0 1372 {
michael@0 1373 nsCOMPtr<nsISupports> contextDataObj, infoDataObj;
michael@0 1374 uint32_t contextLen, infoLen;
michael@0 1375 nsCOMPtr<nsISupportsString> textDataObj;
michael@0 1376
michael@0 1377 nsCOMPtr<nsITransferable> contextTrans =
michael@0 1378 do_CreateInstance("@mozilla.org/widget/transferable;1");
michael@0 1379 NS_ENSURE_TRUE(contextTrans, NS_ERROR_NULL_POINTER);
michael@0 1380 contextTrans->Init(nullptr);
michael@0 1381 contextTrans->AddDataFlavor(kHTMLContext);
michael@0 1382 clipboard->GetData(contextTrans, aSelectionType);
michael@0 1383 contextTrans->GetTransferData(kHTMLContext, getter_AddRefs(contextDataObj), &contextLen);
michael@0 1384
michael@0 1385 nsCOMPtr<nsITransferable> infoTrans =
michael@0 1386 do_CreateInstance("@mozilla.org/widget/transferable;1");
michael@0 1387 NS_ENSURE_TRUE(infoTrans, NS_ERROR_NULL_POINTER);
michael@0 1388 infoTrans->Init(nullptr);
michael@0 1389 infoTrans->AddDataFlavor(kHTMLInfo);
michael@0 1390 clipboard->GetData(infoTrans, aSelectionType);
michael@0 1391 infoTrans->GetTransferData(kHTMLInfo, getter_AddRefs(infoDataObj), &infoLen);
michael@0 1392
michael@0 1393 if (contextDataObj)
michael@0 1394 {
michael@0 1395 nsAutoString text;
michael@0 1396 textDataObj = do_QueryInterface(contextDataObj);
michael@0 1397 textDataObj->GetData(text);
michael@0 1398 NS_ASSERTION(text.Length() <= (contextLen/2), "Invalid length!");
michael@0 1399 contextStr.Assign(text.get(), contextLen / 2);
michael@0 1400 }
michael@0 1401
michael@0 1402 if (infoDataObj)
michael@0 1403 {
michael@0 1404 nsAutoString text;
michael@0 1405 textDataObj = do_QueryInterface(infoDataObj);
michael@0 1406 textDataObj->GetData(text);
michael@0 1407 NS_ASSERTION(text.Length() <= (infoLen/2), "Invalid length!");
michael@0 1408 infoStr.Assign(text.get(), infoLen / 2);
michael@0 1409 }
michael@0 1410 }
michael@0 1411
michael@0 1412 // handle transferable hooks
michael@0 1413 nsCOMPtr<nsIDOMDocument> domdoc;
michael@0 1414 GetDocument(getter_AddRefs(domdoc));
michael@0 1415 if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, trans))
michael@0 1416 return NS_OK;
michael@0 1417
michael@0 1418 return InsertFromTransferable(trans, nullptr, contextStr, infoStr,
michael@0 1419 nullptr, 0, true);
michael@0 1420 }
michael@0 1421
michael@0 1422 NS_IMETHODIMP nsHTMLEditor::PasteTransferable(nsITransferable *aTransferable)
michael@0 1423 {
michael@0 1424 // Use an invalid value for the clipboard type as data comes from aTransferable
michael@0 1425 // and we don't currently implement a way to put that in the data transfer yet.
michael@0 1426 if (!FireClipboardEvent(NS_PASTE, nsIClipboard::kGlobalClipboard))
michael@0 1427 return NS_OK;
michael@0 1428
michael@0 1429 // handle transferable hooks
michael@0 1430 nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
michael@0 1431 if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, aTransferable))
michael@0 1432 return NS_OK;
michael@0 1433
michael@0 1434 nsAutoString contextStr, infoStr;
michael@0 1435 return InsertFromTransferable(aTransferable, nullptr, contextStr, infoStr,
michael@0 1436 nullptr, 0, true);
michael@0 1437 }
michael@0 1438
michael@0 1439 //
michael@0 1440 // HTML PasteNoFormatting. Ignore any HTML styles and formating in paste source
michael@0 1441 //
michael@0 1442 NS_IMETHODIMP nsHTMLEditor::PasteNoFormatting(int32_t aSelectionType)
michael@0 1443 {
michael@0 1444 if (!FireClipboardEvent(NS_PASTE, aSelectionType))
michael@0 1445 return NS_OK;
michael@0 1446
michael@0 1447 ForceCompositionEnd();
michael@0 1448
michael@0 1449 // Get Clipboard Service
michael@0 1450 nsresult rv;
michael@0 1451 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
michael@0 1452 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1453
michael@0 1454 // Get the nsITransferable interface for getting the data from the clipboard.
michael@0 1455 // use nsPlaintextEditor::PrepareTransferable() to force unicode plaintext data.
michael@0 1456 nsCOMPtr<nsITransferable> trans;
michael@0 1457 rv = nsPlaintextEditor::PrepareTransferable(getter_AddRefs(trans));
michael@0 1458 if (NS_SUCCEEDED(rv) && trans)
michael@0 1459 {
michael@0 1460 // Get the Data from the clipboard
michael@0 1461 if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable())
michael@0 1462 {
michael@0 1463 const nsAFlatString& empty = EmptyString();
michael@0 1464 rv = InsertFromTransferable(trans, nullptr, empty, empty, nullptr, 0,
michael@0 1465 true);
michael@0 1466 }
michael@0 1467 }
michael@0 1468
michael@0 1469 return rv;
michael@0 1470 }
michael@0 1471
michael@0 1472
michael@0 1473 // The following arrays contain the MIME types that we can paste. The arrays
michael@0 1474 // are used by CanPaste() and CanPasteTransferable() below.
michael@0 1475
michael@0 1476 static const char* textEditorFlavors[] = { kUnicodeMime };
michael@0 1477 static const char* textHtmlEditorFlavors[] = { kUnicodeMime, kHTMLMime,
michael@0 1478 kJPEGImageMime, kJPGImageMime,
michael@0 1479 kPNGImageMime, kGIFImageMime };
michael@0 1480
michael@0 1481 NS_IMETHODIMP nsHTMLEditor::CanPaste(int32_t aSelectionType, bool *aCanPaste)
michael@0 1482 {
michael@0 1483 NS_ENSURE_ARG_POINTER(aCanPaste);
michael@0 1484 *aCanPaste = false;
michael@0 1485
michael@0 1486 // can't paste if readonly
michael@0 1487 if (!IsModifiable()) {
michael@0 1488 return NS_OK;
michael@0 1489 }
michael@0 1490
michael@0 1491 nsresult rv;
michael@0 1492 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
michael@0 1493 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1494
michael@0 1495 bool haveFlavors;
michael@0 1496
michael@0 1497 // Use the flavors depending on the current editor mask
michael@0 1498 if (IsPlaintextEditor())
michael@0 1499 rv = clipboard->HasDataMatchingFlavors(textEditorFlavors,
michael@0 1500 ArrayLength(textEditorFlavors),
michael@0 1501 aSelectionType, &haveFlavors);
michael@0 1502 else
michael@0 1503 rv = clipboard->HasDataMatchingFlavors(textHtmlEditorFlavors,
michael@0 1504 ArrayLength(textHtmlEditorFlavors),
michael@0 1505 aSelectionType, &haveFlavors);
michael@0 1506
michael@0 1507 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1508
michael@0 1509 *aCanPaste = haveFlavors;
michael@0 1510 return NS_OK;
michael@0 1511 }
michael@0 1512
michael@0 1513 NS_IMETHODIMP nsHTMLEditor::CanPasteTransferable(nsITransferable *aTransferable, bool *aCanPaste)
michael@0 1514 {
michael@0 1515 NS_ENSURE_ARG_POINTER(aCanPaste);
michael@0 1516
michael@0 1517 // can't paste if readonly
michael@0 1518 if (!IsModifiable()) {
michael@0 1519 *aCanPaste = false;
michael@0 1520 return NS_OK;
michael@0 1521 }
michael@0 1522
michael@0 1523 // If |aTransferable| is null, assume that a paste will succeed.
michael@0 1524 if (!aTransferable) {
michael@0 1525 *aCanPaste = true;
michael@0 1526 return NS_OK;
michael@0 1527 }
michael@0 1528
michael@0 1529 // Peek in |aTransferable| to see if it contains a supported MIME type.
michael@0 1530
michael@0 1531 // Use the flavors depending on the current editor mask
michael@0 1532 const char ** flavors;
michael@0 1533 unsigned length;
michael@0 1534 if (IsPlaintextEditor()) {
michael@0 1535 flavors = textEditorFlavors;
michael@0 1536 length = ArrayLength(textEditorFlavors);
michael@0 1537 } else {
michael@0 1538 flavors = textHtmlEditorFlavors;
michael@0 1539 length = ArrayLength(textHtmlEditorFlavors);
michael@0 1540 }
michael@0 1541
michael@0 1542 for (unsigned int i = 0; i < length; i++, flavors++) {
michael@0 1543 nsCOMPtr<nsISupports> data;
michael@0 1544 uint32_t dataLen;
michael@0 1545 nsresult rv = aTransferable->GetTransferData(*flavors,
michael@0 1546 getter_AddRefs(data),
michael@0 1547 &dataLen);
michael@0 1548 if (NS_SUCCEEDED(rv) && data) {
michael@0 1549 *aCanPaste = true;
michael@0 1550 return NS_OK;
michael@0 1551 }
michael@0 1552 }
michael@0 1553
michael@0 1554 *aCanPaste = false;
michael@0 1555 return NS_OK;
michael@0 1556 }
michael@0 1557
michael@0 1558
michael@0 1559 //
michael@0 1560 // HTML PasteAsQuotation: Paste in a blockquote type=cite
michael@0 1561 //
michael@0 1562 NS_IMETHODIMP nsHTMLEditor::PasteAsQuotation(int32_t aSelectionType)
michael@0 1563 {
michael@0 1564 if (IsPlaintextEditor())
michael@0 1565 return PasteAsPlaintextQuotation(aSelectionType);
michael@0 1566
michael@0 1567 nsAutoString citation;
michael@0 1568 return PasteAsCitedQuotation(citation, aSelectionType);
michael@0 1569 }
michael@0 1570
michael@0 1571 NS_IMETHODIMP nsHTMLEditor::PasteAsCitedQuotation(const nsAString & aCitation,
michael@0 1572 int32_t aSelectionType)
michael@0 1573 {
michael@0 1574 nsAutoEditBatch beginBatching(this);
michael@0 1575 nsAutoRules beginRulesSniffing(this, EditAction::insertQuotation, nsIEditor::eNext);
michael@0 1576
michael@0 1577 // get selection
michael@0 1578 nsRefPtr<Selection> selection = GetSelection();
michael@0 1579 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
michael@0 1580
michael@0 1581 // give rules a chance to handle or cancel
michael@0 1582 nsTextRulesInfo ruleInfo(EditAction::insertElement);
michael@0 1583 bool cancel, handled;
michael@0 1584 // Protect the edit rules object from dying
michael@0 1585 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
michael@0 1586 nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
michael@0 1587 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1588 if (cancel || handled) {
michael@0 1589 return NS_OK; // rules canceled the operation
michael@0 1590 }
michael@0 1591
michael@0 1592 nsCOMPtr<nsIDOMNode> newNode;
michael@0 1593 rv = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("blockquote"), getter_AddRefs(newNode));
michael@0 1594 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1595 NS_ENSURE_TRUE(newNode, NS_ERROR_NULL_POINTER);
michael@0 1596
michael@0 1597 // Try to set type=cite. Ignore it if this fails.
michael@0 1598 nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(newNode);
michael@0 1599 if (newElement) {
michael@0 1600 newElement->SetAttribute(NS_LITERAL_STRING("type"), NS_LITERAL_STRING("cite"));
michael@0 1601 }
michael@0 1602
michael@0 1603 // Set the selection to the underneath the node we just inserted:
michael@0 1604 rv = selection->Collapse(newNode, 0);
michael@0 1605 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1606
michael@0 1607 return Paste(aSelectionType);
michael@0 1608 }
michael@0 1609
michael@0 1610 //
michael@0 1611 // Paste a plaintext quotation
michael@0 1612 //
michael@0 1613 NS_IMETHODIMP nsHTMLEditor::PasteAsPlaintextQuotation(int32_t aSelectionType)
michael@0 1614 {
michael@0 1615 // Get Clipboard Service
michael@0 1616 nsresult rv;
michael@0 1617 nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
michael@0 1618 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1619
michael@0 1620 // Create generic Transferable for getting the data
michael@0 1621 nsCOMPtr<nsITransferable> trans =
michael@0 1622 do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
michael@0 1623 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1624 NS_ENSURE_TRUE(trans, NS_ERROR_FAILURE);
michael@0 1625
michael@0 1626 nsCOMPtr<nsIDocument> destdoc = GetDocument();
michael@0 1627 nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr;
michael@0 1628 trans->Init(loadContext);
michael@0 1629
michael@0 1630 // We only handle plaintext pastes here
michael@0 1631 trans->AddDataFlavor(kUnicodeMime);
michael@0 1632
michael@0 1633 // Get the Data from the clipboard
michael@0 1634 clipboard->GetData(trans, aSelectionType);
michael@0 1635
michael@0 1636 // Now we ask the transferable for the data
michael@0 1637 // it still owns the data, we just have a pointer to it.
michael@0 1638 // If it can't support a "text" output of the data the call will fail
michael@0 1639 nsCOMPtr<nsISupports> genericDataObj;
michael@0 1640 uint32_t len = 0;
michael@0 1641 char* flav = 0;
michael@0 1642 rv = trans->GetAnyTransferData(&flav, getter_AddRefs(genericDataObj), &len);
michael@0 1643 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1644
michael@0 1645 if (flav && 0 == nsCRT::strcmp(flav, kUnicodeMime))
michael@0 1646 {
michael@0 1647 #ifdef DEBUG_clipboard
michael@0 1648 printf("Got flavor [%s]\n", flav);
michael@0 1649 #endif
michael@0 1650 nsCOMPtr<nsISupportsString> textDataObj = do_QueryInterface(genericDataObj);
michael@0 1651 if (textDataObj && len > 0)
michael@0 1652 {
michael@0 1653 nsAutoString stuffToPaste;
michael@0 1654 textDataObj->GetData(stuffToPaste);
michael@0 1655 NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!");
michael@0 1656 nsAutoEditBatch beginBatching(this);
michael@0 1657 rv = InsertAsPlaintextQuotation(stuffToPaste, true, 0);
michael@0 1658 }
michael@0 1659 }
michael@0 1660 NS_Free(flav);
michael@0 1661
michael@0 1662 return rv;
michael@0 1663 }
michael@0 1664
michael@0 1665 NS_IMETHODIMP
michael@0 1666 nsHTMLEditor::InsertTextWithQuotations(const nsAString &aStringToInsert)
michael@0 1667 {
michael@0 1668 if (mWrapToWindow)
michael@0 1669 return InsertText(aStringToInsert);
michael@0 1670
michael@0 1671 // The whole operation should be undoable in one transaction:
michael@0 1672 BeginTransaction();
michael@0 1673
michael@0 1674 // We're going to loop over the string, collecting up a "hunk"
michael@0 1675 // that's all the same type (quoted or not),
michael@0 1676 // Whenever the quotedness changes (or we reach the string's end)
michael@0 1677 // we will insert the hunk all at once, quoted or non.
michael@0 1678
michael@0 1679 static const char16_t cite('>');
michael@0 1680 bool curHunkIsQuoted = (aStringToInsert.First() == cite);
michael@0 1681
michael@0 1682 nsAString::const_iterator hunkStart, strEnd;
michael@0 1683 aStringToInsert.BeginReading(hunkStart);
michael@0 1684 aStringToInsert.EndReading(strEnd);
michael@0 1685
michael@0 1686 // In the loop below, we only look for DOM newlines (\n),
michael@0 1687 // because we don't have a FindChars method that can look
michael@0 1688 // for both \r and \n. \r is illegal in the dom anyway,
michael@0 1689 // but in debug builds, let's take the time to verify that
michael@0 1690 // there aren't any there:
michael@0 1691 #ifdef DEBUG
michael@0 1692 nsAString::const_iterator dbgStart (hunkStart);
michael@0 1693 if (FindCharInReadable('\r', dbgStart, strEnd))
michael@0 1694 NS_ASSERTION(false,
michael@0 1695 "Return characters in DOM! InsertTextWithQuotations may be wrong");
michael@0 1696 #endif /* DEBUG */
michael@0 1697
michael@0 1698 // Loop over lines:
michael@0 1699 nsresult rv = NS_OK;
michael@0 1700 nsAString::const_iterator lineStart (hunkStart);
michael@0 1701 while (1) // we will break from inside when we run out of newlines
michael@0 1702 {
michael@0 1703 // Search for the end of this line (dom newlines, see above):
michael@0 1704 bool found = FindCharInReadable('\n', lineStart, strEnd);
michael@0 1705 bool quoted = false;
michael@0 1706 if (found)
michael@0 1707 {
michael@0 1708 // if there's another newline, lineStart now points there.
michael@0 1709 // Loop over any consecutive newline chars:
michael@0 1710 nsAString::const_iterator firstNewline (lineStart);
michael@0 1711 while (*lineStart == '\n')
michael@0 1712 ++lineStart;
michael@0 1713 quoted = (*lineStart == cite);
michael@0 1714 if (quoted == curHunkIsQuoted)
michael@0 1715 continue;
michael@0 1716 // else we're changing state, so we need to insert
michael@0 1717 // from curHunk to lineStart then loop around.
michael@0 1718
michael@0 1719 // But if the current hunk is quoted, then we want to make sure
michael@0 1720 // that any extra newlines on the end do not get included in
michael@0 1721 // the quoted section: blank lines flaking a quoted section
michael@0 1722 // should be considered unquoted, so that if the user clicks
michael@0 1723 // there and starts typing, the new text will be outside of
michael@0 1724 // the quoted block.
michael@0 1725 if (curHunkIsQuoted)
michael@0 1726 lineStart = firstNewline;
michael@0 1727 }
michael@0 1728
michael@0 1729 // If no newline found, lineStart is now strEnd and we can finish up,
michael@0 1730 // inserting from curHunk to lineStart then returning.
michael@0 1731 const nsAString &curHunk = Substring(hunkStart, lineStart);
michael@0 1732 nsCOMPtr<nsIDOMNode> dummyNode;
michael@0 1733 #ifdef DEBUG_akkana_verbose
michael@0 1734 printf("==== Inserting text as %squoted: ---\n%s---\n",
michael@0 1735 curHunkIsQuoted ? "" : "non-",
michael@0 1736 NS_LossyConvertUTF16toASCII(curHunk).get());
michael@0 1737 #endif
michael@0 1738 if (curHunkIsQuoted)
michael@0 1739 rv = InsertAsPlaintextQuotation(curHunk, false,
michael@0 1740 getter_AddRefs(dummyNode));
michael@0 1741 else
michael@0 1742 rv = InsertText(curHunk);
michael@0 1743
michael@0 1744 if (!found)
michael@0 1745 break;
michael@0 1746
michael@0 1747 curHunkIsQuoted = quoted;
michael@0 1748 hunkStart = lineStart;
michael@0 1749 }
michael@0 1750
michael@0 1751 EndTransaction();
michael@0 1752
michael@0 1753 return rv;
michael@0 1754 }
michael@0 1755
michael@0 1756 NS_IMETHODIMP nsHTMLEditor::InsertAsQuotation(const nsAString & aQuotedText,
michael@0 1757 nsIDOMNode **aNodeInserted)
michael@0 1758 {
michael@0 1759 if (IsPlaintextEditor())
michael@0 1760 return InsertAsPlaintextQuotation(aQuotedText, true, aNodeInserted);
michael@0 1761
michael@0 1762 nsAutoString citation;
michael@0 1763 return InsertAsCitedQuotation(aQuotedText, citation, false,
michael@0 1764 aNodeInserted);
michael@0 1765 }
michael@0 1766
michael@0 1767 // Insert plaintext as a quotation, with cite marks (e.g. "> ").
michael@0 1768 // This differs from its corresponding method in nsPlaintextEditor
michael@0 1769 // in that here, quoted material is enclosed in a <pre> tag
michael@0 1770 // in order to preserve the original line wrapping.
michael@0 1771 NS_IMETHODIMP
michael@0 1772 nsHTMLEditor::InsertAsPlaintextQuotation(const nsAString & aQuotedText,
michael@0 1773 bool aAddCites,
michael@0 1774 nsIDOMNode **aNodeInserted)
michael@0 1775 {
michael@0 1776 if (mWrapToWindow)
michael@0 1777 return nsPlaintextEditor::InsertAsQuotation(aQuotedText, aNodeInserted);
michael@0 1778
michael@0 1779 nsCOMPtr<nsIDOMNode> newNode;
michael@0 1780 // get selection
michael@0 1781 nsRefPtr<Selection> selection = GetSelection();
michael@0 1782 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
michael@0 1783
michael@0 1784 nsAutoEditBatch beginBatching(this);
michael@0 1785 nsAutoRules beginRulesSniffing(this, EditAction::insertQuotation, nsIEditor::eNext);
michael@0 1786
michael@0 1787 // give rules a chance to handle or cancel
michael@0 1788 nsTextRulesInfo ruleInfo(EditAction::insertElement);
michael@0 1789 bool cancel, handled;
michael@0 1790 // Protect the edit rules object from dying
michael@0 1791 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
michael@0 1792 nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
michael@0 1793 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1794 if (cancel || handled) {
michael@0 1795 return NS_OK; // rules canceled the operation
michael@0 1796 }
michael@0 1797
michael@0 1798 // Wrap the inserted quote in a <span> so it won't be wrapped:
michael@0 1799 rv = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("span"), getter_AddRefs(newNode));
michael@0 1800
michael@0 1801 // If this succeeded, then set selection inside the pre
michael@0 1802 // so the inserted text will end up there.
michael@0 1803 // If it failed, we don't care what the return value was,
michael@0 1804 // but we'll fall through and try to insert the text anyway.
michael@0 1805 if (NS_SUCCEEDED(rv) && newNode)
michael@0 1806 {
michael@0 1807 // Add an attribute on the pre node so we'll know it's a quotation.
michael@0 1808 // Do this after the insertion, so that
michael@0 1809 nsCOMPtr<nsIDOMElement> preElement = do_QueryInterface(newNode);
michael@0 1810 if (preElement)
michael@0 1811 {
michael@0 1812 preElement->SetAttribute(NS_LITERAL_STRING("_moz_quote"),
michael@0 1813 NS_LITERAL_STRING("true"));
michael@0 1814 // turn off wrapping on spans
michael@0 1815 preElement->SetAttribute(NS_LITERAL_STRING("style"),
michael@0 1816 NS_LITERAL_STRING("white-space: pre;"));
michael@0 1817 }
michael@0 1818 // and set the selection inside it:
michael@0 1819 selection->Collapse(newNode, 0);
michael@0 1820 }
michael@0 1821
michael@0 1822 if (aAddCites)
michael@0 1823 rv = nsPlaintextEditor::InsertAsQuotation(aQuotedText, aNodeInserted);
michael@0 1824 else
michael@0 1825 rv = nsPlaintextEditor::InsertText(aQuotedText);
michael@0 1826 // Note that if !aAddCites, aNodeInserted isn't set.
michael@0 1827 // That's okay because the routines that use aAddCites
michael@0 1828 // don't need to know the inserted node.
michael@0 1829
michael@0 1830 if (aNodeInserted && NS_SUCCEEDED(rv))
michael@0 1831 {
michael@0 1832 *aNodeInserted = newNode;
michael@0 1833 NS_IF_ADDREF(*aNodeInserted);
michael@0 1834 }
michael@0 1835
michael@0 1836 // Set the selection to just after the inserted node:
michael@0 1837 if (NS_SUCCEEDED(rv) && newNode)
michael@0 1838 {
michael@0 1839 int32_t offset;
michael@0 1840 nsCOMPtr<nsIDOMNode> parent = GetNodeLocation(newNode, &offset);
michael@0 1841 if (parent) {
michael@0 1842 selection->Collapse(parent, offset + 1);
michael@0 1843 }
michael@0 1844 }
michael@0 1845 return rv;
michael@0 1846 }
michael@0 1847
michael@0 1848 NS_IMETHODIMP
michael@0 1849 nsHTMLEditor::StripCites()
michael@0 1850 {
michael@0 1851 return nsPlaintextEditor::StripCites();
michael@0 1852 }
michael@0 1853
michael@0 1854 NS_IMETHODIMP
michael@0 1855 nsHTMLEditor::Rewrap(bool aRespectNewlines)
michael@0 1856 {
michael@0 1857 return nsPlaintextEditor::Rewrap(aRespectNewlines);
michael@0 1858 }
michael@0 1859
michael@0 1860 NS_IMETHODIMP
michael@0 1861 nsHTMLEditor::InsertAsCitedQuotation(const nsAString & aQuotedText,
michael@0 1862 const nsAString & aCitation,
michael@0 1863 bool aInsertHTML,
michael@0 1864 nsIDOMNode **aNodeInserted)
michael@0 1865 {
michael@0 1866 // Don't let anyone insert html into a "plaintext" editor:
michael@0 1867 if (IsPlaintextEditor())
michael@0 1868 {
michael@0 1869 NS_ASSERTION(!aInsertHTML, "InsertAsCitedQuotation: trying to insert html into plaintext editor");
michael@0 1870 return InsertAsPlaintextQuotation(aQuotedText, true, aNodeInserted);
michael@0 1871 }
michael@0 1872
michael@0 1873 nsCOMPtr<nsIDOMNode> newNode;
michael@0 1874
michael@0 1875 // get selection
michael@0 1876 nsRefPtr<Selection> selection = GetSelection();
michael@0 1877 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
michael@0 1878
michael@0 1879 nsAutoEditBatch beginBatching(this);
michael@0 1880 nsAutoRules beginRulesSniffing(this, EditAction::insertQuotation, nsIEditor::eNext);
michael@0 1881
michael@0 1882 // give rules a chance to handle or cancel
michael@0 1883 nsTextRulesInfo ruleInfo(EditAction::insertElement);
michael@0 1884 bool cancel, handled;
michael@0 1885 // Protect the edit rules object from dying
michael@0 1886 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
michael@0 1887 nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
michael@0 1888 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1889 if (cancel || handled) {
michael@0 1890 return NS_OK; // rules canceled the operation
michael@0 1891 }
michael@0 1892
michael@0 1893 rv = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("blockquote"), getter_AddRefs(newNode));
michael@0 1894 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1895 NS_ENSURE_TRUE(newNode, NS_ERROR_NULL_POINTER);
michael@0 1896
michael@0 1897 // Try to set type=cite. Ignore it if this fails.
michael@0 1898 nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(newNode);
michael@0 1899 if (newElement)
michael@0 1900 {
michael@0 1901 NS_NAMED_LITERAL_STRING(citeStr, "cite");
michael@0 1902 newElement->SetAttribute(NS_LITERAL_STRING("type"), citeStr);
michael@0 1903
michael@0 1904 if (!aCitation.IsEmpty())
michael@0 1905 newElement->SetAttribute(citeStr, aCitation);
michael@0 1906
michael@0 1907 // Set the selection inside the blockquote so aQuotedText will go there:
michael@0 1908 selection->Collapse(newNode, 0);
michael@0 1909 }
michael@0 1910
michael@0 1911 if (aInsertHTML)
michael@0 1912 rv = LoadHTML(aQuotedText);
michael@0 1913 else
michael@0 1914 rv = InsertText(aQuotedText); // XXX ignore charset
michael@0 1915
michael@0 1916 if (aNodeInserted && NS_SUCCEEDED(rv))
michael@0 1917 {
michael@0 1918 *aNodeInserted = newNode;
michael@0 1919 NS_IF_ADDREF(*aNodeInserted);
michael@0 1920 }
michael@0 1921
michael@0 1922 // Set the selection to just after the inserted node:
michael@0 1923 if (NS_SUCCEEDED(rv) && newNode)
michael@0 1924 {
michael@0 1925 int32_t offset;
michael@0 1926 nsCOMPtr<nsIDOMNode> parent = GetNodeLocation(newNode, &offset);
michael@0 1927 if (parent) {
michael@0 1928 selection->Collapse(parent, offset + 1);
michael@0 1929 }
michael@0 1930 }
michael@0 1931 return rv;
michael@0 1932 }
michael@0 1933
michael@0 1934
michael@0 1935 void RemoveBodyAndHead(nsIDOMNode *aNode)
michael@0 1936 {
michael@0 1937 if (!aNode)
michael@0 1938 return;
michael@0 1939
michael@0 1940 nsCOMPtr<nsIDOMNode> tmp, child, body, head;
michael@0 1941 // find the body and head nodes if any.
michael@0 1942 // look only at immediate children of aNode.
michael@0 1943 aNode->GetFirstChild(getter_AddRefs(child));
michael@0 1944 while (child)
michael@0 1945 {
michael@0 1946 if (nsTextEditUtils::IsBody(child))
michael@0 1947 {
michael@0 1948 body = child;
michael@0 1949 }
michael@0 1950 else if (nsEditor::NodeIsType(child, nsEditProperty::head))
michael@0 1951 {
michael@0 1952 head = child;
michael@0 1953 }
michael@0 1954 child->GetNextSibling(getter_AddRefs(tmp));
michael@0 1955 child = tmp;
michael@0 1956 }
michael@0 1957 if (head)
michael@0 1958 {
michael@0 1959 aNode->RemoveChild(head, getter_AddRefs(tmp));
michael@0 1960 }
michael@0 1961 if (body)
michael@0 1962 {
michael@0 1963 body->GetFirstChild(getter_AddRefs(child));
michael@0 1964 while (child)
michael@0 1965 {
michael@0 1966 aNode->InsertBefore(child, body, getter_AddRefs(tmp));
michael@0 1967 body->GetFirstChild(getter_AddRefs(child));
michael@0 1968 }
michael@0 1969 aNode->RemoveChild(body, getter_AddRefs(tmp));
michael@0 1970 }
michael@0 1971 }
michael@0 1972
michael@0 1973 /**
michael@0 1974 * This function finds the target node that we will be pasting into. aStart is
michael@0 1975 * the context that we're given and aResult will be the target. Initially,
michael@0 1976 * *aResult must be nullptr.
michael@0 1977 *
michael@0 1978 * The target for a paste is found by either finding the node that contains
michael@0 1979 * the magical comment node containing kInsertCookie or, failing that, the
michael@0 1980 * firstChild of the firstChild (until we reach a leaf).
michael@0 1981 */
michael@0 1982 nsresult FindTargetNode(nsIDOMNode *aStart, nsCOMPtr<nsIDOMNode> &aResult)
michael@0 1983 {
michael@0 1984 NS_ENSURE_TRUE(aStart, NS_OK);
michael@0 1985
michael@0 1986 nsCOMPtr<nsIDOMNode> child, tmp;
michael@0 1987
michael@0 1988 nsresult rv = aStart->GetFirstChild(getter_AddRefs(child));
michael@0 1989 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1990
michael@0 1991 if (!child)
michael@0 1992 {
michael@0 1993 // If the current result is nullptr, then aStart is a leaf, and is the
michael@0 1994 // fallback result.
michael@0 1995 if (!aResult)
michael@0 1996 aResult = aStart;
michael@0 1997
michael@0 1998 return NS_OK;
michael@0 1999 }
michael@0 2000
michael@0 2001 do
michael@0 2002 {
michael@0 2003 // Is this child the magical cookie?
michael@0 2004 nsCOMPtr<nsIDOMComment> comment = do_QueryInterface(child);
michael@0 2005 if (comment)
michael@0 2006 {
michael@0 2007 nsAutoString data;
michael@0 2008 rv = comment->GetData(data);
michael@0 2009 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2010
michael@0 2011 if (data.EqualsLiteral(kInsertCookie))
michael@0 2012 {
michael@0 2013 // Yes it is! Return an error so we bubble out and short-circuit the
michael@0 2014 // search.
michael@0 2015 aResult = aStart;
michael@0 2016
michael@0 2017 // Note: it doesn't matter if this fails.
michael@0 2018 aStart->RemoveChild(child, getter_AddRefs(tmp));
michael@0 2019
michael@0 2020 return NS_FOUND_TARGET;
michael@0 2021 }
michael@0 2022 }
michael@0 2023
michael@0 2024 // Note: Don't use NS_ENSURE_* here since we return a failure result to
michael@0 2025 // inicate that we found the magical cookie and we don't want to spam the
michael@0 2026 // console.
michael@0 2027 rv = FindTargetNode(child, aResult);
michael@0 2028 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2029
michael@0 2030 rv = child->GetNextSibling(getter_AddRefs(tmp));
michael@0 2031 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2032
michael@0 2033 child = tmp;
michael@0 2034 } while (child);
michael@0 2035
michael@0 2036 return NS_OK;
michael@0 2037 }
michael@0 2038
michael@0 2039 nsresult nsHTMLEditor::CreateDOMFragmentFromPaste(const nsAString &aInputString,
michael@0 2040 const nsAString & aContextStr,
michael@0 2041 const nsAString & aInfoStr,
michael@0 2042 nsCOMPtr<nsIDOMNode> *outFragNode,
michael@0 2043 nsCOMPtr<nsIDOMNode> *outStartNode,
michael@0 2044 nsCOMPtr<nsIDOMNode> *outEndNode,
michael@0 2045 int32_t *outStartOffset,
michael@0 2046 int32_t *outEndOffset,
michael@0 2047 bool aTrustedInput)
michael@0 2048 {
michael@0 2049 NS_ENSURE_TRUE(outFragNode && outStartNode && outEndNode, NS_ERROR_NULL_POINTER);
michael@0 2050 nsCOMPtr<nsIDOMDocumentFragment> docfrag;
michael@0 2051 nsCOMPtr<nsIDOMNode> contextAsNode, tmp;
michael@0 2052 nsresult rv = NS_OK;
michael@0 2053
michael@0 2054 nsCOMPtr<nsIDocument> doc = GetDocument();
michael@0 2055 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
michael@0 2056
michael@0 2057 // if we have context info, create a fragment for that
michael@0 2058 nsCOMPtr<nsIDOMDocumentFragment> contextfrag;
michael@0 2059 nsCOMPtr<nsIDOMNode> contextLeaf, junk;
michael@0 2060 if (!aContextStr.IsEmpty())
michael@0 2061 {
michael@0 2062 rv = ParseFragment(aContextStr, nullptr, doc, address_of(contextAsNode),
michael@0 2063 aTrustedInput);
michael@0 2064 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2065 NS_ENSURE_TRUE(contextAsNode, NS_ERROR_FAILURE);
michael@0 2066
michael@0 2067 rv = StripFormattingNodes(contextAsNode);
michael@0 2068 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2069
michael@0 2070 RemoveBodyAndHead(contextAsNode);
michael@0 2071
michael@0 2072 rv = FindTargetNode(contextAsNode, contextLeaf);
michael@0 2073 if (rv == NS_FOUND_TARGET) {
michael@0 2074 rv = NS_OK;
michael@0 2075 }
michael@0 2076 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2077 }
michael@0 2078
michael@0 2079 nsCOMPtr<nsIContent> contextLeafAsContent = do_QueryInterface(contextLeaf);
michael@0 2080
michael@0 2081 // create fragment for pasted html
michael@0 2082 nsIAtom* contextAtom;
michael@0 2083 if (contextLeafAsContent) {
michael@0 2084 contextAtom = contextLeafAsContent->Tag();
michael@0 2085 if (contextAtom == nsGkAtoms::html) {
michael@0 2086 contextAtom = nsGkAtoms::body;
michael@0 2087 }
michael@0 2088 } else {
michael@0 2089 contextAtom = nsGkAtoms::body;
michael@0 2090 }
michael@0 2091 rv = ParseFragment(aInputString,
michael@0 2092 contextAtom,
michael@0 2093 doc,
michael@0 2094 outFragNode,
michael@0 2095 aTrustedInput);
michael@0 2096 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2097 NS_ENSURE_TRUE(*outFragNode, NS_ERROR_FAILURE);
michael@0 2098
michael@0 2099 RemoveBodyAndHead(*outFragNode);
michael@0 2100
michael@0 2101 if (contextAsNode)
michael@0 2102 {
michael@0 2103 // unite the two trees
michael@0 2104 contextLeaf->AppendChild(*outFragNode, getter_AddRefs(junk));
michael@0 2105 *outFragNode = contextAsNode;
michael@0 2106 }
michael@0 2107
michael@0 2108 rv = StripFormattingNodes(*outFragNode, true);
michael@0 2109 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2110
michael@0 2111 // If there was no context, then treat all of the data we did get as the
michael@0 2112 // pasted data.
michael@0 2113 if (contextLeaf)
michael@0 2114 *outEndNode = *outStartNode = contextLeaf;
michael@0 2115 else
michael@0 2116 *outEndNode = *outStartNode = *outFragNode;
michael@0 2117
michael@0 2118 *outStartOffset = 0;
michael@0 2119
michael@0 2120 // get the infoString contents
michael@0 2121 nsAutoString numstr1, numstr2;
michael@0 2122 if (!aInfoStr.IsEmpty())
michael@0 2123 {
michael@0 2124 int32_t sep, num;
michael@0 2125 sep = aInfoStr.FindChar((char16_t)',');
michael@0 2126 numstr1 = Substring(aInfoStr, 0, sep);
michael@0 2127 numstr2 = Substring(aInfoStr, sep+1, aInfoStr.Length() - (sep+1));
michael@0 2128
michael@0 2129 // Move the start and end children.
michael@0 2130 nsresult err;
michael@0 2131 num = numstr1.ToInteger(&err);
michael@0 2132 while (num--)
michael@0 2133 {
michael@0 2134 (*outStartNode)->GetFirstChild(getter_AddRefs(tmp));
michael@0 2135 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
michael@0 2136 tmp.swap(*outStartNode);
michael@0 2137 }
michael@0 2138
michael@0 2139 num = numstr2.ToInteger(&err);
michael@0 2140 while (num--)
michael@0 2141 {
michael@0 2142 (*outEndNode)->GetLastChild(getter_AddRefs(tmp));
michael@0 2143 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
michael@0 2144 tmp.swap(*outEndNode);
michael@0 2145 }
michael@0 2146 }
michael@0 2147
michael@0 2148 GetLengthOfDOMNode(*outEndNode, (uint32_t&)*outEndOffset);
michael@0 2149 return NS_OK;
michael@0 2150 }
michael@0 2151
michael@0 2152
michael@0 2153 nsresult nsHTMLEditor::ParseFragment(const nsAString & aFragStr,
michael@0 2154 nsIAtom* aContextLocalName,
michael@0 2155 nsIDocument* aTargetDocument,
michael@0 2156 nsCOMPtr<nsIDOMNode> *outNode,
michael@0 2157 bool aTrustedInput)
michael@0 2158 {
michael@0 2159 nsAutoScriptBlockerSuppressNodeRemoved autoBlocker;
michael@0 2160
michael@0 2161 nsRefPtr<DocumentFragment> fragment =
michael@0 2162 new DocumentFragment(aTargetDocument->NodeInfoManager());
michael@0 2163 nsresult rv = nsContentUtils::ParseFragmentHTML(aFragStr,
michael@0 2164 fragment,
michael@0 2165 aContextLocalName ?
michael@0 2166 aContextLocalName : nsGkAtoms::body,
michael@0 2167 kNameSpaceID_XHTML,
michael@0 2168 false,
michael@0 2169 true);
michael@0 2170 if (!aTrustedInput) {
michael@0 2171 nsTreeSanitizer sanitizer(aContextLocalName ?
michael@0 2172 nsIParserUtils::SanitizerAllowStyle :
michael@0 2173 nsIParserUtils::SanitizerAllowComments);
michael@0 2174 sanitizer.Sanitize(fragment);
michael@0 2175 }
michael@0 2176 *outNode = fragment.forget();
michael@0 2177 return rv;
michael@0 2178 }
michael@0 2179
michael@0 2180 nsresult nsHTMLEditor::CreateListOfNodesToPaste(nsIDOMNode *aFragmentAsNode,
michael@0 2181 nsCOMArray<nsIDOMNode>& outNodeList,
michael@0 2182 nsIDOMNode *aStartNode,
michael@0 2183 int32_t aStartOffset,
michael@0 2184 nsIDOMNode *aEndNode,
michael@0 2185 int32_t aEndOffset)
michael@0 2186 {
michael@0 2187 NS_ENSURE_TRUE(aFragmentAsNode, NS_ERROR_NULL_POINTER);
michael@0 2188
michael@0 2189 nsresult rv;
michael@0 2190
michael@0 2191 // if no info was provided about the boundary between context and stream,
michael@0 2192 // then assume all is stream.
michael@0 2193 if (!aStartNode)
michael@0 2194 {
michael@0 2195 int32_t fragLen;
michael@0 2196 rv = GetLengthOfDOMNode(aFragmentAsNode, (uint32_t&)fragLen);
michael@0 2197 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2198
michael@0 2199 aStartNode = aFragmentAsNode;
michael@0 2200 aStartOffset = 0;
michael@0 2201 aEndNode = aFragmentAsNode;
michael@0 2202 aEndOffset = fragLen;
michael@0 2203 }
michael@0 2204
michael@0 2205 nsRefPtr<nsRange> docFragRange;
michael@0 2206 rv = nsRange::CreateRange(aStartNode, aStartOffset, aEndNode, aEndOffset, getter_AddRefs(docFragRange));
michael@0 2207 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2208
michael@0 2209 // now use a subtree iterator over the range to create a list of nodes
michael@0 2210 nsTrivialFunctor functor;
michael@0 2211 nsDOMSubtreeIterator iter;
michael@0 2212 rv = iter.Init(docFragRange);
michael@0 2213 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2214
michael@0 2215 return iter.AppendList(functor, outNodeList);
michael@0 2216 }
michael@0 2217
michael@0 2218 nsresult
michael@0 2219 nsHTMLEditor::GetListAndTableParents(bool aEnd,
michael@0 2220 nsCOMArray<nsIDOMNode>& aListOfNodes,
michael@0 2221 nsCOMArray<nsIDOMNode>& outArray)
michael@0 2222 {
michael@0 2223 int32_t listCount = aListOfNodes.Count();
michael@0 2224 NS_ENSURE_TRUE(listCount > 0, NS_ERROR_FAILURE); // no empty lists, please
michael@0 2225
michael@0 2226 // build up list of parents of first (or last) node in list
michael@0 2227 // that are either lists, or tables.
michael@0 2228 int32_t idx = 0;
michael@0 2229 if (aEnd) idx = listCount-1;
michael@0 2230
michael@0 2231 nsCOMPtr<nsIDOMNode> pNode = aListOfNodes[idx];
michael@0 2232 while (pNode)
michael@0 2233 {
michael@0 2234 if (nsHTMLEditUtils::IsList(pNode) || nsHTMLEditUtils::IsTable(pNode))
michael@0 2235 {
michael@0 2236 NS_ENSURE_TRUE(outArray.AppendObject(pNode), NS_ERROR_FAILURE);
michael@0 2237 }
michael@0 2238 nsCOMPtr<nsIDOMNode> parent;
michael@0 2239 pNode->GetParentNode(getter_AddRefs(parent));
michael@0 2240 pNode = parent;
michael@0 2241 }
michael@0 2242 return NS_OK;
michael@0 2243 }
michael@0 2244
michael@0 2245 nsresult
michael@0 2246 nsHTMLEditor::DiscoverPartialListsAndTables(nsCOMArray<nsIDOMNode>& aPasteNodes,
michael@0 2247 nsCOMArray<nsIDOMNode>& aListsAndTables,
michael@0 2248 int32_t *outHighWaterMark)
michael@0 2249 {
michael@0 2250 NS_ENSURE_TRUE(outHighWaterMark, NS_ERROR_NULL_POINTER);
michael@0 2251
michael@0 2252 *outHighWaterMark = -1;
michael@0 2253 int32_t listAndTableParents = aListsAndTables.Count();
michael@0 2254
michael@0 2255 // scan insertion list for table elements (other than table).
michael@0 2256 int32_t listCount = aPasteNodes.Count();
michael@0 2257 int32_t j;
michael@0 2258 for (j=0; j<listCount; j++)
michael@0 2259 {
michael@0 2260 nsCOMPtr<nsIDOMNode> curNode = aPasteNodes[j];
michael@0 2261
michael@0 2262 NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE);
michael@0 2263 if (nsHTMLEditUtils::IsTableElement(curNode) && !nsHTMLEditUtils::IsTable(curNode))
michael@0 2264 {
michael@0 2265 nsCOMPtr<nsIDOMNode> theTable = GetTableParent(curNode);
michael@0 2266 if (theTable)
michael@0 2267 {
michael@0 2268 int32_t indexT = aListsAndTables.IndexOf(theTable);
michael@0 2269 if (indexT >= 0)
michael@0 2270 {
michael@0 2271 *outHighWaterMark = indexT;
michael@0 2272 if (*outHighWaterMark == listAndTableParents-1) break;
michael@0 2273 }
michael@0 2274 else
michael@0 2275 {
michael@0 2276 break;
michael@0 2277 }
michael@0 2278 }
michael@0 2279 }
michael@0 2280 if (nsHTMLEditUtils::IsListItem(curNode))
michael@0 2281 {
michael@0 2282 nsCOMPtr<nsIDOMNode> theList = GetListParent(curNode);
michael@0 2283 if (theList)
michael@0 2284 {
michael@0 2285 int32_t indexL = aListsAndTables.IndexOf(theList);
michael@0 2286 if (indexL >= 0)
michael@0 2287 {
michael@0 2288 *outHighWaterMark = indexL;
michael@0 2289 if (*outHighWaterMark == listAndTableParents-1) break;
michael@0 2290 }
michael@0 2291 else
michael@0 2292 {
michael@0 2293 break;
michael@0 2294 }
michael@0 2295 }
michael@0 2296 }
michael@0 2297 }
michael@0 2298 return NS_OK;
michael@0 2299 }
michael@0 2300
michael@0 2301 nsresult
michael@0 2302 nsHTMLEditor::ScanForListAndTableStructure( bool aEnd,
michael@0 2303 nsCOMArray<nsIDOMNode>& aNodes,
michael@0 2304 nsIDOMNode *aListOrTable,
michael@0 2305 nsCOMPtr<nsIDOMNode> *outReplaceNode)
michael@0 2306 {
michael@0 2307 NS_ENSURE_TRUE(aListOrTable, NS_ERROR_NULL_POINTER);
michael@0 2308 NS_ENSURE_TRUE(outReplaceNode, NS_ERROR_NULL_POINTER);
michael@0 2309
michael@0 2310 *outReplaceNode = 0;
michael@0 2311
michael@0 2312 // look upward from first/last paste node for a piece of this list/table
michael@0 2313 int32_t listCount = aNodes.Count(), idx = 0;
michael@0 2314 if (aEnd) idx = listCount-1;
michael@0 2315 bool bList = nsHTMLEditUtils::IsList(aListOrTable);
michael@0 2316
michael@0 2317 nsCOMPtr<nsIDOMNode> pNode = aNodes[idx];
michael@0 2318 nsCOMPtr<nsIDOMNode> originalNode = pNode;
michael@0 2319 while (pNode)
michael@0 2320 {
michael@0 2321 if ((bList && nsHTMLEditUtils::IsListItem(pNode)) ||
michael@0 2322 (!bList && (nsHTMLEditUtils::IsTableElement(pNode) && !nsHTMLEditUtils::IsTable(pNode))))
michael@0 2323 {
michael@0 2324 nsCOMPtr<nsIDOMNode> structureNode;
michael@0 2325 if (bList) structureNode = GetListParent(pNode);
michael@0 2326 else structureNode = GetTableParent(pNode);
michael@0 2327 if (structureNode == aListOrTable)
michael@0 2328 {
michael@0 2329 if (bList)
michael@0 2330 *outReplaceNode = structureNode;
michael@0 2331 else
michael@0 2332 *outReplaceNode = pNode;
michael@0 2333 break;
michael@0 2334 }
michael@0 2335 }
michael@0 2336 nsCOMPtr<nsIDOMNode> parent;
michael@0 2337 pNode->GetParentNode(getter_AddRefs(parent));
michael@0 2338 pNode = parent;
michael@0 2339 }
michael@0 2340 return NS_OK;
michael@0 2341 }
michael@0 2342
michael@0 2343 nsresult
michael@0 2344 nsHTMLEditor::ReplaceOrphanedStructure(bool aEnd,
michael@0 2345 nsCOMArray<nsIDOMNode>& aNodeArray,
michael@0 2346 nsCOMArray<nsIDOMNode>& aListAndTableArray,
michael@0 2347 int32_t aHighWaterMark)
michael@0 2348 {
michael@0 2349 nsCOMPtr<nsIDOMNode> curNode = aListAndTableArray[aHighWaterMark];
michael@0 2350 NS_ENSURE_TRUE(curNode, NS_ERROR_NULL_POINTER);
michael@0 2351
michael@0 2352 nsCOMPtr<nsIDOMNode> replaceNode, originalNode;
michael@0 2353
michael@0 2354 // find substructure of list or table that must be included in paste.
michael@0 2355 nsresult rv = ScanForListAndTableStructure(aEnd, aNodeArray,
michael@0 2356 curNode, address_of(replaceNode));
michael@0 2357 NS_ENSURE_SUCCESS(rv, rv);
michael@0 2358
michael@0 2359 // if we found substructure, paste it instead of its descendants
michael@0 2360 if (replaceNode)
michael@0 2361 {
michael@0 2362 // postprocess list to remove any descendants of this node
michael@0 2363 // so that we don't insert them twice.
michael@0 2364 nsCOMPtr<nsIDOMNode> endpoint;
michael@0 2365 do
michael@0 2366 {
michael@0 2367 endpoint = GetArrayEndpoint(aEnd, aNodeArray);
michael@0 2368 if (!endpoint) break;
michael@0 2369 if (nsEditorUtils::IsDescendantOf(endpoint, replaceNode))
michael@0 2370 aNodeArray.RemoveObject(endpoint);
michael@0 2371 else
michael@0 2372 break;
michael@0 2373 } while(endpoint);
michael@0 2374
michael@0 2375 // now replace the removed nodes with the structural parent
michael@0 2376 if (aEnd) aNodeArray.AppendObject(replaceNode);
michael@0 2377 else aNodeArray.InsertObjectAt(replaceNode, 0);
michael@0 2378 }
michael@0 2379 return NS_OK;
michael@0 2380 }
michael@0 2381
michael@0 2382 nsIDOMNode* nsHTMLEditor::GetArrayEndpoint(bool aEnd,
michael@0 2383 nsCOMArray<nsIDOMNode>& aNodeArray)
michael@0 2384 {
michael@0 2385 int32_t listCount = aNodeArray.Count();
michael@0 2386 if (listCount <= 0) {
michael@0 2387 return nullptr;
michael@0 2388 }
michael@0 2389
michael@0 2390 if (aEnd) {
michael@0 2391 return aNodeArray[listCount-1];
michael@0 2392 }
michael@0 2393
michael@0 2394 return aNodeArray[0];
michael@0 2395 }

mercurial