michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=78: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: michael@0: #include "mozilla/dom/DocumentFragment.h" michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/Base64.h" michael@0: #include "mozilla/BasicEvents.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/dom/Selection.h" michael@0: #include "nsAString.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCOMArray.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsCRT.h" michael@0: #include "nsCRTGlue.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsDebug.h" michael@0: #include "nsDependentSubstring.h" michael@0: #include "nsEditProperty.h" michael@0: #include "nsEditRules.h" michael@0: #include "nsEditor.h" michael@0: #include "nsEditorUtils.h" michael@0: #include "nsError.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsHTMLEditUtils.h" michael@0: #include "nsHTMLEditor.h" michael@0: #include "nsIClipboard.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIContentFilter.h" michael@0: #include "nsIDOMComment.h" michael@0: #include "mozilla/dom/DOMStringList.h" michael@0: #include "mozilla/dom/DataTransfer.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMDocumentFragment.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMHTMLAnchorElement.h" michael@0: #include "nsIDOMHTMLEmbedElement.h" michael@0: #include "nsIDOMHTMLFrameElement.h" michael@0: #include "nsIDOMHTMLIFrameElement.h" michael@0: #include "nsIDOMHTMLImageElement.h" michael@0: #include "nsIDOMHTMLInputElement.h" michael@0: #include "nsIDOMHTMLLinkElement.h" michael@0: #include "nsIDOMHTMLObjectElement.h" michael@0: #include "nsIDOMHTMLScriptElement.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMRange.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIEditor.h" michael@0: #include "nsIEditorIMESupport.h" michael@0: #include "nsIEditorMailSupport.h" michael@0: #include "nsIFile.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsIMIMEService.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsINode.h" michael@0: #include "nsIParserUtils.h" michael@0: #include "nsIPlaintextEditor.h" michael@0: #include "nsISelection.h" michael@0: #include "nsISupportsImpl.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsISupportsUtils.h" michael@0: #include "nsITransferable.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIVariant.h" michael@0: #include "nsLinebreakConverter.h" michael@0: #include "nsLiteralString.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsPlaintextEditor.h" michael@0: #include "nsRange.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsSelectionState.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsStringFwd.h" michael@0: #include "nsStringIterator.h" michael@0: #include "nsSubstringTuple.h" michael@0: #include "nsTextEditRules.h" michael@0: #include "nsTextEditUtils.h" michael@0: #include "nsTreeSanitizer.h" michael@0: #include "nsWSRunObject.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nscore.h" michael@0: michael@0: class nsIAtom; michael@0: class nsILoadContext; michael@0: class nsISupports; michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: #define kInsertCookie "_moz_Insert Here_moz_" michael@0: michael@0: // some little helpers michael@0: static bool FindIntegerAfterString(const char *aLeadingString, michael@0: nsCString &aCStr, int32_t &foundNumber); michael@0: static nsresult RemoveFragComments(nsCString &theStr); michael@0: static void RemoveBodyAndHead(nsIDOMNode *aNode); michael@0: static nsresult FindTargetNode(nsIDOMNode *aStart, nsCOMPtr &aResult); michael@0: michael@0: static nsCOMPtr GetListParent(nsIDOMNode* aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, nullptr); michael@0: nsCOMPtr parent, tmp; michael@0: aNode->GetParentNode(getter_AddRefs(parent)); michael@0: while (parent) michael@0: { michael@0: if (nsHTMLEditUtils::IsList(parent)) { michael@0: return parent; michael@0: } michael@0: parent->GetParentNode(getter_AddRefs(tmp)); michael@0: parent = tmp; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: static nsCOMPtr GetTableParent(nsIDOMNode* aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, nullptr); michael@0: nsCOMPtr parent, tmp; michael@0: aNode->GetParentNode(getter_AddRefs(parent)); michael@0: while (parent) michael@0: { michael@0: if (nsHTMLEditUtils::IsTable(parent)) { michael@0: return parent; michael@0: } michael@0: parent->GetParentNode(getter_AddRefs(tmp)); michael@0: parent = tmp; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::LoadHTML(const nsAString & aInputString) michael@0: { michael@0: NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // force IME commit; set up rules sniffing and batching michael@0: ForceCompositionEnd(); michael@0: nsAutoEditBatch beginBatching(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::loadHTML, nsIEditor::eNext); michael@0: michael@0: // Get selection michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_STATE(selection); michael@0: michael@0: nsTextRulesInfo ruleInfo(EditAction::loadHTML); michael@0: bool cancel, handled; michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (cancel) { michael@0: return NS_OK; // rules canceled the operation michael@0: } michael@0: michael@0: if (!handled) michael@0: { michael@0: // Delete Selection, but only if it isn't collapsed, see bug #106269 michael@0: if (!selection->Collapsed()) { michael@0: rv = DeleteSelection(eNone, eStrip); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Get the first range in the selection, for context: michael@0: nsCOMPtr range; michael@0: rv = selection->GetRangeAt(0, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER); michael@0: michael@0: // create fragment for pasted html michael@0: nsCOMPtr docfrag; michael@0: { michael@0: rv = range->CreateContextualFragment(aInputString, getter_AddRefs(docfrag)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: // put the fragment into the document michael@0: nsCOMPtr parent, junk; michael@0: rv = range->GetStartContainer(getter_AddRefs(parent)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); michael@0: int32_t childOffset; michael@0: rv = range->GetStartOffset(&childOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr nodeToInsert; michael@0: docfrag->GetFirstChild(getter_AddRefs(nodeToInsert)); michael@0: while (nodeToInsert) michael@0: { michael@0: rv = InsertNode(nodeToInsert, parent, childOffset++); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: docfrag->GetFirstChild(getter_AddRefs(nodeToInsert)); michael@0: } michael@0: } michael@0: michael@0: return mRules->DidDoAction(selection, &ruleInfo, rv); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::InsertHTML(const nsAString & aInString) michael@0: { michael@0: const nsAFlatString& empty = EmptyString(); michael@0: michael@0: return InsertHTMLWithContext(aInString, empty, empty, empty, michael@0: nullptr, nullptr, 0, true); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::InsertHTMLWithContext(const nsAString & aInputString, michael@0: const nsAString & aContextStr, michael@0: const nsAString & aInfoStr, michael@0: const nsAString & aFlavor, michael@0: nsIDOMDocument *aSourceDoc, michael@0: nsIDOMNode *aDestNode, michael@0: int32_t aDestOffset, michael@0: bool aDeleteSelection) michael@0: { michael@0: return DoInsertHTMLWithContext(aInputString, aContextStr, aInfoStr, michael@0: aFlavor, aSourceDoc, aDestNode, aDestOffset, aDeleteSelection, michael@0: /* trusted input */ true, /* clear style */ false); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::DoInsertHTMLWithContext(const nsAString & aInputString, michael@0: const nsAString & aContextStr, michael@0: const nsAString & aInfoStr, michael@0: const nsAString & aFlavor, michael@0: nsIDOMDocument *aSourceDoc, michael@0: nsIDOMNode *aDestNode, michael@0: int32_t aDestOffset, michael@0: bool aDeleteSelection, michael@0: bool aTrustedInput, michael@0: bool aClearStyle) michael@0: { michael@0: NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // Prevent the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: // force IME commit; set up rules sniffing and batching michael@0: ForceCompositionEnd(); michael@0: nsAutoEditBatch beginBatching(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::htmlPaste, nsIEditor::eNext); michael@0: michael@0: // Get selection michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_STATE(selection); michael@0: michael@0: // create a dom document fragment that represents the structure to paste michael@0: nsCOMPtr fragmentAsNode, streamStartParent, streamEndParent; michael@0: int32_t streamStartOffset = 0, streamEndOffset = 0; michael@0: michael@0: nsresult rv = CreateDOMFragmentFromPaste(aInputString, aContextStr, aInfoStr, michael@0: address_of(fragmentAsNode), michael@0: address_of(streamStartParent), michael@0: address_of(streamEndParent), michael@0: &streamStartOffset, michael@0: &streamEndOffset, michael@0: aTrustedInput); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr targetNode, tempNode; michael@0: int32_t targetOffset=0; michael@0: michael@0: if (!aDestNode) michael@0: { michael@0: // if caller didn't provide the destination/target node, michael@0: // fetch the paste insertion point from our selection michael@0: rv = GetStartNodeAndOffset(selection, getter_AddRefs(targetNode), &targetOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!targetNode || !IsEditable(targetNode)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: targetNode = aDestNode; michael@0: targetOffset = aDestOffset; michael@0: } michael@0: michael@0: bool doContinue = true; michael@0: michael@0: rv = DoContentFilterCallback(aFlavor, aSourceDoc, aDeleteSelection, michael@0: (nsIDOMNode **)address_of(fragmentAsNode), michael@0: (nsIDOMNode **)address_of(streamStartParent), michael@0: &streamStartOffset, michael@0: (nsIDOMNode **)address_of(streamEndParent), michael@0: &streamEndOffset, michael@0: (nsIDOMNode **)address_of(targetNode), michael@0: &targetOffset, &doContinue); michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(doContinue, NS_OK); michael@0: michael@0: // if we have a destination / target node, we want to insert there michael@0: // rather than in place of the selection michael@0: // ignore aDeleteSelection here if no aDestNode since deletion will michael@0: // also occur later; this block is intended to cover the various michael@0: // scenarios where we are dropping in an editor (and may want to delete michael@0: // the selection before collapsing the selection in the new destination) michael@0: if (aDestNode) michael@0: { michael@0: if (aDeleteSelection) michael@0: { michael@0: // Use an auto tracker so that our drop point is correctly michael@0: // positioned after the delete. michael@0: nsAutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset); michael@0: rv = DeleteSelection(eNone, eStrip); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = selection->Collapse(targetNode, targetOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // we need to recalculate various things based on potentially new offsets michael@0: // this is work to be completed at a later date (probably by jfrancis) michael@0: michael@0: // make a list of what nodes in docFrag we need to move michael@0: nsCOMArray nodeList; michael@0: rv = CreateListOfNodesToPaste(fragmentAsNode, nodeList, michael@0: streamStartParent, streamStartOffset, michael@0: streamEndParent, streamEndOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (nodeList.Count() == 0) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Are there any table elements in the list? michael@0: // node and offset for insertion michael@0: nsCOMPtr parentNode; michael@0: int32_t offsetOfNewNode; michael@0: michael@0: // check for table cell selection mode michael@0: bool cellSelectionMode = false; michael@0: nsCOMPtr cell; michael@0: rv = GetFirstSelectedCell(nullptr, getter_AddRefs(cell)); michael@0: if (NS_SUCCEEDED(rv) && cell) michael@0: { michael@0: cellSelectionMode = true; michael@0: } michael@0: michael@0: if (cellSelectionMode) michael@0: { michael@0: // do we have table content to paste? If so, we want to delete michael@0: // the selected table cells and replace with new table elements; michael@0: // but if not we want to delete _contents_ of cells and replace michael@0: // with non-table elements. Use cellSelectionMode bool to michael@0: // indicate results. michael@0: nsIDOMNode* firstNode = nodeList[0]; michael@0: if (!nsHTMLEditUtils::IsTableElement(firstNode)) michael@0: cellSelectionMode = false; michael@0: } michael@0: michael@0: if (!cellSelectionMode) michael@0: { michael@0: rv = DeleteSelectionAndPrepareToCreateNode(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (aClearStyle) { michael@0: // pasting does not inherit local inline styles michael@0: nsCOMPtr tmpNode = michael@0: do_QueryInterface(selection->GetAnchorNode()); michael@0: int32_t tmpOffset = static_cast(selection->AnchorOffset()); michael@0: rv = ClearStyle(address_of(tmpNode), &tmpOffset, nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // delete whole cells: we will replace with new table content michael@0: { // Braces for artificial block to scope nsAutoSelectionReset. michael@0: // Save current selection since DeleteTableCell perturbs it michael@0: nsAutoSelectionReset selectionResetter(selection, this); michael@0: rv = DeleteTableCell(1); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: // collapse selection to beginning of deleted table content michael@0: selection->CollapseToStart(); michael@0: } michael@0: michael@0: // give rules a chance to handle or cancel michael@0: nsTextRulesInfo ruleInfo(EditAction::insertElement); michael@0: bool cancel, handled; michael@0: rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (cancel) { michael@0: return NS_OK; // rules canceled the operation michael@0: } michael@0: michael@0: if (!handled) michael@0: { michael@0: // The rules code (WillDoAction above) might have changed the selection. michael@0: // refresh our memory... michael@0: rv = GetStartNodeAndOffset(selection, getter_AddRefs(parentNode), &offsetOfNewNode); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(parentNode, NS_ERROR_FAILURE); michael@0: michael@0: // Adjust position based on the first node we are going to insert. michael@0: NormalizeEOLInsertPosition(nodeList[0], address_of(parentNode), &offsetOfNewNode); michael@0: michael@0: // if there are any invisible br's after our insertion point, remove them. michael@0: // this is because if there is a br at end of what we paste, it will make michael@0: // the invisible br visible. michael@0: nsWSRunObject wsObj(this, parentNode, offsetOfNewNode); michael@0: if (nsTextEditUtils::IsBreak(wsObj.mEndReasonNode) && michael@0: !IsVisBreak(wsObj.mEndReasonNode) ) michael@0: { michael@0: rv = DeleteNode(wsObj.mEndReasonNode); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Remember if we are in a link. michael@0: bool bStartedInLink = IsInLink(parentNode); michael@0: michael@0: // Are we in a text node? If so, split it. michael@0: if (IsTextNode(parentNode)) michael@0: { michael@0: nsCOMPtr temp; michael@0: rv = SplitNodeDeep(parentNode, parentNode, offsetOfNewNode, &offsetOfNewNode); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = parentNode->GetParentNode(getter_AddRefs(temp)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: parentNode = temp; michael@0: } michael@0: michael@0: // build up list of parents of first node in list that are either michael@0: // lists or tables. First examine front of paste node list. michael@0: nsCOMArray startListAndTableArray; michael@0: rv = GetListAndTableParents(false, nodeList, startListAndTableArray); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // remember number of lists and tables above us michael@0: int32_t highWaterMark = -1; michael@0: if (startListAndTableArray.Count() > 0) michael@0: { michael@0: rv = DiscoverPartialListsAndTables(nodeList, startListAndTableArray, &highWaterMark); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // if we have pieces of tables or lists to be inserted, let's force the paste michael@0: // to deal with table elements right away, so that it doesn't orphan some michael@0: // table or list contents outside the table or list. michael@0: if (highWaterMark >= 0) michael@0: { michael@0: rv = ReplaceOrphanedStructure(false, nodeList, startListAndTableArray, highWaterMark); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Now go through the same process again for the end of the paste node list. michael@0: nsCOMArray endListAndTableArray; michael@0: rv = GetListAndTableParents(true, nodeList, endListAndTableArray); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: highWaterMark = -1; michael@0: michael@0: // remember number of lists and tables above us michael@0: if (endListAndTableArray.Count() > 0) michael@0: { michael@0: rv = DiscoverPartialListsAndTables(nodeList, endListAndTableArray, &highWaterMark); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // don't orphan partial list or table structure michael@0: if (highWaterMark >= 0) michael@0: { michael@0: rv = ReplaceOrphanedStructure(true, nodeList, endListAndTableArray, highWaterMark); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Loop over the node list and paste the nodes: michael@0: nsCOMPtr parentBlock, lastInsertNode, insertedContextParent; michael@0: int32_t listCount = nodeList.Count(); michael@0: int32_t j; michael@0: if (IsBlockNode(parentNode)) michael@0: parentBlock = parentNode; michael@0: else michael@0: parentBlock = GetBlockNodeParent(parentNode); michael@0: michael@0: for (j=0; j curNode = nodeList[j]; michael@0: michael@0: NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE); michael@0: NS_ENSURE_TRUE(curNode != fragmentAsNode, NS_ERROR_FAILURE); michael@0: NS_ENSURE_TRUE(!nsTextEditUtils::IsBody(curNode), NS_ERROR_FAILURE); michael@0: michael@0: if (insertedContextParent) michael@0: { michael@0: // if we had to insert something higher up in the paste hierarchy, we want to michael@0: // skip any further paste nodes that descend from that. Else we will paste twice. michael@0: if (nsEditorUtils::IsDescendantOf(curNode, insertedContextParent)) michael@0: continue; michael@0: } michael@0: michael@0: // give the user a hand on table element insertion. if they have michael@0: // a table or table row on the clipboard, and are trying to insert michael@0: // into a table or table row, insert the appropriate children instead. michael@0: if ( (nsHTMLEditUtils::IsTableRow(curNode) && nsHTMLEditUtils::IsTableRow(parentNode)) michael@0: && (nsHTMLEditUtils::IsTable(curNode) || nsHTMLEditUtils::IsTable(parentNode)) ) michael@0: { michael@0: nsCOMPtr child; michael@0: curNode->GetFirstChild(getter_AddRefs(child)); michael@0: while (child) michael@0: { michael@0: rv = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, true); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: bDidInsert = true; michael@0: lastInsertNode = child; michael@0: offsetOfNewNode++; michael@0: michael@0: curNode->GetFirstChild(getter_AddRefs(child)); michael@0: } michael@0: } michael@0: // give the user a hand on list insertion. if they have michael@0: // a list on the clipboard, and are trying to insert michael@0: // into a list or list item, insert the appropriate children instead, michael@0: // ie, merge the lists instead of pasting in a sublist. michael@0: else if (nsHTMLEditUtils::IsList(curNode) && michael@0: (nsHTMLEditUtils::IsList(parentNode) || nsHTMLEditUtils::IsListItem(parentNode)) ) michael@0: { michael@0: nsCOMPtr child, tmp; michael@0: curNode->GetFirstChild(getter_AddRefs(child)); michael@0: while (child) michael@0: { michael@0: if (nsHTMLEditUtils::IsListItem(child) || nsHTMLEditUtils::IsList(child)) michael@0: { michael@0: // Check if we are pasting into empty list item. If so michael@0: // delete it and paste into parent list instead. michael@0: if (nsHTMLEditUtils::IsListItem(parentNode)) michael@0: { michael@0: bool isEmpty; michael@0: rv = IsEmptyNode(parentNode, &isEmpty, true); michael@0: if (NS_SUCCEEDED(rv) && isEmpty) michael@0: { michael@0: int32_t newOffset; michael@0: nsCOMPtr listNode = GetNodeLocation(parentNode, &newOffset); michael@0: if (listNode) michael@0: { michael@0: DeleteNode(parentNode); michael@0: parentNode = listNode; michael@0: offsetOfNewNode = newOffset; michael@0: } michael@0: } michael@0: } michael@0: rv = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, true); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: bDidInsert = true; michael@0: lastInsertNode = child; michael@0: offsetOfNewNode++; michael@0: } michael@0: else michael@0: { michael@0: curNode->RemoveChild(child, getter_AddRefs(tmp)); michael@0: } michael@0: curNode->GetFirstChild(getter_AddRefs(child)); michael@0: } michael@0: michael@0: } michael@0: // Check for pre's going into pre's. michael@0: else if (nsHTMLEditUtils::IsPre(parentBlock) && nsHTMLEditUtils::IsPre(curNode)) michael@0: { michael@0: nsCOMPtr child, tmp; michael@0: curNode->GetFirstChild(getter_AddRefs(child)); michael@0: while (child) michael@0: { michael@0: rv = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, true); michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: bDidInsert = true; michael@0: lastInsertNode = child; michael@0: offsetOfNewNode++; michael@0: michael@0: curNode->GetFirstChild(getter_AddRefs(child)); michael@0: } michael@0: } michael@0: michael@0: if (!bDidInsert || NS_FAILED(rv)) michael@0: { michael@0: // try to insert michael@0: rv = InsertNodeAtPoint(curNode, address_of(parentNode), &offsetOfNewNode, true); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: bDidInsert = true; michael@0: lastInsertNode = curNode; michael@0: } michael@0: michael@0: // Assume failure means no legal parent in the document hierarchy, michael@0: // try again with the parent of curNode in the paste hierarchy. michael@0: nsCOMPtr parent; michael@0: while (NS_FAILED(rv) && curNode) michael@0: { michael@0: curNode->GetParentNode(getter_AddRefs(parent)); michael@0: if (parent && !nsTextEditUtils::IsBody(parent)) michael@0: { michael@0: rv = InsertNodeAtPoint(parent, address_of(parentNode), &offsetOfNewNode, true); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: bDidInsert = true; michael@0: insertedContextParent = parent; michael@0: lastInsertNode = GetChildAt(parentNode, offsetOfNewNode); michael@0: } michael@0: } michael@0: curNode = parent; michael@0: } michael@0: } michael@0: if (lastInsertNode) michael@0: { michael@0: parentNode = GetNodeLocation(lastInsertNode, &offsetOfNewNode); michael@0: offsetOfNewNode++; michael@0: } michael@0: } michael@0: michael@0: // Now collapse the selection to the end of what we just inserted: michael@0: if (lastInsertNode) michael@0: { michael@0: // set selection to the end of what we just pasted. michael@0: nsCOMPtr selNode, tmp, visNode, highTable; michael@0: int32_t selOffset; michael@0: michael@0: // but don't cross tables michael@0: if (!nsHTMLEditUtils::IsTable(lastInsertNode)) michael@0: { michael@0: rv = GetLastEditableLeaf(lastInsertNode, address_of(selNode)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: tmp = selNode; michael@0: while (tmp && (tmp != lastInsertNode)) michael@0: { michael@0: if (nsHTMLEditUtils::IsTable(tmp)) michael@0: highTable = tmp; michael@0: nsCOMPtr parent = tmp; michael@0: tmp->GetParentNode(getter_AddRefs(parent)); michael@0: tmp = parent; michael@0: } michael@0: if (highTable) michael@0: selNode = highTable; michael@0: } michael@0: if (!selNode) michael@0: selNode = lastInsertNode; michael@0: if (IsTextNode(selNode) || (IsContainer(selNode) && !nsHTMLEditUtils::IsTable(selNode))) michael@0: { michael@0: rv = GetLengthOfDOMNode(selNode, (uint32_t&)selOffset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else // we need to find a container for selection. Look up. michael@0: { michael@0: tmp = selNode; michael@0: selNode = GetNodeLocation(tmp, &selOffset); michael@0: ++selOffset; // want to be *after* last leaf node in paste michael@0: } michael@0: michael@0: // make sure we don't end up with selection collapsed after an invisible break node michael@0: nsWSRunObject wsRunObj(this, selNode, selOffset); michael@0: int32_t outVisOffset=0; michael@0: WSType visType; michael@0: wsRunObj.PriorVisibleNode(selNode, selOffset, address_of(visNode), michael@0: &outVisOffset, &visType); michael@0: if (visType == WSType::br) { michael@0: // we are after a break. Is it visible? Despite the name, michael@0: // PriorVisibleNode does not make that determination for breaks. michael@0: // It also may not return the break in visNode. We have to pull it michael@0: // out of the nsWSRunObject's state. michael@0: if (!IsVisBreak(wsRunObj.mStartReasonNode)) michael@0: { michael@0: // don't leave selection past an invisible break; michael@0: // reset {selNode,selOffset} to point before break michael@0: selNode = GetNodeLocation(wsRunObj.mStartReasonNode, &selOffset); michael@0: // we want to be inside any inline style prior to break michael@0: nsWSRunObject wsRunObj(this, selNode, selOffset); michael@0: wsRunObj.PriorVisibleNode(selNode, selOffset, address_of(visNode), michael@0: &outVisOffset, &visType); michael@0: if (visType == WSType::text || visType == WSType::normalWS) { michael@0: selNode = visNode; michael@0: selOffset = outVisOffset; // PriorVisibleNode already set offset to _after_ the text or ws michael@0: } else if (visType == WSType::special) { michael@0: // prior visible thing is an image or some other non-text thingy. michael@0: // We want to be right after it. michael@0: selNode = GetNodeLocation(wsRunObj.mStartReasonNode, &selOffset); michael@0: ++selOffset; michael@0: } michael@0: } michael@0: } michael@0: selection->Collapse(selNode, selOffset); michael@0: michael@0: // if we just pasted a link, discontinue link style michael@0: nsCOMPtr link; michael@0: if (!bStartedInLink && IsInLink(selNode, address_of(link))) michael@0: { michael@0: // so, if we just pasted a link, I split it. Why do that instead of just michael@0: // nudging selection point beyond it? Because it might have ended in a BR michael@0: // that is not visible. If so, the code above just placed selection michael@0: // inside that. So I split it instead. michael@0: nsCOMPtr leftLink; michael@0: int32_t linkOffset; michael@0: rv = SplitNodeDeep(link, selNode, selOffset, &linkOffset, true, address_of(leftLink)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: selNode = GetNodeLocation(leftLink, &selOffset); michael@0: selection->Collapse(selNode, selOffset+1); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return mRules->DidDoAction(selection, &ruleInfo, rv); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::AddInsertionListener(nsIContentFilter *aListener) michael@0: { michael@0: NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER); michael@0: michael@0: // don't let a listener be added more than once michael@0: if (mContentFilters.IndexOfObject(aListener) == -1) michael@0: { michael@0: NS_ENSURE_TRUE(mContentFilters.AppendObject(aListener), NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::RemoveInsertionListener(nsIContentFilter *aListener) michael@0: { michael@0: NS_ENSURE_TRUE(aListener, NS_ERROR_FAILURE); michael@0: michael@0: NS_ENSURE_TRUE(mContentFilters.RemoveObject(aListener), NS_ERROR_FAILURE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::DoContentFilterCallback(const nsAString &aFlavor, michael@0: nsIDOMDocument *sourceDoc, michael@0: bool aWillDeleteSelection, michael@0: nsIDOMNode **aFragmentAsNode, michael@0: nsIDOMNode **aFragStartNode, michael@0: int32_t *aFragStartOffset, michael@0: nsIDOMNode **aFragEndNode, michael@0: int32_t *aFragEndOffset, michael@0: nsIDOMNode **aTargetNode, michael@0: int32_t *aTargetOffset, michael@0: bool *aDoContinue) michael@0: { michael@0: *aDoContinue = true; michael@0: michael@0: int32_t i; michael@0: nsIContentFilter *listener; michael@0: for (i=0; i < mContentFilters.Count() && *aDoContinue; i++) michael@0: { michael@0: listener = (nsIContentFilter *)mContentFilters[i]; michael@0: if (listener) michael@0: listener->NotifyOfInsertion(aFlavor, nullptr, sourceDoc, michael@0: aWillDeleteSelection, aFragmentAsNode, michael@0: aFragStartNode, aFragStartOffset, michael@0: aFragEndNode, aFragEndOffset, michael@0: aTargetNode, aTargetOffset, aDoContinue); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::IsInLink(nsIDOMNode *aNode, nsCOMPtr *outLink) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, false); michael@0: if (outLink) michael@0: *outLink = nullptr; michael@0: nsCOMPtr tmp, node = aNode; michael@0: while (node) michael@0: { michael@0: if (nsHTMLEditUtils::IsLink(node)) michael@0: { michael@0: if (outLink) michael@0: *outLink = node; michael@0: return true; michael@0: } michael@0: tmp = node; michael@0: tmp->GetParentNode(getter_AddRefs(node)); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::StripFormattingNodes(nsIDOMNode *aNode, bool aListOnly) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr content = do_QueryInterface(aNode); michael@0: if (content->TextIsOnlyWhitespace()) michael@0: { michael@0: nsCOMPtr parent, ignored; michael@0: aNode->GetParentNode(getter_AddRefs(parent)); michael@0: if (parent) michael@0: { michael@0: if (!aListOnly || nsHTMLEditUtils::IsList(parent)) { michael@0: return parent->RemoveChild(aNode, getter_AddRefs(ignored)); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: if (!nsHTMLEditUtils::IsPre(aNode)) michael@0: { michael@0: nsCOMPtr child; michael@0: aNode->GetLastChild(getter_AddRefs(child)); michael@0: michael@0: while (child) michael@0: { michael@0: nsCOMPtr tmp; michael@0: child->GetPreviousSibling(getter_AddRefs(tmp)); michael@0: nsresult rv = StripFormattingNodes(child, aListOnly); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: child = tmp; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::PrepareTransferable(nsITransferable **transferable) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::PrepareHTMLTransferable(nsITransferable **aTransferable, michael@0: bool aHavePrivFlavor) michael@0: { michael@0: // Create generic Transferable for getting the data michael@0: nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", aTransferable); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Get the nsITransferable interface for getting the data from the clipboard michael@0: if (aTransferable) michael@0: { michael@0: nsCOMPtr destdoc = GetDocument(); michael@0: nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr; michael@0: (*aTransferable)->Init(loadContext); michael@0: michael@0: // Create the desired DataFlavor for the type of data michael@0: // we want to get out of the transferable michael@0: // This should only happen in html editors, not plaintext michael@0: if (!IsPlaintextEditor()) michael@0: { michael@0: if (!aHavePrivFlavor) michael@0: { michael@0: (*aTransferable)->AddDataFlavor(kNativeHTMLMime); michael@0: } michael@0: (*aTransferable)->AddDataFlavor(kHTMLMime); michael@0: (*aTransferable)->AddDataFlavor(kFileMime); michael@0: michael@0: switch (Preferences::GetInt("clipboard.paste_image_type", 1)) michael@0: { michael@0: case 0: // prefer JPEG over PNG over GIF encoding michael@0: (*aTransferable)->AddDataFlavor(kJPEGImageMime); michael@0: (*aTransferable)->AddDataFlavor(kJPGImageMime); michael@0: (*aTransferable)->AddDataFlavor(kPNGImageMime); michael@0: (*aTransferable)->AddDataFlavor(kGIFImageMime); michael@0: break; michael@0: case 1: // prefer PNG over JPEG over GIF encoding (default) michael@0: default: michael@0: (*aTransferable)->AddDataFlavor(kPNGImageMime); michael@0: (*aTransferable)->AddDataFlavor(kJPEGImageMime); michael@0: (*aTransferable)->AddDataFlavor(kJPGImageMime); michael@0: (*aTransferable)->AddDataFlavor(kGIFImageMime); michael@0: break; michael@0: case 2: // prefer GIF over JPEG over PNG encoding michael@0: (*aTransferable)->AddDataFlavor(kGIFImageMime); michael@0: (*aTransferable)->AddDataFlavor(kJPEGImageMime); michael@0: (*aTransferable)->AddDataFlavor(kJPGImageMime); michael@0: (*aTransferable)->AddDataFlavor(kPNGImageMime); michael@0: break; michael@0: } michael@0: } michael@0: (*aTransferable)->AddDataFlavor(kUnicodeMime); michael@0: (*aTransferable)->AddDataFlavor(kMozTextInternal); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: FindIntegerAfterString(const char *aLeadingString, michael@0: nsCString &aCStr, int32_t &foundNumber) michael@0: { michael@0: // first obtain offsets from cfhtml str michael@0: int32_t numFront = aCStr.Find(aLeadingString); michael@0: if (numFront == -1) michael@0: return false; michael@0: numFront += strlen(aLeadingString); michael@0: michael@0: int32_t numBack = aCStr.FindCharInSet(CRLF, numFront); michael@0: if (numBack == -1) michael@0: return false; michael@0: michael@0: nsAutoCString numStr(Substring(aCStr, numFront, numBack-numFront)); michael@0: nsresult errorCode; michael@0: foundNumber = numStr.ToInteger(&errorCode); michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: RemoveFragComments(nsCString & aStr) michael@0: { michael@0: // remove the StartFragment/EndFragment comments from the str, if present michael@0: int32_t startCommentIndx = aStr.Find("", false, startCommentIndx); michael@0: if (startCommentEnd > startCommentIndx) michael@0: aStr.Cut(startCommentIndx, (startCommentEnd+3)-startCommentIndx); michael@0: } michael@0: int32_t endCommentIndx = aStr.Find("", false, endCommentIndx); michael@0: if (endCommentEnd > endCommentIndx) michael@0: aStr.Cut(endCommentIndx, (endCommentEnd+3)-endCommentIndx); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::ParseCFHTML(nsCString & aCfhtml, char16_t **aStuffToPaste, char16_t **aCfcontext) michael@0: { michael@0: // First obtain offsets from cfhtml str. michael@0: int32_t startHTML, endHTML, startFragment, endFragment; michael@0: if (!FindIntegerAfterString("StartHTML:", aCfhtml, startHTML) || michael@0: startHTML < -1) michael@0: return NS_ERROR_FAILURE; michael@0: if (!FindIntegerAfterString("EndHTML:", aCfhtml, endHTML) || michael@0: endHTML < -1) michael@0: return NS_ERROR_FAILURE; michael@0: if (!FindIntegerAfterString("StartFragment:", aCfhtml, startFragment) || michael@0: startFragment < 0) michael@0: return NS_ERROR_FAILURE; michael@0: if (!FindIntegerAfterString("EndFragment:", aCfhtml, endFragment) || michael@0: startFragment < 0) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // The StartHTML and EndHTML markers are allowed to be -1 to include everything. michael@0: // See Reference: MSDN doc entitled "HTML Clipboard Format" michael@0: // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854 michael@0: if (startHTML == -1) { michael@0: startHTML = aCfhtml.Find(""); michael@0: if (startHTML == -1) michael@0: return NS_OK; michael@0: } michael@0: if (endHTML == -1) { michael@0: const char endFragmentMarker[] = ""; michael@0: endHTML = aCfhtml.Find(endFragmentMarker); michael@0: if (endHTML == -1) michael@0: return NS_OK; michael@0: endHTML += ArrayLength(endFragmentMarker) - 1; michael@0: } michael@0: michael@0: // create context string michael@0: nsAutoCString contextUTF8(Substring(aCfhtml, startHTML, startFragment - startHTML) + michael@0: NS_LITERAL_CSTRING("") + michael@0: Substring(aCfhtml, endFragment, endHTML - endFragment)); michael@0: michael@0: // validate startFragment michael@0: // make sure it's not in the middle of a HTML tag michael@0: // see bug #228879 for more details michael@0: int32_t curPos = startFragment; michael@0: while (curPos > startHTML) michael@0: { michael@0: if (aCfhtml[curPos] == '>') michael@0: { michael@0: // working backwards, the first thing we see is the end of a tag michael@0: // so StartFragment is good, so do nothing. michael@0: break; michael@0: } michael@0: else if (aCfhtml[curPos] == '<') michael@0: { michael@0: // if we are at the start, then we want to see the '<' michael@0: if (curPos != startFragment) michael@0: { michael@0: // working backwards, the first thing we see is the start of a tag michael@0: // so StartFragment is bad, so we need to update it. michael@0: NS_ERROR("StartFragment byte count in the clipboard looks bad, see bug #228879"); michael@0: startFragment = curPos - 1; michael@0: } michael@0: break; michael@0: } michael@0: else michael@0: { michael@0: curPos--; michael@0: } michael@0: } michael@0: michael@0: // create fragment string michael@0: nsAutoCString fragmentUTF8(Substring(aCfhtml, startFragment, endFragment-startFragment)); michael@0: michael@0: // remove the StartFragment/EndFragment comments from the fragment, if present michael@0: RemoveFragComments(fragmentUTF8); michael@0: michael@0: // remove the StartFragment/EndFragment comments from the context, if present michael@0: RemoveFragComments(contextUTF8); michael@0: michael@0: // convert both strings to usc2 michael@0: const nsAFlatString& fragUcs2Str = NS_ConvertUTF8toUTF16(fragmentUTF8); michael@0: const nsAFlatString& cntxtUcs2Str = NS_ConvertUTF8toUTF16(contextUTF8); michael@0: michael@0: // translate platform linebreaks for fragment michael@0: int32_t oldLengthInChars = fragUcs2Str.Length() + 1; // +1 to include null terminator michael@0: int32_t newLengthInChars = 0; michael@0: *aStuffToPaste = nsLinebreakConverter::ConvertUnicharLineBreaks(fragUcs2Str.get(), michael@0: nsLinebreakConverter::eLinebreakAny, michael@0: nsLinebreakConverter::eLinebreakContent, michael@0: oldLengthInChars, &newLengthInChars); michael@0: NS_ENSURE_TRUE(*aStuffToPaste, NS_ERROR_FAILURE); michael@0: michael@0: // translate platform linebreaks for context michael@0: oldLengthInChars = cntxtUcs2Str.Length() + 1; // +1 to include null terminator michael@0: newLengthInChars = 0; michael@0: *aCfcontext = nsLinebreakConverter::ConvertUnicharLineBreaks(cntxtUcs2Str.get(), michael@0: nsLinebreakConverter::eLinebreakAny, michael@0: nsLinebreakConverter::eLinebreakContent, michael@0: oldLengthInChars, &newLengthInChars); michael@0: // it's ok for context to be empty. frag might be whole doc and contain all its context. michael@0: michael@0: // we're done! michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsHTMLEditor::InsertObject(const char* aType, nsISupports* aObject, bool aIsSafe, michael@0: nsIDOMDocument *aSourceDoc, michael@0: nsIDOMNode *aDestinationNode, michael@0: int32_t aDestOffset, michael@0: bool aDoDeleteSelection) michael@0: { michael@0: nsresult rv; michael@0: michael@0: const char* type = aType; michael@0: michael@0: // Check to see if we can insert an image file michael@0: bool insertAsImage = false; michael@0: nsCOMPtr fileURI; michael@0: if (0 == nsCRT::strcmp(type, kFileMime)) michael@0: { michael@0: nsCOMPtr fileObj = do_QueryInterface(aObject); michael@0: if (fileObj) michael@0: { michael@0: rv = NS_NewFileURI(getter_AddRefs(fileURI), fileObj); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr mime = do_GetService("@mozilla.org/mime;1"); michael@0: NS_ENSURE_TRUE(mime, NS_ERROR_FAILURE); michael@0: nsAutoCString contentType; michael@0: rv = mime->GetTypeFromFile(fileObj, contentType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Accept any image type fed to us michael@0: if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) { michael@0: insertAsImage = true; michael@0: type = contentType.get(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (0 == nsCRT::strcmp(type, kJPEGImageMime) || michael@0: 0 == nsCRT::strcmp(type, kJPGImageMime) || michael@0: 0 == nsCRT::strcmp(type, kPNGImageMime) || michael@0: 0 == nsCRT::strcmp(type, kGIFImageMime) || michael@0: insertAsImage) michael@0: { michael@0: nsCOMPtr imageStream; michael@0: if (insertAsImage) { michael@0: NS_ASSERTION(fileURI, "The file URI should be retrieved earlier"); michael@0: rv = NS_OpenURI(getter_AddRefs(imageStream), fileURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else { michael@0: imageStream = do_QueryInterface(aObject); michael@0: NS_ENSURE_TRUE(imageStream, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: nsCString imageData; michael@0: rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = imageStream->Close(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString data64; michael@0: rv = Base64Encode(imageData, data64); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoString stuffToPaste; michael@0: stuffToPaste.AssignLiteral("\"\""); michael@0: nsAutoEditBatch beginBatching(this); michael@0: rv = DoInsertHTMLWithContext(stuffToPaste, EmptyString(), EmptyString(), michael@0: NS_LITERAL_STRING(kFileMime), michael@0: aSourceDoc, michael@0: aDestinationNode, aDestOffset, michael@0: aDoDeleteSelection, michael@0: aIsSafe); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::InsertFromTransferable(nsITransferable *transferable, michael@0: nsIDOMDocument *aSourceDoc, michael@0: const nsAString & aContextStr, michael@0: const nsAString & aInfoStr, michael@0: nsIDOMNode *aDestinationNode, michael@0: int32_t aDestOffset, michael@0: bool aDoDeleteSelection) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: nsXPIDLCString bestFlavor; michael@0: nsCOMPtr genericDataObj; michael@0: uint32_t len = 0; michael@0: if (NS_SUCCEEDED(transferable->GetAnyTransferData(getter_Copies(bestFlavor), getter_AddRefs(genericDataObj), &len))) michael@0: { michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(this); michael@0: nsAutoString flavor; michael@0: flavor.AssignWithConversion(bestFlavor); michael@0: nsAutoString stuffToPaste; michael@0: #ifdef DEBUG_clipboard michael@0: printf("Got flavor [%s]\n", bestFlavor.get()); michael@0: #endif michael@0: michael@0: bool isSafe = IsSafeToInsertData(aSourceDoc); michael@0: michael@0: if (0 == nsCRT::strcmp(bestFlavor, kFileMime) || michael@0: 0 == nsCRT::strcmp(bestFlavor, kJPEGImageMime) || michael@0: 0 == nsCRT::strcmp(bestFlavor, kJPGImageMime) || michael@0: 0 == nsCRT::strcmp(bestFlavor, kPNGImageMime) || michael@0: 0 == nsCRT::strcmp(bestFlavor, kGIFImageMime)) { michael@0: rv = InsertObject(bestFlavor, genericDataObj, isSafe, michael@0: aSourceDoc, aDestinationNode, aDestOffset, aDoDeleteSelection); michael@0: } michael@0: else if (0 == nsCRT::strcmp(bestFlavor, kNativeHTMLMime)) michael@0: { michael@0: // note cf_html uses utf8, hence use length = len, not len/2 as in flavors below michael@0: nsCOMPtr textDataObj = do_QueryInterface(genericDataObj); michael@0: if (textDataObj && len > 0) michael@0: { michael@0: nsAutoCString cfhtml; michael@0: textDataObj->GetData(cfhtml); michael@0: NS_ASSERTION(cfhtml.Length() <= (len), "Invalid length!"); michael@0: nsXPIDLString cfcontext, cffragment, cfselection; // cfselection left emtpy for now michael@0: michael@0: rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), getter_Copies(cfcontext)); michael@0: if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) michael@0: { michael@0: nsAutoEditBatch beginBatching(this); michael@0: rv = DoInsertHTMLWithContext(cffragment, michael@0: cfcontext, cfselection, flavor, michael@0: aSourceDoc, michael@0: aDestinationNode, aDestOffset, michael@0: aDoDeleteSelection, michael@0: isSafe); michael@0: } else { michael@0: // In some platforms (like Linux), the clipboard might return data michael@0: // requested for unknown flavors (for example: michael@0: // application/x-moz-nativehtml). In this case, treat the data michael@0: // to be pasted as mere HTML to get the best chance of pasting it michael@0: // correctly. michael@0: bestFlavor.AssignLiteral(kHTMLMime); michael@0: // Fall through the next case michael@0: } michael@0: } michael@0: } michael@0: if (0 == nsCRT::strcmp(bestFlavor, kHTMLMime) || michael@0: 0 == nsCRT::strcmp(bestFlavor, kUnicodeMime) || michael@0: 0 == nsCRT::strcmp(bestFlavor, kMozTextInternal)) { michael@0: nsCOMPtr textDataObj = do_QueryInterface(genericDataObj); michael@0: if (textDataObj && len > 0) { michael@0: nsAutoString text; michael@0: textDataObj->GetData(text); michael@0: NS_ASSERTION(text.Length() <= (len/2), "Invalid length!"); michael@0: stuffToPaste.Assign(text.get(), len / 2); michael@0: } else { michael@0: nsCOMPtr textDataObj(do_QueryInterface(genericDataObj)); michael@0: if (textDataObj && len > 0) { michael@0: nsAutoCString text; michael@0: textDataObj->GetData(text); michael@0: NS_ASSERTION(text.Length() <= len, "Invalid length!"); michael@0: stuffToPaste.Assign(NS_ConvertUTF8toUTF16(Substring(text, 0, len))); michael@0: } michael@0: } michael@0: michael@0: if (!stuffToPaste.IsEmpty()) { michael@0: nsAutoEditBatch beginBatching(this); michael@0: if (0 == nsCRT::strcmp(bestFlavor, kHTMLMime)) { michael@0: rv = DoInsertHTMLWithContext(stuffToPaste, michael@0: aContextStr, aInfoStr, flavor, michael@0: aSourceDoc, michael@0: aDestinationNode, aDestOffset, michael@0: aDoDeleteSelection, michael@0: isSafe); michael@0: } else { michael@0: rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Try to scroll the selection into view if the paste succeeded michael@0: if (NS_SUCCEEDED(rv)) michael@0: ScrollSelectionIntoView(false); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: static void michael@0: GetStringFromDataTransfer(nsIDOMDataTransfer *aDataTransfer, const nsAString& aType, michael@0: int32_t aIndex, nsAString& aOutputString) michael@0: { michael@0: nsCOMPtr variant; michael@0: aDataTransfer->MozGetDataAt(aType, aIndex, getter_AddRefs(variant)); michael@0: if (variant) michael@0: variant->GetAsAString(aOutputString); michael@0: } michael@0: michael@0: nsresult nsHTMLEditor::InsertFromDataTransfer(DataTransfer *aDataTransfer, michael@0: int32_t aIndex, michael@0: nsIDOMDocument *aSourceDoc, michael@0: nsIDOMNode *aDestinationNode, michael@0: int32_t aDestOffset, michael@0: bool aDoDeleteSelection) michael@0: { michael@0: ErrorResult rv; michael@0: nsRefPtr types = aDataTransfer->MozTypesAt(aIndex, rv); michael@0: if (rv.Failed()) { michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: bool hasPrivateHTMLFlavor = types->Contains(NS_LITERAL_STRING(kHTMLContext)); michael@0: michael@0: bool isText = IsPlaintextEditor(); michael@0: bool isSafe = IsSafeToInsertData(aSourceDoc); michael@0: michael@0: uint32_t length = types->Length(); michael@0: for (uint32_t t = 0; t < length; t++) { michael@0: nsAutoString type; michael@0: types->Item(t, type); michael@0: michael@0: if (!isText) { michael@0: if (type.EqualsLiteral(kFileMime) || michael@0: type.EqualsLiteral(kJPEGImageMime) || michael@0: type.EqualsLiteral(kJPGImageMime) || michael@0: type.EqualsLiteral(kPNGImageMime) || michael@0: type.EqualsLiteral(kGIFImageMime)) { michael@0: nsCOMPtr variant; michael@0: aDataTransfer->MozGetDataAt(type, aIndex, getter_AddRefs(variant)); michael@0: if (variant) { michael@0: nsCOMPtr object; michael@0: variant->GetAsISupports(getter_AddRefs(object)); michael@0: return InsertObject(NS_ConvertUTF16toUTF8(type).get(), object, isSafe, michael@0: aSourceDoc, aDestinationNode, aDestOffset, aDoDeleteSelection); michael@0: } michael@0: } michael@0: else if (!hasPrivateHTMLFlavor && type.EqualsLiteral(kNativeHTMLMime)) { michael@0: nsAutoString text; michael@0: GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kNativeHTMLMime), aIndex, text); michael@0: NS_ConvertUTF16toUTF8 cfhtml(text); michael@0: michael@0: nsXPIDLString cfcontext, cffragment, cfselection; // cfselection left emtpy for now michael@0: michael@0: nsresult rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), getter_Copies(cfcontext)); michael@0: if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty()) michael@0: { michael@0: nsAutoEditBatch beginBatching(this); michael@0: return DoInsertHTMLWithContext(cffragment, michael@0: cfcontext, cfselection, type, michael@0: aSourceDoc, michael@0: aDestinationNode, aDestOffset, michael@0: aDoDeleteSelection, michael@0: isSafe); michael@0: } michael@0: } michael@0: else if (type.EqualsLiteral(kHTMLMime)) { michael@0: nsAutoString text, contextString, infoString; michael@0: GetStringFromDataTransfer(aDataTransfer, type, aIndex, text); michael@0: GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kHTMLContext), aIndex, contextString); michael@0: GetStringFromDataTransfer(aDataTransfer, NS_LITERAL_STRING(kHTMLInfo), aIndex, infoString); michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: if (type.EqualsLiteral(kHTMLMime)) { michael@0: return DoInsertHTMLWithContext(text, michael@0: contextString, infoString, type, michael@0: aSourceDoc, michael@0: aDestinationNode, aDestOffset, michael@0: aDoDeleteSelection, michael@0: isSafe); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (type.EqualsLiteral(kTextMime) || michael@0: type.EqualsLiteral(kMozTextInternal)) { michael@0: nsAutoString text; michael@0: GetStringFromDataTransfer(aDataTransfer, type, aIndex, text); michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: return InsertTextAt(text, aDestinationNode, aDestOffset, aDoDeleteSelection); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool nsHTMLEditor::HavePrivateHTMLFlavor(nsIClipboard *aClipboard) michael@0: { michael@0: // check the clipboard for our special kHTMLContext flavor. If that is there, we know michael@0: // we have our own internal html format on clipboard. michael@0: michael@0: NS_ENSURE_TRUE(aClipboard, false); michael@0: bool bHavePrivateHTMLFlavor = false; michael@0: michael@0: const char* flavArray[] = { kHTMLContext }; michael@0: michael@0: if (NS_SUCCEEDED(aClipboard->HasDataMatchingFlavors(flavArray, michael@0: ArrayLength(flavArray), nsIClipboard::kGlobalClipboard, michael@0: &bHavePrivateHTMLFlavor))) michael@0: return bHavePrivateHTMLFlavor; michael@0: michael@0: return false; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::Paste(int32_t aSelectionType) michael@0: { michael@0: if (!FireClipboardEvent(NS_PASTE, aSelectionType)) michael@0: return NS_OK; michael@0: michael@0: // Get Clipboard Service michael@0: nsresult rv; michael@0: nsCOMPtr clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // find out if we have our internal html flavor on the clipboard. We don't want to mess michael@0: // around with cfhtml if we do. michael@0: bool bHavePrivateHTMLFlavor = HavePrivateHTMLFlavor(clipboard); michael@0: michael@0: // Get the nsITransferable interface for getting the data from the clipboard michael@0: nsCOMPtr trans; michael@0: rv = PrepareHTMLTransferable(getter_AddRefs(trans), bHavePrivateHTMLFlavor); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(trans, NS_ERROR_FAILURE); michael@0: // Get the Data from the clipboard michael@0: rv = clipboard->GetData(trans, aSelectionType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!IsModifiable()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // also get additional html copy hints, if present michael@0: nsAutoString contextStr, infoStr; michael@0: michael@0: // also get additional html copy hints, if present michael@0: if (bHavePrivateHTMLFlavor) michael@0: { michael@0: nsCOMPtr contextDataObj, infoDataObj; michael@0: uint32_t contextLen, infoLen; michael@0: nsCOMPtr textDataObj; michael@0: michael@0: nsCOMPtr contextTrans = michael@0: do_CreateInstance("@mozilla.org/widget/transferable;1"); michael@0: NS_ENSURE_TRUE(contextTrans, NS_ERROR_NULL_POINTER); michael@0: contextTrans->Init(nullptr); michael@0: contextTrans->AddDataFlavor(kHTMLContext); michael@0: clipboard->GetData(contextTrans, aSelectionType); michael@0: contextTrans->GetTransferData(kHTMLContext, getter_AddRefs(contextDataObj), &contextLen); michael@0: michael@0: nsCOMPtr infoTrans = michael@0: do_CreateInstance("@mozilla.org/widget/transferable;1"); michael@0: NS_ENSURE_TRUE(infoTrans, NS_ERROR_NULL_POINTER); michael@0: infoTrans->Init(nullptr); michael@0: infoTrans->AddDataFlavor(kHTMLInfo); michael@0: clipboard->GetData(infoTrans, aSelectionType); michael@0: infoTrans->GetTransferData(kHTMLInfo, getter_AddRefs(infoDataObj), &infoLen); michael@0: michael@0: if (contextDataObj) michael@0: { michael@0: nsAutoString text; michael@0: textDataObj = do_QueryInterface(contextDataObj); michael@0: textDataObj->GetData(text); michael@0: NS_ASSERTION(text.Length() <= (contextLen/2), "Invalid length!"); michael@0: contextStr.Assign(text.get(), contextLen / 2); michael@0: } michael@0: michael@0: if (infoDataObj) michael@0: { michael@0: nsAutoString text; michael@0: textDataObj = do_QueryInterface(infoDataObj); michael@0: textDataObj->GetData(text); michael@0: NS_ASSERTION(text.Length() <= (infoLen/2), "Invalid length!"); michael@0: infoStr.Assign(text.get(), infoLen / 2); michael@0: } michael@0: } michael@0: michael@0: // handle transferable hooks michael@0: nsCOMPtr domdoc; michael@0: GetDocument(getter_AddRefs(domdoc)); michael@0: if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, trans)) michael@0: return NS_OK; michael@0: michael@0: return InsertFromTransferable(trans, nullptr, contextStr, infoStr, michael@0: nullptr, 0, true); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::PasteTransferable(nsITransferable *aTransferable) michael@0: { michael@0: // Use an invalid value for the clipboard type as data comes from aTransferable michael@0: // and we don't currently implement a way to put that in the data transfer yet. michael@0: if (!FireClipboardEvent(NS_PASTE, nsIClipboard::kGlobalClipboard)) michael@0: return NS_OK; michael@0: michael@0: // handle transferable hooks michael@0: nsCOMPtr domdoc = GetDOMDocument(); michael@0: if (!nsEditorHookUtils::DoInsertionHook(domdoc, nullptr, aTransferable)) michael@0: return NS_OK; michael@0: michael@0: nsAutoString contextStr, infoStr; michael@0: return InsertFromTransferable(aTransferable, nullptr, contextStr, infoStr, michael@0: nullptr, 0, true); michael@0: } michael@0: michael@0: // michael@0: // HTML PasteNoFormatting. Ignore any HTML styles and formating in paste source michael@0: // michael@0: NS_IMETHODIMP nsHTMLEditor::PasteNoFormatting(int32_t aSelectionType) michael@0: { michael@0: if (!FireClipboardEvent(NS_PASTE, aSelectionType)) michael@0: return NS_OK; michael@0: michael@0: ForceCompositionEnd(); michael@0: michael@0: // Get Clipboard Service michael@0: nsresult rv; michael@0: nsCOMPtr clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Get the nsITransferable interface for getting the data from the clipboard. michael@0: // use nsPlaintextEditor::PrepareTransferable() to force unicode plaintext data. michael@0: nsCOMPtr trans; michael@0: rv = nsPlaintextEditor::PrepareTransferable(getter_AddRefs(trans)); michael@0: if (NS_SUCCEEDED(rv) && trans) michael@0: { michael@0: // Get the Data from the clipboard michael@0: if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable()) michael@0: { michael@0: const nsAFlatString& empty = EmptyString(); michael@0: rv = InsertFromTransferable(trans, nullptr, empty, empty, nullptr, 0, michael@0: true); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: // The following arrays contain the MIME types that we can paste. The arrays michael@0: // are used by CanPaste() and CanPasteTransferable() below. michael@0: michael@0: static const char* textEditorFlavors[] = { kUnicodeMime }; michael@0: static const char* textHtmlEditorFlavors[] = { kUnicodeMime, kHTMLMime, michael@0: kJPEGImageMime, kJPGImageMime, michael@0: kPNGImageMime, kGIFImageMime }; michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::CanPaste(int32_t aSelectionType, bool *aCanPaste) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCanPaste); michael@0: *aCanPaste = false; michael@0: michael@0: // can't paste if readonly michael@0: if (!IsModifiable()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool haveFlavors; michael@0: michael@0: // Use the flavors depending on the current editor mask michael@0: if (IsPlaintextEditor()) michael@0: rv = clipboard->HasDataMatchingFlavors(textEditorFlavors, michael@0: ArrayLength(textEditorFlavors), michael@0: aSelectionType, &haveFlavors); michael@0: else michael@0: rv = clipboard->HasDataMatchingFlavors(textHtmlEditorFlavors, michael@0: ArrayLength(textHtmlEditorFlavors), michael@0: aSelectionType, &haveFlavors); michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aCanPaste = haveFlavors; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::CanPasteTransferable(nsITransferable *aTransferable, bool *aCanPaste) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCanPaste); michael@0: michael@0: // can't paste if readonly michael@0: if (!IsModifiable()) { michael@0: *aCanPaste = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If |aTransferable| is null, assume that a paste will succeed. michael@0: if (!aTransferable) { michael@0: *aCanPaste = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Peek in |aTransferable| to see if it contains a supported MIME type. michael@0: michael@0: // Use the flavors depending on the current editor mask michael@0: const char ** flavors; michael@0: unsigned length; michael@0: if (IsPlaintextEditor()) { michael@0: flavors = textEditorFlavors; michael@0: length = ArrayLength(textEditorFlavors); michael@0: } else { michael@0: flavors = textHtmlEditorFlavors; michael@0: length = ArrayLength(textHtmlEditorFlavors); michael@0: } michael@0: michael@0: for (unsigned int i = 0; i < length; i++, flavors++) { michael@0: nsCOMPtr data; michael@0: uint32_t dataLen; michael@0: nsresult rv = aTransferable->GetTransferData(*flavors, michael@0: getter_AddRefs(data), michael@0: &dataLen); michael@0: if (NS_SUCCEEDED(rv) && data) { michael@0: *aCanPaste = true; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: *aCanPaste = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // michael@0: // HTML PasteAsQuotation: Paste in a blockquote type=cite michael@0: // michael@0: NS_IMETHODIMP nsHTMLEditor::PasteAsQuotation(int32_t aSelectionType) michael@0: { michael@0: if (IsPlaintextEditor()) michael@0: return PasteAsPlaintextQuotation(aSelectionType); michael@0: michael@0: nsAutoString citation; michael@0: return PasteAsCitedQuotation(citation, aSelectionType); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::PasteAsCitedQuotation(const nsAString & aCitation, michael@0: int32_t aSelectionType) michael@0: { michael@0: nsAutoEditBatch beginBatching(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::insertQuotation, nsIEditor::eNext); michael@0: michael@0: // get selection michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // give rules a chance to handle or cancel michael@0: nsTextRulesInfo ruleInfo(EditAction::insertElement); michael@0: bool cancel, handled; michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (cancel || handled) { michael@0: return NS_OK; // rules canceled the operation michael@0: } michael@0: michael@0: nsCOMPtr newNode; michael@0: rv = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("blockquote"), getter_AddRefs(newNode)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(newNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Try to set type=cite. Ignore it if this fails. michael@0: nsCOMPtr newElement = do_QueryInterface(newNode); michael@0: if (newElement) { michael@0: newElement->SetAttribute(NS_LITERAL_STRING("type"), NS_LITERAL_STRING("cite")); michael@0: } michael@0: michael@0: // Set the selection to the underneath the node we just inserted: michael@0: rv = selection->Collapse(newNode, 0); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return Paste(aSelectionType); michael@0: } michael@0: michael@0: // michael@0: // Paste a plaintext quotation michael@0: // michael@0: NS_IMETHODIMP nsHTMLEditor::PasteAsPlaintextQuotation(int32_t aSelectionType) michael@0: { michael@0: // Get Clipboard Service michael@0: nsresult rv; michael@0: nsCOMPtr clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Create generic Transferable for getting the data michael@0: nsCOMPtr trans = michael@0: do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(trans, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr destdoc = GetDocument(); michael@0: nsILoadContext* loadContext = destdoc ? destdoc->GetLoadContext() : nullptr; michael@0: trans->Init(loadContext); michael@0: michael@0: // We only handle plaintext pastes here michael@0: trans->AddDataFlavor(kUnicodeMime); michael@0: michael@0: // Get the Data from the clipboard michael@0: clipboard->GetData(trans, aSelectionType); michael@0: michael@0: // Now we ask the transferable for the data michael@0: // it still owns the data, we just have a pointer to it. michael@0: // If it can't support a "text" output of the data the call will fail michael@0: nsCOMPtr genericDataObj; michael@0: uint32_t len = 0; michael@0: char* flav = 0; michael@0: rv = trans->GetAnyTransferData(&flav, getter_AddRefs(genericDataObj), &len); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (flav && 0 == nsCRT::strcmp(flav, kUnicodeMime)) michael@0: { michael@0: #ifdef DEBUG_clipboard michael@0: printf("Got flavor [%s]\n", flav); michael@0: #endif michael@0: nsCOMPtr textDataObj = do_QueryInterface(genericDataObj); michael@0: if (textDataObj && len > 0) michael@0: { michael@0: nsAutoString stuffToPaste; michael@0: textDataObj->GetData(stuffToPaste); michael@0: NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!"); michael@0: nsAutoEditBatch beginBatching(this); michael@0: rv = InsertAsPlaintextQuotation(stuffToPaste, true, 0); michael@0: } michael@0: } michael@0: NS_Free(flav); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::InsertTextWithQuotations(const nsAString &aStringToInsert) michael@0: { michael@0: if (mWrapToWindow) michael@0: return InsertText(aStringToInsert); michael@0: michael@0: // The whole operation should be undoable in one transaction: michael@0: BeginTransaction(); michael@0: michael@0: // We're going to loop over the string, collecting up a "hunk" michael@0: // that's all the same type (quoted or not), michael@0: // Whenever the quotedness changes (or we reach the string's end) michael@0: // we will insert the hunk all at once, quoted or non. michael@0: michael@0: static const char16_t cite('>'); michael@0: bool curHunkIsQuoted = (aStringToInsert.First() == cite); michael@0: michael@0: nsAString::const_iterator hunkStart, strEnd; michael@0: aStringToInsert.BeginReading(hunkStart); michael@0: aStringToInsert.EndReading(strEnd); michael@0: michael@0: // In the loop below, we only look for DOM newlines (\n), michael@0: // because we don't have a FindChars method that can look michael@0: // for both \r and \n. \r is illegal in the dom anyway, michael@0: // but in debug builds, let's take the time to verify that michael@0: // there aren't any there: michael@0: #ifdef DEBUG michael@0: nsAString::const_iterator dbgStart (hunkStart); michael@0: if (FindCharInReadable('\r', dbgStart, strEnd)) michael@0: NS_ASSERTION(false, michael@0: "Return characters in DOM! InsertTextWithQuotations may be wrong"); michael@0: #endif /* DEBUG */ michael@0: michael@0: // Loop over lines: michael@0: nsresult rv = NS_OK; michael@0: nsAString::const_iterator lineStart (hunkStart); michael@0: while (1) // we will break from inside when we run out of newlines michael@0: { michael@0: // Search for the end of this line (dom newlines, see above): michael@0: bool found = FindCharInReadable('\n', lineStart, strEnd); michael@0: bool quoted = false; michael@0: if (found) michael@0: { michael@0: // if there's another newline, lineStart now points there. michael@0: // Loop over any consecutive newline chars: michael@0: nsAString::const_iterator firstNewline (lineStart); michael@0: while (*lineStart == '\n') michael@0: ++lineStart; michael@0: quoted = (*lineStart == cite); michael@0: if (quoted == curHunkIsQuoted) michael@0: continue; michael@0: // else we're changing state, so we need to insert michael@0: // from curHunk to lineStart then loop around. michael@0: michael@0: // But if the current hunk is quoted, then we want to make sure michael@0: // that any extra newlines on the end do not get included in michael@0: // the quoted section: blank lines flaking a quoted section michael@0: // should be considered unquoted, so that if the user clicks michael@0: // there and starts typing, the new text will be outside of michael@0: // the quoted block. michael@0: if (curHunkIsQuoted) michael@0: lineStart = firstNewline; michael@0: } michael@0: michael@0: // If no newline found, lineStart is now strEnd and we can finish up, michael@0: // inserting from curHunk to lineStart then returning. michael@0: const nsAString &curHunk = Substring(hunkStart, lineStart); michael@0: nsCOMPtr dummyNode; michael@0: #ifdef DEBUG_akkana_verbose michael@0: printf("==== Inserting text as %squoted: ---\n%s---\n", michael@0: curHunkIsQuoted ? "" : "non-", michael@0: NS_LossyConvertUTF16toASCII(curHunk).get()); michael@0: #endif michael@0: if (curHunkIsQuoted) michael@0: rv = InsertAsPlaintextQuotation(curHunk, false, michael@0: getter_AddRefs(dummyNode)); michael@0: else michael@0: rv = InsertText(curHunk); michael@0: michael@0: if (!found) michael@0: break; michael@0: michael@0: curHunkIsQuoted = quoted; michael@0: hunkStart = lineStart; michael@0: } michael@0: michael@0: EndTransaction(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::InsertAsQuotation(const nsAString & aQuotedText, michael@0: nsIDOMNode **aNodeInserted) michael@0: { michael@0: if (IsPlaintextEditor()) michael@0: return InsertAsPlaintextQuotation(aQuotedText, true, aNodeInserted); michael@0: michael@0: nsAutoString citation; michael@0: return InsertAsCitedQuotation(aQuotedText, citation, false, michael@0: aNodeInserted); michael@0: } michael@0: michael@0: // Insert plaintext as a quotation, with cite marks (e.g. "> "). michael@0: // This differs from its corresponding method in nsPlaintextEditor michael@0: // in that here, quoted material is enclosed in a
 tag
michael@0: // in order to preserve the original line wrapping.
michael@0: NS_IMETHODIMP
michael@0: nsHTMLEditor::InsertAsPlaintextQuotation(const nsAString & aQuotedText,
michael@0:                                          bool aAddCites,
michael@0:                                          nsIDOMNode **aNodeInserted)
michael@0: {
michael@0:   if (mWrapToWindow)
michael@0:     return nsPlaintextEditor::InsertAsQuotation(aQuotedText, aNodeInserted);
michael@0: 
michael@0:   nsCOMPtr newNode;
michael@0:   // get selection
michael@0:   nsRefPtr selection = GetSelection();
michael@0:   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
michael@0: 
michael@0:   nsAutoEditBatch beginBatching(this);
michael@0:   nsAutoRules beginRulesSniffing(this, EditAction::insertQuotation, nsIEditor::eNext);
michael@0: 
michael@0:   // give rules a chance to handle or cancel
michael@0:   nsTextRulesInfo ruleInfo(EditAction::insertElement);
michael@0:   bool cancel, handled;
michael@0:   // Protect the edit rules object from dying
michael@0:   nsCOMPtr kungFuDeathGrip(mRules);
michael@0:   nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
michael@0:   NS_ENSURE_SUCCESS(rv, rv);
michael@0:   if (cancel || handled) {
michael@0:     return NS_OK; // rules canceled the operation
michael@0:   }
michael@0: 
michael@0:   // Wrap the inserted quote in a  so it won't be wrapped:
michael@0:   rv = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("span"), getter_AddRefs(newNode));
michael@0: 
michael@0:   // If this succeeded, then set selection inside the pre
michael@0:   // so the inserted text will end up there.
michael@0:   // If it failed, we don't care what the return value was,
michael@0:   // but we'll fall through and try to insert the text anyway.
michael@0:   if (NS_SUCCEEDED(rv) && newNode)
michael@0:   {
michael@0:     // Add an attribute on the pre node so we'll know it's a quotation.
michael@0:     // Do this after the insertion, so that
michael@0:     nsCOMPtr preElement = do_QueryInterface(newNode);
michael@0:     if (preElement)
michael@0:     {
michael@0:       preElement->SetAttribute(NS_LITERAL_STRING("_moz_quote"),
michael@0:                                NS_LITERAL_STRING("true"));
michael@0:       // turn off wrapping on spans
michael@0:       preElement->SetAttribute(NS_LITERAL_STRING("style"),
michael@0:                                NS_LITERAL_STRING("white-space: pre;"));
michael@0:     }
michael@0:     // and set the selection inside it:
michael@0:     selection->Collapse(newNode, 0);
michael@0:   }
michael@0: 
michael@0:   if (aAddCites)
michael@0:     rv = nsPlaintextEditor::InsertAsQuotation(aQuotedText, aNodeInserted);
michael@0:   else
michael@0:     rv = nsPlaintextEditor::InsertText(aQuotedText);
michael@0:   // Note that if !aAddCites, aNodeInserted isn't set.
michael@0:   // That's okay because the routines that use aAddCites
michael@0:   // don't need to know the inserted node.
michael@0: 
michael@0:   if (aNodeInserted && NS_SUCCEEDED(rv))
michael@0:   {
michael@0:     *aNodeInserted = newNode;
michael@0:     NS_IF_ADDREF(*aNodeInserted);
michael@0:   }
michael@0: 
michael@0:   // Set the selection to just after the inserted node:
michael@0:   if (NS_SUCCEEDED(rv) && newNode)
michael@0:   {
michael@0:     int32_t offset;
michael@0:     nsCOMPtr parent = GetNodeLocation(newNode, &offset);
michael@0:     if (parent) {
michael@0:       selection->Collapse(parent, offset + 1);
michael@0:     }
michael@0:   }
michael@0:   return rv;
michael@0: }
michael@0: 
michael@0: NS_IMETHODIMP
michael@0: nsHTMLEditor::StripCites()
michael@0: {
michael@0:   return nsPlaintextEditor::StripCites();
michael@0: }
michael@0: 
michael@0: NS_IMETHODIMP
michael@0: nsHTMLEditor::Rewrap(bool aRespectNewlines)
michael@0: {
michael@0:   return nsPlaintextEditor::Rewrap(aRespectNewlines);
michael@0: }
michael@0: 
michael@0: NS_IMETHODIMP
michael@0: nsHTMLEditor::InsertAsCitedQuotation(const nsAString & aQuotedText,
michael@0:                                      const nsAString & aCitation,
michael@0:                                      bool aInsertHTML,
michael@0:                                      nsIDOMNode **aNodeInserted)
michael@0: {
michael@0:   // Don't let anyone insert html into a "plaintext" editor:
michael@0:   if (IsPlaintextEditor())
michael@0:   {
michael@0:     NS_ASSERTION(!aInsertHTML, "InsertAsCitedQuotation: trying to insert html into plaintext editor");
michael@0:     return InsertAsPlaintextQuotation(aQuotedText, true, aNodeInserted);
michael@0:   }
michael@0: 
michael@0:   nsCOMPtr newNode;
michael@0: 
michael@0:   // get selection
michael@0:   nsRefPtr selection = GetSelection();
michael@0:   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
michael@0: 
michael@0:   nsAutoEditBatch beginBatching(this);
michael@0:   nsAutoRules beginRulesSniffing(this, EditAction::insertQuotation, nsIEditor::eNext);
michael@0: 
michael@0:   // give rules a chance to handle or cancel
michael@0:   nsTextRulesInfo ruleInfo(EditAction::insertElement);
michael@0:   bool cancel, handled;
michael@0:   // Protect the edit rules object from dying
michael@0:   nsCOMPtr kungFuDeathGrip(mRules);
michael@0:   nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
michael@0:   NS_ENSURE_SUCCESS(rv, rv);
michael@0:   if (cancel || handled) {
michael@0:     return NS_OK; // rules canceled the operation
michael@0:   }
michael@0: 
michael@0:   rv = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("blockquote"), getter_AddRefs(newNode));
michael@0:   NS_ENSURE_SUCCESS(rv, rv);
michael@0:   NS_ENSURE_TRUE(newNode, NS_ERROR_NULL_POINTER);
michael@0: 
michael@0:   // Try to set type=cite.  Ignore it if this fails.
michael@0:   nsCOMPtr newElement = do_QueryInterface(newNode);
michael@0:   if (newElement)
michael@0:   {
michael@0:     NS_NAMED_LITERAL_STRING(citeStr, "cite");
michael@0:     newElement->SetAttribute(NS_LITERAL_STRING("type"), citeStr);
michael@0: 
michael@0:     if (!aCitation.IsEmpty())
michael@0:       newElement->SetAttribute(citeStr, aCitation);
michael@0: 
michael@0:     // Set the selection inside the blockquote so aQuotedText will go there:
michael@0:     selection->Collapse(newNode, 0);
michael@0:   }
michael@0: 
michael@0:   if (aInsertHTML)
michael@0:     rv = LoadHTML(aQuotedText);
michael@0:   else
michael@0:     rv = InsertText(aQuotedText);  // XXX ignore charset
michael@0: 
michael@0:   if (aNodeInserted && NS_SUCCEEDED(rv))
michael@0:   {
michael@0:     *aNodeInserted = newNode;
michael@0:     NS_IF_ADDREF(*aNodeInserted);
michael@0:   }
michael@0: 
michael@0:   // Set the selection to just after the inserted node:
michael@0:   if (NS_SUCCEEDED(rv) && newNode)
michael@0:   {
michael@0:     int32_t offset;
michael@0:     nsCOMPtr parent = GetNodeLocation(newNode, &offset);
michael@0:     if (parent) {
michael@0:       selection->Collapse(parent, offset + 1);
michael@0:     }
michael@0:   }
michael@0:   return rv;
michael@0: }
michael@0: 
michael@0: 
michael@0: void RemoveBodyAndHead(nsIDOMNode *aNode)
michael@0: {
michael@0:   if (!aNode)
michael@0:     return;
michael@0: 
michael@0:   nsCOMPtr tmp, child, body, head;
michael@0:   // find the body and head nodes if any.
michael@0:   // look only at immediate children of aNode.
michael@0:   aNode->GetFirstChild(getter_AddRefs(child));
michael@0:   while (child)
michael@0:   {
michael@0:     if (nsTextEditUtils::IsBody(child))
michael@0:     {
michael@0:       body = child;
michael@0:     }
michael@0:     else if (nsEditor::NodeIsType(child, nsEditProperty::head))
michael@0:     {
michael@0:       head = child;
michael@0:     }
michael@0:     child->GetNextSibling(getter_AddRefs(tmp));
michael@0:     child = tmp;
michael@0:   }
michael@0:   if (head)
michael@0:   {
michael@0:     aNode->RemoveChild(head, getter_AddRefs(tmp));
michael@0:   }
michael@0:   if (body)
michael@0:   {
michael@0:     body->GetFirstChild(getter_AddRefs(child));
michael@0:     while (child)
michael@0:     {
michael@0:       aNode->InsertBefore(child, body, getter_AddRefs(tmp));
michael@0:       body->GetFirstChild(getter_AddRefs(child));
michael@0:     }
michael@0:     aNode->RemoveChild(body, getter_AddRefs(tmp));
michael@0:   }
michael@0: }
michael@0: 
michael@0: /**
michael@0:  * This function finds the target node that we will be pasting into. aStart is
michael@0:  * the context that we're given and aResult will be the target. Initially,
michael@0:  * *aResult must be nullptr.
michael@0:  *
michael@0:  * The target for a paste is found by either finding the node that contains
michael@0:  * the magical comment node containing kInsertCookie or, failing that, the
michael@0:  * firstChild of the firstChild (until we reach a leaf).
michael@0:  */
michael@0: nsresult FindTargetNode(nsIDOMNode *aStart, nsCOMPtr &aResult)
michael@0: {
michael@0:   NS_ENSURE_TRUE(aStart, NS_OK);
michael@0: 
michael@0:   nsCOMPtr child, tmp;
michael@0: 
michael@0:   nsresult rv = aStart->GetFirstChild(getter_AddRefs(child));
michael@0:   NS_ENSURE_SUCCESS(rv, rv);
michael@0: 
michael@0:   if (!child)
michael@0:   {
michael@0:     // If the current result is nullptr, then aStart is a leaf, and is the
michael@0:     // fallback result.
michael@0:     if (!aResult)
michael@0:       aResult = aStart;
michael@0: 
michael@0:     return NS_OK;
michael@0:   }
michael@0: 
michael@0:   do
michael@0:   {
michael@0:     // Is this child the magical cookie?
michael@0:     nsCOMPtr comment = do_QueryInterface(child);
michael@0:     if (comment)
michael@0:     {
michael@0:       nsAutoString data;
michael@0:       rv = comment->GetData(data);
michael@0:       NS_ENSURE_SUCCESS(rv, rv);
michael@0: 
michael@0:       if (data.EqualsLiteral(kInsertCookie))
michael@0:       {
michael@0:         // Yes it is! Return an error so we bubble out and short-circuit the
michael@0:         // search.
michael@0:         aResult = aStart;
michael@0: 
michael@0:         // Note: it doesn't matter if this fails.
michael@0:         aStart->RemoveChild(child, getter_AddRefs(tmp));
michael@0: 
michael@0:         return NS_FOUND_TARGET;
michael@0:       }
michael@0:     }
michael@0: 
michael@0:     // Note: Don't use NS_ENSURE_* here since we return a failure result to
michael@0:     // inicate that we found the magical cookie and we don't want to spam the
michael@0:     // console.
michael@0:     rv = FindTargetNode(child, aResult);
michael@0:     NS_ENSURE_SUCCESS(rv, rv);
michael@0: 
michael@0:     rv = child->GetNextSibling(getter_AddRefs(tmp));
michael@0:     NS_ENSURE_SUCCESS(rv, rv);
michael@0: 
michael@0:     child = tmp;
michael@0:   } while (child);
michael@0: 
michael@0:   return NS_OK;
michael@0: }
michael@0: 
michael@0: nsresult nsHTMLEditor::CreateDOMFragmentFromPaste(const nsAString &aInputString,
michael@0:                                                   const nsAString & aContextStr,
michael@0:                                                   const nsAString & aInfoStr,
michael@0:                                                   nsCOMPtr *outFragNode,
michael@0:                                                   nsCOMPtr *outStartNode,
michael@0:                                                   nsCOMPtr *outEndNode,
michael@0:                                                   int32_t *outStartOffset,
michael@0:                                                   int32_t *outEndOffset,
michael@0:                                                   bool aTrustedInput)
michael@0: {
michael@0:   NS_ENSURE_TRUE(outFragNode && outStartNode && outEndNode, NS_ERROR_NULL_POINTER);
michael@0:   nsCOMPtr docfrag;
michael@0:   nsCOMPtr contextAsNode, tmp;
michael@0:   nsresult rv = NS_OK;
michael@0: 
michael@0:   nsCOMPtr doc = GetDocument();
michael@0:   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
michael@0: 
michael@0:   // if we have context info, create a fragment for that
michael@0:   nsCOMPtr contextfrag;
michael@0:   nsCOMPtr contextLeaf, junk;
michael@0:   if (!aContextStr.IsEmpty())
michael@0:   {
michael@0:     rv = ParseFragment(aContextStr, nullptr, doc, address_of(contextAsNode),
michael@0:                        aTrustedInput);
michael@0:     NS_ENSURE_SUCCESS(rv, rv);
michael@0:     NS_ENSURE_TRUE(contextAsNode, NS_ERROR_FAILURE);
michael@0: 
michael@0:     rv = StripFormattingNodes(contextAsNode);
michael@0:     NS_ENSURE_SUCCESS(rv, rv);
michael@0: 
michael@0:     RemoveBodyAndHead(contextAsNode);
michael@0: 
michael@0:     rv = FindTargetNode(contextAsNode, contextLeaf);
michael@0:     if (rv == NS_FOUND_TARGET) {
michael@0:       rv = NS_OK;
michael@0:     }
michael@0:     NS_ENSURE_SUCCESS(rv, rv);
michael@0:   }
michael@0: 
michael@0:   nsCOMPtr contextLeafAsContent = do_QueryInterface(contextLeaf);
michael@0: 
michael@0:   // create fragment for pasted html
michael@0:   nsIAtom* contextAtom;
michael@0:   if (contextLeafAsContent) {
michael@0:     contextAtom = contextLeafAsContent->Tag();
michael@0:     if (contextAtom == nsGkAtoms::html) {
michael@0:       contextAtom = nsGkAtoms::body;
michael@0:     }
michael@0:   } else {
michael@0:     contextAtom = nsGkAtoms::body;
michael@0:   }
michael@0:   rv = ParseFragment(aInputString,
michael@0:                      contextAtom,
michael@0:                      doc,
michael@0:                      outFragNode,
michael@0:                      aTrustedInput);
michael@0:   NS_ENSURE_SUCCESS(rv, rv);
michael@0:   NS_ENSURE_TRUE(*outFragNode, NS_ERROR_FAILURE);
michael@0: 
michael@0:   RemoveBodyAndHead(*outFragNode);
michael@0: 
michael@0:   if (contextAsNode)
michael@0:   {
michael@0:     // unite the two trees
michael@0:     contextLeaf->AppendChild(*outFragNode, getter_AddRefs(junk));
michael@0:     *outFragNode = contextAsNode;
michael@0:   }
michael@0: 
michael@0:   rv = StripFormattingNodes(*outFragNode, true);
michael@0:   NS_ENSURE_SUCCESS(rv, rv);
michael@0: 
michael@0:   // If there was no context, then treat all of the data we did get as the
michael@0:   // pasted data.
michael@0:   if (contextLeaf)
michael@0:     *outEndNode = *outStartNode = contextLeaf;
michael@0:   else
michael@0:     *outEndNode = *outStartNode = *outFragNode;
michael@0: 
michael@0:   *outStartOffset = 0;
michael@0: 
michael@0:   // get the infoString contents
michael@0:   nsAutoString numstr1, numstr2;
michael@0:   if (!aInfoStr.IsEmpty())
michael@0:   {
michael@0:     int32_t sep, num;
michael@0:     sep = aInfoStr.FindChar((char16_t)',');
michael@0:     numstr1 = Substring(aInfoStr, 0, sep);
michael@0:     numstr2 = Substring(aInfoStr, sep+1, aInfoStr.Length() - (sep+1));
michael@0: 
michael@0:     // Move the start and end children.
michael@0:     nsresult err;
michael@0:     num = numstr1.ToInteger(&err);
michael@0:     while (num--)
michael@0:     {
michael@0:       (*outStartNode)->GetFirstChild(getter_AddRefs(tmp));
michael@0:       NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
michael@0:       tmp.swap(*outStartNode);
michael@0:     }
michael@0: 
michael@0:     num = numstr2.ToInteger(&err);
michael@0:     while (num--)
michael@0:     {
michael@0:       (*outEndNode)->GetLastChild(getter_AddRefs(tmp));
michael@0:       NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
michael@0:       tmp.swap(*outEndNode);
michael@0:     }
michael@0:   }
michael@0: 
michael@0:   GetLengthOfDOMNode(*outEndNode, (uint32_t&)*outEndOffset);
michael@0:   return NS_OK;
michael@0: }
michael@0: 
michael@0: 
michael@0: nsresult nsHTMLEditor::ParseFragment(const nsAString & aFragStr,
michael@0:                                      nsIAtom* aContextLocalName,
michael@0:                                      nsIDocument* aTargetDocument,
michael@0:                                      nsCOMPtr *outNode,
michael@0:                                      bool aTrustedInput)
michael@0: {
michael@0:   nsAutoScriptBlockerSuppressNodeRemoved autoBlocker;
michael@0: 
michael@0:   nsRefPtr fragment =
michael@0:     new DocumentFragment(aTargetDocument->NodeInfoManager());
michael@0:   nsresult rv = nsContentUtils::ParseFragmentHTML(aFragStr,
michael@0:                                                   fragment,
michael@0:                                                   aContextLocalName ?
michael@0:                                                     aContextLocalName : nsGkAtoms::body,
michael@0:                                                     kNameSpaceID_XHTML,
michael@0:                                                   false,
michael@0:                                                   true);
michael@0:   if (!aTrustedInput) {
michael@0:     nsTreeSanitizer sanitizer(aContextLocalName ?
michael@0:                               nsIParserUtils::SanitizerAllowStyle :
michael@0:                               nsIParserUtils::SanitizerAllowComments);
michael@0:     sanitizer.Sanitize(fragment);
michael@0:   }
michael@0:   *outNode = fragment.forget();
michael@0:   return rv;
michael@0: }
michael@0: 
michael@0: nsresult nsHTMLEditor::CreateListOfNodesToPaste(nsIDOMNode  *aFragmentAsNode,
michael@0:                                                 nsCOMArray& outNodeList,
michael@0:                                                 nsIDOMNode *aStartNode,
michael@0:                                                 int32_t aStartOffset,
michael@0:                                                 nsIDOMNode *aEndNode,
michael@0:                                                 int32_t aEndOffset)
michael@0: {
michael@0:   NS_ENSURE_TRUE(aFragmentAsNode, NS_ERROR_NULL_POINTER);
michael@0: 
michael@0:   nsresult rv;
michael@0: 
michael@0:   // if no info was provided about the boundary between context and stream,
michael@0:   // then assume all is stream.
michael@0:   if (!aStartNode)
michael@0:   {
michael@0:     int32_t fragLen;
michael@0:     rv = GetLengthOfDOMNode(aFragmentAsNode, (uint32_t&)fragLen);
michael@0:     NS_ENSURE_SUCCESS(rv, rv);
michael@0: 
michael@0:     aStartNode = aFragmentAsNode;
michael@0:     aStartOffset = 0;
michael@0:     aEndNode = aFragmentAsNode;
michael@0:     aEndOffset = fragLen;
michael@0:   }
michael@0: 
michael@0:   nsRefPtr docFragRange;
michael@0:   rv = nsRange::CreateRange(aStartNode, aStartOffset, aEndNode, aEndOffset, getter_AddRefs(docFragRange));
michael@0:   NS_ENSURE_SUCCESS(rv, rv);
michael@0: 
michael@0:   // now use a subtree iterator over the range to create a list of nodes
michael@0:   nsTrivialFunctor functor;
michael@0:   nsDOMSubtreeIterator iter;
michael@0:   rv = iter.Init(docFragRange);
michael@0:   NS_ENSURE_SUCCESS(rv, rv);
michael@0: 
michael@0:   return iter.AppendList(functor, outNodeList);
michael@0: }
michael@0: 
michael@0: nsresult
michael@0: nsHTMLEditor::GetListAndTableParents(bool aEnd,
michael@0:                                      nsCOMArray& aListOfNodes,
michael@0:                                      nsCOMArray& outArray)
michael@0: {
michael@0:   int32_t listCount = aListOfNodes.Count();
michael@0:   NS_ENSURE_TRUE(listCount > 0, NS_ERROR_FAILURE);  // no empty lists, please
michael@0: 
michael@0:   // build up list of parents of first (or last) node in list
michael@0:   // that are either lists, or tables.
michael@0:   int32_t idx = 0;
michael@0:   if (aEnd) idx = listCount-1;
michael@0: 
michael@0:   nsCOMPtr pNode = aListOfNodes[idx];
michael@0:   while (pNode)
michael@0:   {
michael@0:     if (nsHTMLEditUtils::IsList(pNode) || nsHTMLEditUtils::IsTable(pNode))
michael@0:     {
michael@0:       NS_ENSURE_TRUE(outArray.AppendObject(pNode), NS_ERROR_FAILURE);
michael@0:     }
michael@0:     nsCOMPtr parent;
michael@0:     pNode->GetParentNode(getter_AddRefs(parent));
michael@0:     pNode = parent;
michael@0:   }
michael@0:   return NS_OK;
michael@0: }
michael@0: 
michael@0: nsresult
michael@0: nsHTMLEditor::DiscoverPartialListsAndTables(nsCOMArray& aPasteNodes,
michael@0:                                             nsCOMArray& aListsAndTables,
michael@0:                                             int32_t *outHighWaterMark)
michael@0: {
michael@0:   NS_ENSURE_TRUE(outHighWaterMark, NS_ERROR_NULL_POINTER);
michael@0:   
michael@0:   *outHighWaterMark = -1;
michael@0:   int32_t listAndTableParents = aListsAndTables.Count();
michael@0:   
michael@0:   // scan insertion list for table elements (other than table).
michael@0:   int32_t listCount = aPasteNodes.Count();
michael@0:   int32_t j;  
michael@0:   for (j=0; j curNode = aPasteNodes[j];
michael@0: 
michael@0:     NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE);
michael@0:     if (nsHTMLEditUtils::IsTableElement(curNode) && !nsHTMLEditUtils::IsTable(curNode))
michael@0:     {
michael@0:       nsCOMPtr theTable = GetTableParent(curNode);
michael@0:       if (theTable)
michael@0:       {
michael@0:         int32_t indexT = aListsAndTables.IndexOf(theTable);
michael@0:         if (indexT >= 0)
michael@0:         {
michael@0:           *outHighWaterMark = indexT;
michael@0:           if (*outHighWaterMark == listAndTableParents-1) break;
michael@0:         }
michael@0:         else
michael@0:         {
michael@0:           break;
michael@0:         }
michael@0:       }
michael@0:     }
michael@0:     if (nsHTMLEditUtils::IsListItem(curNode))
michael@0:     {
michael@0:       nsCOMPtr theList = GetListParent(curNode);
michael@0:       if (theList)
michael@0:       {
michael@0:         int32_t indexL = aListsAndTables.IndexOf(theList);
michael@0:         if (indexL >= 0)
michael@0:         {
michael@0:           *outHighWaterMark = indexL;
michael@0:           if (*outHighWaterMark == listAndTableParents-1) break;
michael@0:         }
michael@0:         else
michael@0:         {
michael@0:           break;
michael@0:         }
michael@0:       }
michael@0:     }
michael@0:   }
michael@0:   return NS_OK;
michael@0: }
michael@0: 
michael@0: nsresult
michael@0: nsHTMLEditor::ScanForListAndTableStructure( bool aEnd,
michael@0:                                             nsCOMArray& aNodes,
michael@0:                                             nsIDOMNode *aListOrTable,
michael@0:                                             nsCOMPtr *outReplaceNode)
michael@0: {
michael@0:   NS_ENSURE_TRUE(aListOrTable, NS_ERROR_NULL_POINTER);
michael@0:   NS_ENSURE_TRUE(outReplaceNode, NS_ERROR_NULL_POINTER);
michael@0: 
michael@0:   *outReplaceNode = 0;
michael@0:   
michael@0:   // look upward from first/last paste node for a piece of this list/table
michael@0:   int32_t listCount = aNodes.Count(), idx = 0;
michael@0:   if (aEnd) idx = listCount-1;
michael@0:   bool bList = nsHTMLEditUtils::IsList(aListOrTable);
michael@0:   
michael@0:   nsCOMPtr  pNode = aNodes[idx];
michael@0:   nsCOMPtr  originalNode = pNode;
michael@0:   while (pNode)
michael@0:   {
michael@0:     if ((bList && nsHTMLEditUtils::IsListItem(pNode)) ||
michael@0:         (!bList && (nsHTMLEditUtils::IsTableElement(pNode) && !nsHTMLEditUtils::IsTable(pNode))))
michael@0:     {
michael@0:       nsCOMPtr structureNode;
michael@0:       if (bList) structureNode = GetListParent(pNode);
michael@0:       else structureNode = GetTableParent(pNode);
michael@0:       if (structureNode == aListOrTable)
michael@0:       {
michael@0:         if (bList)
michael@0:           *outReplaceNode = structureNode;
michael@0:         else
michael@0:           *outReplaceNode = pNode;
michael@0:         break;
michael@0:       }
michael@0:     }
michael@0:     nsCOMPtr parent;
michael@0:     pNode->GetParentNode(getter_AddRefs(parent));
michael@0:     pNode = parent;
michael@0:   }
michael@0:   return NS_OK;
michael@0: }
michael@0: 
michael@0: nsresult
michael@0: nsHTMLEditor::ReplaceOrphanedStructure(bool aEnd,
michael@0:                                        nsCOMArray& aNodeArray,
michael@0:                                        nsCOMArray& aListAndTableArray,
michael@0:                                        int32_t aHighWaterMark)
michael@0: {
michael@0:   nsCOMPtr curNode = aListAndTableArray[aHighWaterMark];
michael@0:   NS_ENSURE_TRUE(curNode, NS_ERROR_NULL_POINTER);
michael@0: 
michael@0:   nsCOMPtr replaceNode, originalNode;
michael@0: 
michael@0:   // find substructure of list or table that must be included in paste.
michael@0:   nsresult rv = ScanForListAndTableStructure(aEnd, aNodeArray,
michael@0:                                  curNode, address_of(replaceNode));
michael@0:   NS_ENSURE_SUCCESS(rv, rv);
michael@0: 
michael@0:   // if we found substructure, paste it instead of its descendants
michael@0:   if (replaceNode)
michael@0:   {
michael@0:     // postprocess list to remove any descendants of this node
michael@0:     // so that we don't insert them twice.
michael@0:     nsCOMPtr endpoint;
michael@0:     do
michael@0:     {
michael@0:       endpoint = GetArrayEndpoint(aEnd, aNodeArray);
michael@0:       if (!endpoint) break;
michael@0:       if (nsEditorUtils::IsDescendantOf(endpoint, replaceNode))
michael@0:         aNodeArray.RemoveObject(endpoint);
michael@0:       else
michael@0:         break;
michael@0:     } while(endpoint);
michael@0: 
michael@0:     // now replace the removed nodes with the structural parent
michael@0:     if (aEnd) aNodeArray.AppendObject(replaceNode);
michael@0:     else aNodeArray.InsertObjectAt(replaceNode, 0);
michael@0:   }
michael@0:   return NS_OK;
michael@0: }
michael@0: 
michael@0: nsIDOMNode* nsHTMLEditor::GetArrayEndpoint(bool aEnd,
michael@0:                                            nsCOMArray& aNodeArray)
michael@0: {
michael@0:   int32_t listCount = aNodeArray.Count();
michael@0:   if (listCount <= 0) {
michael@0:     return nullptr;
michael@0:   }
michael@0: 
michael@0:   if (aEnd) {
michael@0:     return aNodeArray[listCount-1];
michael@0:   }
michael@0: 
michael@0:   return aNodeArray[0];
michael@0: }