|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 /* |
|
7 * Object that can be used to serialize selections, ranges, or nodes |
|
8 * to strings in a gazillion different ways. |
|
9 */ |
|
10 |
|
11 #include "nsIDocumentEncoder.h" |
|
12 |
|
13 #include "nscore.h" |
|
14 #include "nsIFactory.h" |
|
15 #include "nsISupports.h" |
|
16 #include "nsIComponentManager.h" |
|
17 #include "nsIServiceManager.h" |
|
18 #include "nsIDocument.h" |
|
19 #include "nsIHTMLDocument.h" |
|
20 #include "nsCOMPtr.h" |
|
21 #include "nsIContentSerializer.h" |
|
22 #include "nsIUnicodeEncoder.h" |
|
23 #include "nsIOutputStream.h" |
|
24 #include "nsIDOMElement.h" |
|
25 #include "nsIDOMText.h" |
|
26 #include "nsIDOMCDATASection.h" |
|
27 #include "nsIDOMComment.h" |
|
28 #include "nsIDOMProcessingInstruction.h" |
|
29 #include "nsIDOMDocumentType.h" |
|
30 #include "nsIDOMNodeList.h" |
|
31 #include "nsRange.h" |
|
32 #include "nsIDOMRange.h" |
|
33 #include "nsIDOMDocument.h" |
|
34 #include "nsICharsetConverterManager.h" |
|
35 #include "nsGkAtoms.h" |
|
36 #include "nsIContent.h" |
|
37 #include "nsIParserService.h" |
|
38 #include "nsIScriptContext.h" |
|
39 #include "nsIScriptGlobalObject.h" |
|
40 #include "nsIScriptSecurityManager.h" |
|
41 #include "mozilla/dom/Selection.h" |
|
42 #include "nsISelectionPrivate.h" |
|
43 #include "nsITransferable.h" // for kUnicodeMime |
|
44 #include "nsContentUtils.h" |
|
45 #include "nsNodeUtils.h" |
|
46 #include "nsUnicharUtils.h" |
|
47 #include "nsReadableUtils.h" |
|
48 #include "nsTArray.h" |
|
49 #include "nsIFrame.h" |
|
50 #include "nsStringBuffer.h" |
|
51 #include "mozilla/dom/Element.h" |
|
52 #include "mozilla/dom/ShadowRoot.h" |
|
53 #include "nsIEditor.h" |
|
54 #include "nsIHTMLEditor.h" |
|
55 #include "nsIDocShell.h" |
|
56 |
|
57 using namespace mozilla; |
|
58 using namespace mozilla::dom; |
|
59 |
|
60 nsresult NS_NewDomSelection(nsISelection **aDomSelection); |
|
61 |
|
62 enum nsRangeIterationDirection { |
|
63 kDirectionOut = -1, |
|
64 kDirectionIn = 1 |
|
65 }; |
|
66 |
|
67 class nsDocumentEncoder : public nsIDocumentEncoder |
|
68 { |
|
69 public: |
|
70 nsDocumentEncoder(); |
|
71 virtual ~nsDocumentEncoder(); |
|
72 |
|
73 NS_DECL_CYCLE_COLLECTING_ISUPPORTS |
|
74 NS_DECL_CYCLE_COLLECTION_CLASS(nsDocumentEncoder) |
|
75 NS_DECL_NSIDOCUMENTENCODER |
|
76 |
|
77 protected: |
|
78 void Initialize(bool aClearCachedSerializer = true); |
|
79 nsresult SerializeNodeStart(nsINode* aNode, int32_t aStartOffset, |
|
80 int32_t aEndOffset, nsAString& aStr, |
|
81 nsINode* aOriginalNode = nullptr); |
|
82 nsresult SerializeToStringRecursive(nsINode* aNode, |
|
83 nsAString& aStr, |
|
84 bool aDontSerializeRoot, |
|
85 uint32_t aMaxLength = 0); |
|
86 nsresult SerializeNodeEnd(nsINode* aNode, nsAString& aStr); |
|
87 // This serializes the content of aNode. |
|
88 nsresult SerializeToStringIterative(nsINode* aNode, |
|
89 nsAString& aStr); |
|
90 nsresult SerializeRangeToString(nsRange *aRange, |
|
91 nsAString& aOutputString); |
|
92 nsresult SerializeRangeNodes(nsRange* aRange, |
|
93 nsINode* aNode, |
|
94 nsAString& aString, |
|
95 int32_t aDepth); |
|
96 nsresult SerializeRangeContextStart(const nsTArray<nsINode*>& aAncestorArray, |
|
97 nsAString& aString); |
|
98 nsresult SerializeRangeContextEnd(const nsTArray<nsINode*>& aAncestorArray, |
|
99 nsAString& aString); |
|
100 virtual int32_t |
|
101 GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray) |
|
102 { |
|
103 return -1; |
|
104 } |
|
105 |
|
106 nsresult FlushText(nsAString& aString, bool aForce); |
|
107 |
|
108 bool IsVisibleNode(nsINode* aNode) |
|
109 { |
|
110 NS_PRECONDITION(aNode, ""); |
|
111 |
|
112 if (mFlags & SkipInvisibleContent) { |
|
113 // Treat the visibility of the ShadowRoot as if it were |
|
114 // the host content. |
|
115 nsCOMPtr<nsIContent> content; |
|
116 ShadowRoot* shadowRoot = ShadowRoot::FromNode(aNode); |
|
117 if (shadowRoot) { |
|
118 content = shadowRoot->GetHost(); |
|
119 } else { |
|
120 content = do_QueryInterface(aNode); |
|
121 } |
|
122 |
|
123 if (content) { |
|
124 nsIFrame* frame = content->GetPrimaryFrame(); |
|
125 if (!frame) { |
|
126 if (aNode->IsNodeOfType(nsINode::eTEXT)) { |
|
127 // We have already checked that our parent is visible. |
|
128 return true; |
|
129 } |
|
130 return false; |
|
131 } |
|
132 bool isVisible = frame->StyleVisibility()->IsVisible(); |
|
133 if (!isVisible && aNode->IsNodeOfType(nsINode::eTEXT)) |
|
134 return false; |
|
135 } |
|
136 } |
|
137 return true; |
|
138 } |
|
139 |
|
140 static bool IsTag(nsIContent* aContent, nsIAtom* aAtom); |
|
141 |
|
142 virtual bool IncludeInContext(nsINode *aNode); |
|
143 |
|
144 nsCOMPtr<nsIDocument> mDocument; |
|
145 nsCOMPtr<nsISelection> mSelection; |
|
146 nsRefPtr<nsRange> mRange; |
|
147 nsCOMPtr<nsINode> mNode; |
|
148 nsCOMPtr<nsIOutputStream> mStream; |
|
149 nsCOMPtr<nsIContentSerializer> mSerializer; |
|
150 nsCOMPtr<nsIUnicodeEncoder> mUnicodeEncoder; |
|
151 nsCOMPtr<nsINode> mCommonParent; |
|
152 nsCOMPtr<nsIDocumentEncoderNodeFixup> mNodeFixup; |
|
153 nsCOMPtr<nsICharsetConverterManager> mCharsetConverterManager; |
|
154 |
|
155 nsString mMimeType; |
|
156 nsCString mCharset; |
|
157 uint32_t mFlags; |
|
158 uint32_t mWrapColumn; |
|
159 uint32_t mStartDepth; |
|
160 uint32_t mEndDepth; |
|
161 int32_t mStartRootIndex; |
|
162 int32_t mEndRootIndex; |
|
163 nsAutoTArray<nsINode*, 8> mCommonAncestors; |
|
164 nsAutoTArray<nsIContent*, 8> mStartNodes; |
|
165 nsAutoTArray<int32_t, 8> mStartOffsets; |
|
166 nsAutoTArray<nsIContent*, 8> mEndNodes; |
|
167 nsAutoTArray<int32_t, 8> mEndOffsets; |
|
168 bool mHaltRangeHint; |
|
169 // Used when context has already been serialized for |
|
170 // table cell selections (where parent is <tr>) |
|
171 bool mDisableContextSerialize; |
|
172 bool mIsCopying; // Set to true only while copying |
|
173 bool mNodeIsContainer; |
|
174 nsStringBuffer* mCachedBuffer; |
|
175 }; |
|
176 |
|
177 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocumentEncoder) |
|
178 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDocumentEncoder) |
|
179 |
|
180 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocumentEncoder) |
|
181 NS_INTERFACE_MAP_ENTRY(nsIDocumentEncoder) |
|
182 NS_INTERFACE_MAP_ENTRY(nsISupports) |
|
183 NS_INTERFACE_MAP_END |
|
184 |
|
185 NS_IMPL_CYCLE_COLLECTION(nsDocumentEncoder, |
|
186 mDocument, mSelection, mRange, mNode, mCommonParent) |
|
187 |
|
188 nsDocumentEncoder::nsDocumentEncoder() : mCachedBuffer(nullptr) |
|
189 { |
|
190 Initialize(); |
|
191 mMimeType.AssignLiteral("text/plain"); |
|
192 } |
|
193 |
|
194 void nsDocumentEncoder::Initialize(bool aClearCachedSerializer) |
|
195 { |
|
196 mFlags = 0; |
|
197 mWrapColumn = 72; |
|
198 mStartDepth = 0; |
|
199 mEndDepth = 0; |
|
200 mStartRootIndex = 0; |
|
201 mEndRootIndex = 0; |
|
202 mHaltRangeHint = false; |
|
203 mDisableContextSerialize = false; |
|
204 mNodeIsContainer = false; |
|
205 if (aClearCachedSerializer) { |
|
206 mSerializer = nullptr; |
|
207 } |
|
208 } |
|
209 |
|
210 nsDocumentEncoder::~nsDocumentEncoder() |
|
211 { |
|
212 if (mCachedBuffer) { |
|
213 mCachedBuffer->Release(); |
|
214 } |
|
215 } |
|
216 |
|
217 NS_IMETHODIMP |
|
218 nsDocumentEncoder::Init(nsIDOMDocument* aDocument, |
|
219 const nsAString& aMimeType, |
|
220 uint32_t aFlags) |
|
221 { |
|
222 if (!aDocument) |
|
223 return NS_ERROR_INVALID_ARG; |
|
224 |
|
225 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDocument); |
|
226 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); |
|
227 |
|
228 return NativeInit(doc, aMimeType, aFlags); |
|
229 } |
|
230 |
|
231 NS_IMETHODIMP |
|
232 nsDocumentEncoder::NativeInit(nsIDocument* aDocument, |
|
233 const nsAString& aMimeType, |
|
234 uint32_t aFlags) |
|
235 { |
|
236 if (!aDocument) |
|
237 return NS_ERROR_INVALID_ARG; |
|
238 |
|
239 Initialize(!mMimeType.Equals(aMimeType)); |
|
240 |
|
241 mDocument = aDocument; |
|
242 |
|
243 mMimeType = aMimeType; |
|
244 |
|
245 mFlags = aFlags; |
|
246 mIsCopying = false; |
|
247 |
|
248 return NS_OK; |
|
249 } |
|
250 |
|
251 NS_IMETHODIMP |
|
252 nsDocumentEncoder::SetWrapColumn(uint32_t aWC) |
|
253 { |
|
254 mWrapColumn = aWC; |
|
255 return NS_OK; |
|
256 } |
|
257 |
|
258 NS_IMETHODIMP |
|
259 nsDocumentEncoder::SetSelection(nsISelection* aSelection) |
|
260 { |
|
261 mSelection = aSelection; |
|
262 return NS_OK; |
|
263 } |
|
264 |
|
265 NS_IMETHODIMP |
|
266 nsDocumentEncoder::SetRange(nsIDOMRange* aRange) |
|
267 { |
|
268 mRange = static_cast<nsRange*>(aRange); |
|
269 return NS_OK; |
|
270 } |
|
271 |
|
272 NS_IMETHODIMP |
|
273 nsDocumentEncoder::SetNode(nsIDOMNode* aNode) |
|
274 { |
|
275 mNodeIsContainer = false; |
|
276 mNode = do_QueryInterface(aNode); |
|
277 return NS_OK; |
|
278 } |
|
279 |
|
280 NS_IMETHODIMP |
|
281 nsDocumentEncoder::SetNativeNode(nsINode* aNode) |
|
282 { |
|
283 mNodeIsContainer = false; |
|
284 mNode = aNode; |
|
285 return NS_OK; |
|
286 } |
|
287 |
|
288 NS_IMETHODIMP |
|
289 nsDocumentEncoder::SetContainerNode(nsIDOMNode *aContainer) |
|
290 { |
|
291 mNodeIsContainer = true; |
|
292 mNode = do_QueryInterface(aContainer); |
|
293 return NS_OK; |
|
294 } |
|
295 |
|
296 NS_IMETHODIMP |
|
297 nsDocumentEncoder::SetNativeContainerNode(nsINode* aContainer) |
|
298 { |
|
299 mNodeIsContainer = true; |
|
300 mNode = aContainer; |
|
301 return NS_OK; |
|
302 } |
|
303 |
|
304 NS_IMETHODIMP |
|
305 nsDocumentEncoder::SetCharset(const nsACString& aCharset) |
|
306 { |
|
307 mCharset = aCharset; |
|
308 return NS_OK; |
|
309 } |
|
310 |
|
311 NS_IMETHODIMP |
|
312 nsDocumentEncoder::GetMimeType(nsAString& aMimeType) |
|
313 { |
|
314 aMimeType = mMimeType; |
|
315 return NS_OK; |
|
316 } |
|
317 |
|
318 |
|
319 bool |
|
320 nsDocumentEncoder::IncludeInContext(nsINode *aNode) |
|
321 { |
|
322 return false; |
|
323 } |
|
324 |
|
325 static |
|
326 bool |
|
327 IsInvisibleBreak(nsINode *aNode) { |
|
328 // xxxehsan: we should probably figure out a way to determine |
|
329 // if a BR node is visible without using the editor. |
|
330 Element* elt = aNode->AsElement(); |
|
331 if (!elt->IsHTML(nsGkAtoms::br) || |
|
332 !aNode->IsEditable()) { |
|
333 return false; |
|
334 } |
|
335 |
|
336 // Grab the editor associated with the document |
|
337 nsIDocument *doc = aNode->GetCurrentDoc(); |
|
338 if (doc) { |
|
339 nsPIDOMWindow *window = doc->GetWindow(); |
|
340 if (window) { |
|
341 nsIDocShell *docShell = window->GetDocShell(); |
|
342 if (docShell) { |
|
343 nsCOMPtr<nsIEditor> editor; |
|
344 docShell->GetEditor(getter_AddRefs(editor)); |
|
345 nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(editor); |
|
346 if (htmlEditor) { |
|
347 bool isVisible = false; |
|
348 nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(aNode); |
|
349 htmlEditor->BreakIsVisible(domNode, &isVisible); |
|
350 return !isVisible; |
|
351 } |
|
352 } |
|
353 } |
|
354 } |
|
355 return false; |
|
356 } |
|
357 |
|
358 nsresult |
|
359 nsDocumentEncoder::SerializeNodeStart(nsINode* aNode, |
|
360 int32_t aStartOffset, |
|
361 int32_t aEndOffset, |
|
362 nsAString& aStr, |
|
363 nsINode* aOriginalNode) |
|
364 { |
|
365 if (!IsVisibleNode(aNode)) |
|
366 return NS_OK; |
|
367 |
|
368 nsINode* node = nullptr; |
|
369 nsCOMPtr<nsINode> fixedNodeKungfuDeathGrip; |
|
370 |
|
371 // Caller didn't do fixup, so we'll do it ourselves |
|
372 if (!aOriginalNode) { |
|
373 aOriginalNode = aNode; |
|
374 if (mNodeFixup) { |
|
375 bool dummy; |
|
376 nsCOMPtr<nsIDOMNode> domNodeIn = do_QueryInterface(aNode); |
|
377 nsCOMPtr<nsIDOMNode> domNodeOut; |
|
378 mNodeFixup->FixupNode(domNodeIn, &dummy, getter_AddRefs(domNodeOut)); |
|
379 fixedNodeKungfuDeathGrip = do_QueryInterface(domNodeOut); |
|
380 node = fixedNodeKungfuDeathGrip; |
|
381 } |
|
382 } |
|
383 |
|
384 // Either there was no fixed-up node, |
|
385 // or the caller did fixup themselves and aNode is already fixed |
|
386 if (!node) |
|
387 node = aNode; |
|
388 |
|
389 if (node->IsElement()) { |
|
390 if ((mFlags & (nsIDocumentEncoder::OutputPreformatted | |
|
391 nsIDocumentEncoder::OutputDropInvisibleBreak)) && |
|
392 IsInvisibleBreak(node)) { |
|
393 return NS_OK; |
|
394 } |
|
395 Element* originalElement = |
|
396 aOriginalNode && aOriginalNode->IsElement() ? |
|
397 aOriginalNode->AsElement() : nullptr; |
|
398 mSerializer->AppendElementStart(node->AsElement(), |
|
399 originalElement, aStr); |
|
400 return NS_OK; |
|
401 } |
|
402 |
|
403 switch (node->NodeType()) { |
|
404 case nsIDOMNode::TEXT_NODE: |
|
405 { |
|
406 mSerializer->AppendText(static_cast<nsIContent*>(node), |
|
407 aStartOffset, aEndOffset, aStr); |
|
408 break; |
|
409 } |
|
410 case nsIDOMNode::CDATA_SECTION_NODE: |
|
411 { |
|
412 mSerializer->AppendCDATASection(static_cast<nsIContent*>(node), |
|
413 aStartOffset, aEndOffset, aStr); |
|
414 break; |
|
415 } |
|
416 case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: |
|
417 { |
|
418 mSerializer->AppendProcessingInstruction(static_cast<nsIContent*>(node), |
|
419 aStartOffset, aEndOffset, aStr); |
|
420 break; |
|
421 } |
|
422 case nsIDOMNode::COMMENT_NODE: |
|
423 { |
|
424 mSerializer->AppendComment(static_cast<nsIContent*>(node), |
|
425 aStartOffset, aEndOffset, aStr); |
|
426 break; |
|
427 } |
|
428 case nsIDOMNode::DOCUMENT_TYPE_NODE: |
|
429 { |
|
430 mSerializer->AppendDoctype(static_cast<nsIContent*>(node), aStr); |
|
431 break; |
|
432 } |
|
433 } |
|
434 |
|
435 return NS_OK; |
|
436 } |
|
437 |
|
438 nsresult |
|
439 nsDocumentEncoder::SerializeNodeEnd(nsINode* aNode, |
|
440 nsAString& aStr) |
|
441 { |
|
442 if (!IsVisibleNode(aNode)) |
|
443 return NS_OK; |
|
444 |
|
445 if (aNode->IsElement()) { |
|
446 mSerializer->AppendElementEnd(aNode->AsElement(), aStr); |
|
447 } |
|
448 return NS_OK; |
|
449 } |
|
450 |
|
451 nsresult |
|
452 nsDocumentEncoder::SerializeToStringRecursive(nsINode* aNode, |
|
453 nsAString& aStr, |
|
454 bool aDontSerializeRoot, |
|
455 uint32_t aMaxLength) |
|
456 { |
|
457 if (aMaxLength > 0 && aStr.Length() >= aMaxLength) { |
|
458 return NS_OK; |
|
459 } |
|
460 |
|
461 if (!IsVisibleNode(aNode)) |
|
462 return NS_OK; |
|
463 |
|
464 nsresult rv = NS_OK; |
|
465 bool serializeClonedChildren = false; |
|
466 nsINode* maybeFixedNode = nullptr; |
|
467 |
|
468 // Keep the node from FixupNode alive. |
|
469 nsCOMPtr<nsINode> fixedNodeKungfuDeathGrip; |
|
470 if (mNodeFixup) { |
|
471 nsCOMPtr<nsIDOMNode> domNodeIn = do_QueryInterface(aNode); |
|
472 nsCOMPtr<nsIDOMNode> domNodeOut; |
|
473 mNodeFixup->FixupNode(domNodeIn, &serializeClonedChildren, getter_AddRefs(domNodeOut)); |
|
474 fixedNodeKungfuDeathGrip = do_QueryInterface(domNodeOut); |
|
475 maybeFixedNode = fixedNodeKungfuDeathGrip; |
|
476 } |
|
477 |
|
478 if (!maybeFixedNode) |
|
479 maybeFixedNode = aNode; |
|
480 |
|
481 if ((mFlags & SkipInvisibleContent) && |
|
482 !(mFlags & OutputNonTextContentAsPlaceholder)) { |
|
483 if (aNode->IsNodeOfType(nsINode::eCONTENT)) { |
|
484 nsIFrame* frame = static_cast<nsIContent*>(aNode)->GetPrimaryFrame(); |
|
485 if (frame) { |
|
486 bool isSelectable; |
|
487 frame->IsSelectable(&isSelectable, nullptr); |
|
488 if (!isSelectable){ |
|
489 aDontSerializeRoot = true; |
|
490 } |
|
491 } |
|
492 } |
|
493 } |
|
494 |
|
495 if (!aDontSerializeRoot) { |
|
496 int32_t endOffset = -1; |
|
497 if (aMaxLength > 0) { |
|
498 MOZ_ASSERT(aMaxLength >= aStr.Length()); |
|
499 endOffset = aMaxLength - aStr.Length(); |
|
500 } |
|
501 rv = SerializeNodeStart(maybeFixedNode, 0, endOffset, aStr, aNode); |
|
502 NS_ENSURE_SUCCESS(rv, rv); |
|
503 } |
|
504 |
|
505 nsINode* node = serializeClonedChildren ? maybeFixedNode : aNode; |
|
506 |
|
507 for (nsINode* child = nsNodeUtils::GetFirstChildOfTemplateOrNode(node); |
|
508 child; |
|
509 child = child->GetNextSibling()) { |
|
510 rv = SerializeToStringRecursive(child, aStr, false, aMaxLength); |
|
511 NS_ENSURE_SUCCESS(rv, rv); |
|
512 } |
|
513 |
|
514 if (!aDontSerializeRoot) { |
|
515 rv = SerializeNodeEnd(node, aStr); |
|
516 NS_ENSURE_SUCCESS(rv, rv); |
|
517 } |
|
518 |
|
519 return FlushText(aStr, false); |
|
520 } |
|
521 |
|
522 nsresult |
|
523 nsDocumentEncoder::SerializeToStringIterative(nsINode* aNode, |
|
524 nsAString& aStr) |
|
525 { |
|
526 nsresult rv; |
|
527 |
|
528 nsINode* node = aNode->GetFirstChild(); |
|
529 while (node) { |
|
530 nsINode* current = node; |
|
531 rv = SerializeNodeStart(current, 0, -1, aStr, current); |
|
532 NS_ENSURE_SUCCESS(rv, rv); |
|
533 node = current->GetFirstChild(); |
|
534 while (!node && current && current != aNode) { |
|
535 rv = SerializeNodeEnd(current, aStr); |
|
536 NS_ENSURE_SUCCESS(rv, rv); |
|
537 // Check if we have siblings. |
|
538 node = current->GetNextSibling(); |
|
539 if (!node) { |
|
540 // Perhaps parent node has siblings. |
|
541 current = current->GetParentNode(); |
|
542 } |
|
543 } |
|
544 } |
|
545 |
|
546 return NS_OK; |
|
547 } |
|
548 |
|
549 bool |
|
550 nsDocumentEncoder::IsTag(nsIContent* aContent, nsIAtom* aAtom) |
|
551 { |
|
552 return aContent && aContent->Tag() == aAtom; |
|
553 } |
|
554 |
|
555 static nsresult |
|
556 ConvertAndWrite(const nsAString& aString, |
|
557 nsIOutputStream* aStream, |
|
558 nsIUnicodeEncoder* aEncoder) |
|
559 { |
|
560 NS_ENSURE_ARG_POINTER(aStream); |
|
561 NS_ENSURE_ARG_POINTER(aEncoder); |
|
562 nsresult rv; |
|
563 int32_t charLength, startCharLength; |
|
564 const nsPromiseFlatString& flat = PromiseFlatString(aString); |
|
565 const char16_t* unicodeBuf = flat.get(); |
|
566 int32_t unicodeLength = aString.Length(); |
|
567 int32_t startLength = unicodeLength; |
|
568 |
|
569 rv = aEncoder->GetMaxLength(unicodeBuf, unicodeLength, &charLength); |
|
570 startCharLength = charLength; |
|
571 NS_ENSURE_SUCCESS(rv, rv); |
|
572 |
|
573 if (!charLength) { |
|
574 // Nothing to write. Besides, a length 0 string has an immutable buffer, so |
|
575 // attempts to null-terminate it will crash. |
|
576 return NS_OK; |
|
577 } |
|
578 |
|
579 nsAutoCString charXferString; |
|
580 if (!charXferString.SetLength(charLength, fallible_t())) |
|
581 return NS_ERROR_OUT_OF_MEMORY; |
|
582 |
|
583 char* charXferBuf = charXferString.BeginWriting(); |
|
584 nsresult convert_rv = NS_OK; |
|
585 |
|
586 do { |
|
587 unicodeLength = startLength; |
|
588 charLength = startCharLength; |
|
589 |
|
590 convert_rv = aEncoder->Convert(unicodeBuf, &unicodeLength, charXferBuf, &charLength); |
|
591 NS_ENSURE_SUCCESS(convert_rv, convert_rv); |
|
592 |
|
593 // Make sure charXferBuf is null-terminated before we call |
|
594 // Write(). |
|
595 |
|
596 charXferBuf[charLength] = '\0'; |
|
597 |
|
598 uint32_t written; |
|
599 rv = aStream->Write(charXferBuf, charLength, &written); |
|
600 NS_ENSURE_SUCCESS(rv, rv); |
|
601 |
|
602 // If the converter couldn't convert a chraacer we replace the |
|
603 // character with a characre entity. |
|
604 if (convert_rv == NS_ERROR_UENC_NOMAPPING) { |
|
605 // Finishes the conversion. |
|
606 // The converter has the possibility to write some extra data and flush its final state. |
|
607 char finish_buf[33]; |
|
608 charLength = sizeof(finish_buf) - 1; |
|
609 rv = aEncoder->Finish(finish_buf, &charLength); |
|
610 NS_ENSURE_SUCCESS(rv, rv); |
|
611 |
|
612 // Make sure finish_buf is null-terminated before we call |
|
613 // Write(). |
|
614 |
|
615 finish_buf[charLength] = '\0'; |
|
616 |
|
617 rv = aStream->Write(finish_buf, charLength, &written); |
|
618 NS_ENSURE_SUCCESS(rv, rv); |
|
619 |
|
620 nsAutoCString entString("&#"); |
|
621 if (NS_IS_HIGH_SURROGATE(unicodeBuf[unicodeLength - 1]) && |
|
622 unicodeLength < startLength && NS_IS_LOW_SURROGATE(unicodeBuf[unicodeLength])) { |
|
623 entString.AppendInt(SURROGATE_TO_UCS4(unicodeBuf[unicodeLength - 1], |
|
624 unicodeBuf[unicodeLength])); |
|
625 unicodeLength += 1; |
|
626 } |
|
627 else |
|
628 entString.AppendInt(unicodeBuf[unicodeLength - 1]); |
|
629 entString.Append(';'); |
|
630 |
|
631 // Since entString is an nsAutoCString we know entString.get() |
|
632 // returns a null-terminated string, so no need for extra |
|
633 // null-termination before calling Write() here. |
|
634 |
|
635 rv = aStream->Write(entString.get(), entString.Length(), &written); |
|
636 NS_ENSURE_SUCCESS(rv, rv); |
|
637 |
|
638 unicodeBuf += unicodeLength; |
|
639 startLength -= unicodeLength; |
|
640 } |
|
641 } while (convert_rv == NS_ERROR_UENC_NOMAPPING); |
|
642 |
|
643 return rv; |
|
644 } |
|
645 |
|
646 nsresult |
|
647 nsDocumentEncoder::FlushText(nsAString& aString, bool aForce) |
|
648 { |
|
649 if (!mStream) |
|
650 return NS_OK; |
|
651 |
|
652 nsresult rv = NS_OK; |
|
653 |
|
654 if (aString.Length() > 1024 || aForce) { |
|
655 rv = ConvertAndWrite(aString, mStream, mUnicodeEncoder); |
|
656 |
|
657 aString.Truncate(); |
|
658 } |
|
659 |
|
660 return rv; |
|
661 } |
|
662 |
|
663 #if 0 // This code is really fast at serializing a range, but unfortunately |
|
664 // there are problems with it so we don't use it now, maybe later... |
|
665 static nsresult ChildAt(nsIDOMNode* aNode, int32_t aIndex, nsIDOMNode*& aChild) |
|
666 { |
|
667 nsCOMPtr<nsIContent> content(do_QueryInterface(aNode)); |
|
668 |
|
669 aChild = nullptr; |
|
670 |
|
671 NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); |
|
672 |
|
673 nsIContent *child = content->GetChildAt(aIndex); |
|
674 |
|
675 if (child) |
|
676 return CallQueryInterface(child, &aChild); |
|
677 |
|
678 return NS_OK; |
|
679 } |
|
680 |
|
681 static int32_t IndexOf(nsIDOMNode* aParent, nsIDOMNode* aChild) |
|
682 { |
|
683 nsCOMPtr<nsIContent> parent(do_QueryInterface(aParent)); |
|
684 nsCOMPtr<nsIContent> child(do_QueryInterface(aChild)); |
|
685 |
|
686 if (!parent) |
|
687 return -1; |
|
688 |
|
689 return parent->IndexOf(child); |
|
690 } |
|
691 |
|
692 static inline int32_t GetIndex(nsTArray<int32_t>& aIndexArray) |
|
693 { |
|
694 int32_t count = aIndexArray.Length(); |
|
695 |
|
696 if (count) { |
|
697 return aIndexArray.ElementAt(count - 1); |
|
698 } |
|
699 |
|
700 return 0; |
|
701 } |
|
702 |
|
703 static nsresult GetNextNode(nsIDOMNode* aNode, nsTArray<int32_t>& aIndexArray, |
|
704 nsIDOMNode*& aNextNode, |
|
705 nsRangeIterationDirection& aDirection) |
|
706 { |
|
707 bool hasChildren; |
|
708 |
|
709 aNextNode = nullptr; |
|
710 |
|
711 aNode->HasChildNodes(&hasChildren); |
|
712 |
|
713 if (hasChildren && aDirection == kDirectionIn) { |
|
714 ChildAt(aNode, 0, aNextNode); |
|
715 NS_ENSURE_TRUE(aNextNode, NS_ERROR_FAILURE); |
|
716 |
|
717 aIndexArray.AppendElement(0); |
|
718 |
|
719 aDirection = kDirectionIn; |
|
720 } else if (aDirection == kDirectionIn) { |
|
721 aNextNode = aNode; |
|
722 |
|
723 NS_ADDREF(aNextNode); |
|
724 |
|
725 aDirection = kDirectionOut; |
|
726 } else { |
|
727 nsCOMPtr<nsIDOMNode> parent; |
|
728 |
|
729 aNode->GetParentNode(getter_AddRefs(parent)); |
|
730 NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE); |
|
731 |
|
732 int32_t count = aIndexArray.Length(); |
|
733 |
|
734 if (count) { |
|
735 int32_t indx = aIndexArray.ElementAt(count - 1); |
|
736 |
|
737 ChildAt(parent, indx + 1, aNextNode); |
|
738 |
|
739 if (aNextNode) |
|
740 aIndexArray.ElementAt(count - 1) = indx + 1; |
|
741 else |
|
742 aIndexArray.RemoveElementAt(count - 1); |
|
743 } else { |
|
744 int32_t indx = IndexOf(parent, aNode); |
|
745 |
|
746 if (indx >= 0) { |
|
747 ChildAt(parent, indx + 1, aNextNode); |
|
748 |
|
749 if (aNextNode) |
|
750 aIndexArray.AppendElement(indx + 1); |
|
751 } |
|
752 } |
|
753 |
|
754 if (aNextNode) { |
|
755 aDirection = kDirectionIn; |
|
756 } else { |
|
757 aDirection = kDirectionOut; |
|
758 |
|
759 aNextNode = parent; |
|
760 |
|
761 NS_ADDREF(aNextNode); |
|
762 } |
|
763 } |
|
764 |
|
765 return NS_OK; |
|
766 } |
|
767 #endif |
|
768 |
|
769 static bool IsTextNode(nsINode *aNode) |
|
770 { |
|
771 return aNode && aNode->IsNodeOfType(nsINode::eTEXT); |
|
772 } |
|
773 |
|
774 nsresult |
|
775 nsDocumentEncoder::SerializeRangeNodes(nsRange* aRange, |
|
776 nsINode* aNode, |
|
777 nsAString& aString, |
|
778 int32_t aDepth) |
|
779 { |
|
780 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); |
|
781 NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); |
|
782 |
|
783 if (!IsVisibleNode(aNode)) |
|
784 return NS_OK; |
|
785 |
|
786 nsresult rv = NS_OK; |
|
787 |
|
788 // get start and end nodes for this recursion level |
|
789 nsCOMPtr<nsIContent> startNode, endNode; |
|
790 { |
|
791 int32_t start = mStartRootIndex - aDepth; |
|
792 if (start >= 0 && (uint32_t)start <= mStartNodes.Length()) |
|
793 startNode = mStartNodes[start]; |
|
794 |
|
795 int32_t end = mEndRootIndex - aDepth; |
|
796 if (end >= 0 && (uint32_t)end <= mEndNodes.Length()) |
|
797 endNode = mEndNodes[end]; |
|
798 } |
|
799 |
|
800 if (startNode != content && endNode != content) |
|
801 { |
|
802 // node is completely contained in range. Serialize the whole subtree |
|
803 // rooted by this node. |
|
804 rv = SerializeToStringRecursive(aNode, aString, false); |
|
805 NS_ENSURE_SUCCESS(rv, rv); |
|
806 } |
|
807 else |
|
808 { |
|
809 // due to implementation it is impossible for text node to be both start and end of |
|
810 // range. We would have handled that case without getting here. |
|
811 //XXXsmaug What does this all mean? |
|
812 if (IsTextNode(aNode)) |
|
813 { |
|
814 if (startNode == content) |
|
815 { |
|
816 int32_t startOffset = aRange->StartOffset(); |
|
817 rv = SerializeNodeStart(aNode, startOffset, -1, aString); |
|
818 NS_ENSURE_SUCCESS(rv, rv); |
|
819 } |
|
820 else |
|
821 { |
|
822 int32_t endOffset = aRange->EndOffset(); |
|
823 rv = SerializeNodeStart(aNode, 0, endOffset, aString); |
|
824 NS_ENSURE_SUCCESS(rv, rv); |
|
825 } |
|
826 } |
|
827 else |
|
828 { |
|
829 if (aNode != mCommonParent) |
|
830 { |
|
831 if (IncludeInContext(aNode)) |
|
832 { |
|
833 // halt the incrementing of mStartDepth/mEndDepth. This is |
|
834 // so paste client will include this node in paste. |
|
835 mHaltRangeHint = true; |
|
836 } |
|
837 if ((startNode == content) && !mHaltRangeHint) mStartDepth++; |
|
838 if ((endNode == content) && !mHaltRangeHint) mEndDepth++; |
|
839 |
|
840 // serialize the start of this node |
|
841 rv = SerializeNodeStart(aNode, 0, -1, aString); |
|
842 NS_ENSURE_SUCCESS(rv, rv); |
|
843 } |
|
844 |
|
845 // do some calculations that will tell us which children of this |
|
846 // node are in the range. |
|
847 nsIContent* childAsNode = nullptr; |
|
848 int32_t startOffset = 0, endOffset = -1; |
|
849 if (startNode == content && mStartRootIndex >= aDepth) |
|
850 startOffset = mStartOffsets[mStartRootIndex - aDepth]; |
|
851 if (endNode == content && mEndRootIndex >= aDepth) |
|
852 endOffset = mEndOffsets[mEndRootIndex - aDepth]; |
|
853 // generated content will cause offset values of -1 to be returned. |
|
854 int32_t j; |
|
855 uint32_t childCount = content->GetChildCount(); |
|
856 |
|
857 if (startOffset == -1) startOffset = 0; |
|
858 if (endOffset == -1) endOffset = childCount; |
|
859 else |
|
860 { |
|
861 // if we are at the "tip" of the selection, endOffset is fine. |
|
862 // otherwise, we need to add one. This is because of the semantics |
|
863 // of the offset list created by GetAncestorsAndOffsets(). The |
|
864 // intermediate points on the list use the endOffset of the |
|
865 // location of the ancestor, rather than just past it. So we need |
|
866 // to add one here in order to include it in the children we serialize. |
|
867 if (aNode != aRange->GetEndParent()) |
|
868 { |
|
869 endOffset++; |
|
870 } |
|
871 } |
|
872 // serialize the children of this node that are in the range |
|
873 for (j=startOffset; j<endOffset; j++) |
|
874 { |
|
875 childAsNode = content->GetChildAt(j); |
|
876 |
|
877 if ((j==startOffset) || (j==endOffset-1)) |
|
878 rv = SerializeRangeNodes(aRange, childAsNode, aString, aDepth+1); |
|
879 else |
|
880 rv = SerializeToStringRecursive(childAsNode, aString, false); |
|
881 |
|
882 NS_ENSURE_SUCCESS(rv, rv); |
|
883 } |
|
884 |
|
885 // serialize the end of this node |
|
886 if (aNode != mCommonParent) |
|
887 { |
|
888 rv = SerializeNodeEnd(aNode, aString); |
|
889 NS_ENSURE_SUCCESS(rv, rv); |
|
890 } |
|
891 } |
|
892 } |
|
893 return NS_OK; |
|
894 } |
|
895 |
|
896 nsresult |
|
897 nsDocumentEncoder::SerializeRangeContextStart(const nsTArray<nsINode*>& aAncestorArray, |
|
898 nsAString& aString) |
|
899 { |
|
900 if (mDisableContextSerialize) { |
|
901 return NS_OK; |
|
902 } |
|
903 int32_t i = aAncestorArray.Length(), j; |
|
904 nsresult rv = NS_OK; |
|
905 |
|
906 // currently only for table-related elements; see Bug 137450 |
|
907 j = GetImmediateContextCount(aAncestorArray); |
|
908 |
|
909 while (i > 0) { |
|
910 nsINode *node = aAncestorArray.ElementAt(--i); |
|
911 |
|
912 if (!node) |
|
913 break; |
|
914 |
|
915 // Either a general inclusion or as immediate context |
|
916 if (IncludeInContext(node) || i < j) { |
|
917 rv = SerializeNodeStart(node, 0, -1, aString); |
|
918 |
|
919 if (NS_FAILED(rv)) |
|
920 break; |
|
921 } |
|
922 } |
|
923 |
|
924 return rv; |
|
925 } |
|
926 |
|
927 nsresult |
|
928 nsDocumentEncoder::SerializeRangeContextEnd(const nsTArray<nsINode*>& aAncestorArray, |
|
929 nsAString& aString) |
|
930 { |
|
931 if (mDisableContextSerialize) { |
|
932 return NS_OK; |
|
933 } |
|
934 int32_t i = 0, j; |
|
935 int32_t count = aAncestorArray.Length(); |
|
936 nsresult rv = NS_OK; |
|
937 |
|
938 // currently only for table-related elements |
|
939 j = GetImmediateContextCount(aAncestorArray); |
|
940 |
|
941 while (i < count) { |
|
942 nsINode *node = aAncestorArray.ElementAt(i++); |
|
943 |
|
944 if (!node) |
|
945 break; |
|
946 |
|
947 // Either a general inclusion or as immediate context |
|
948 if (IncludeInContext(node) || i - 1 < j) { |
|
949 rv = SerializeNodeEnd(node, aString); |
|
950 |
|
951 if (NS_FAILED(rv)) |
|
952 break; |
|
953 } |
|
954 } |
|
955 |
|
956 return rv; |
|
957 } |
|
958 |
|
959 nsresult |
|
960 nsDocumentEncoder::SerializeRangeToString(nsRange *aRange, |
|
961 nsAString& aOutputString) |
|
962 { |
|
963 if (!aRange || aRange->Collapsed()) |
|
964 return NS_OK; |
|
965 |
|
966 mCommonParent = aRange->GetCommonAncestor(); |
|
967 |
|
968 if (!mCommonParent) |
|
969 return NS_OK; |
|
970 |
|
971 nsINode* startParent = aRange->GetStartParent(); |
|
972 NS_ENSURE_TRUE(startParent, NS_ERROR_FAILURE); |
|
973 int32_t startOffset = aRange->StartOffset(); |
|
974 |
|
975 nsINode* endParent = aRange->GetEndParent(); |
|
976 NS_ENSURE_TRUE(endParent, NS_ERROR_FAILURE); |
|
977 int32_t endOffset = aRange->EndOffset(); |
|
978 |
|
979 mCommonAncestors.Clear(); |
|
980 mStartNodes.Clear(); |
|
981 mStartOffsets.Clear(); |
|
982 mEndNodes.Clear(); |
|
983 mEndOffsets.Clear(); |
|
984 |
|
985 nsContentUtils::GetAncestors(mCommonParent, mCommonAncestors); |
|
986 nsCOMPtr<nsIDOMNode> sp = do_QueryInterface(startParent); |
|
987 nsContentUtils::GetAncestorsAndOffsets(sp, startOffset, |
|
988 &mStartNodes, &mStartOffsets); |
|
989 nsCOMPtr<nsIDOMNode> ep = do_QueryInterface(endParent); |
|
990 nsContentUtils::GetAncestorsAndOffsets(ep, endOffset, |
|
991 &mEndNodes, &mEndOffsets); |
|
992 |
|
993 nsCOMPtr<nsIContent> commonContent = do_QueryInterface(mCommonParent); |
|
994 mStartRootIndex = mStartNodes.IndexOf(commonContent); |
|
995 mEndRootIndex = mEndNodes.IndexOf(commonContent); |
|
996 |
|
997 nsresult rv = NS_OK; |
|
998 |
|
999 rv = SerializeRangeContextStart(mCommonAncestors, aOutputString); |
|
1000 NS_ENSURE_SUCCESS(rv, rv); |
|
1001 |
|
1002 if ((startParent == endParent) && IsTextNode(startParent)) |
|
1003 { |
|
1004 if (mFlags & SkipInvisibleContent) { |
|
1005 // Check that the parent is visible if we don't a frame. |
|
1006 // IsVisibleNode() will do it when there's a frame. |
|
1007 nsCOMPtr<nsIContent> content = do_QueryInterface(startParent); |
|
1008 if (content && !content->GetPrimaryFrame()) { |
|
1009 nsIContent* parent = content->GetParent(); |
|
1010 if (!parent || !IsVisibleNode(parent)) |
|
1011 return NS_OK; |
|
1012 } |
|
1013 } |
|
1014 rv = SerializeNodeStart(startParent, startOffset, endOffset, aOutputString); |
|
1015 NS_ENSURE_SUCCESS(rv, rv); |
|
1016 } |
|
1017 else |
|
1018 { |
|
1019 rv = SerializeRangeNodes(aRange, mCommonParent, aOutputString, 0); |
|
1020 NS_ENSURE_SUCCESS(rv, rv); |
|
1021 } |
|
1022 rv = SerializeRangeContextEnd(mCommonAncestors, aOutputString); |
|
1023 NS_ENSURE_SUCCESS(rv, rv); |
|
1024 |
|
1025 return rv; |
|
1026 } |
|
1027 |
|
1028 NS_IMETHODIMP |
|
1029 nsDocumentEncoder::EncodeToString(nsAString& aOutputString) |
|
1030 { |
|
1031 return EncodeToStringWithMaxLength(0, aOutputString); |
|
1032 } |
|
1033 |
|
1034 NS_IMETHODIMP |
|
1035 nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength, |
|
1036 nsAString& aOutputString) |
|
1037 { |
|
1038 if (!mDocument) |
|
1039 return NS_ERROR_NOT_INITIALIZED; |
|
1040 |
|
1041 aOutputString.Truncate(); |
|
1042 |
|
1043 nsString output; |
|
1044 static const size_t bufferSize = 2048; |
|
1045 if (!mCachedBuffer) { |
|
1046 mCachedBuffer = nsStringBuffer::Alloc(bufferSize).take(); |
|
1047 } |
|
1048 NS_ASSERTION(!mCachedBuffer->IsReadonly(), |
|
1049 "DocumentEncoder shouldn't keep reference to non-readonly buffer!"); |
|
1050 static_cast<char16_t*>(mCachedBuffer->Data())[0] = char16_t(0); |
|
1051 mCachedBuffer->ToString(0, output, true); |
|
1052 // output owns the buffer now! |
|
1053 mCachedBuffer = nullptr; |
|
1054 |
|
1055 |
|
1056 if (!mSerializer) { |
|
1057 nsAutoCString progId(NS_CONTENTSERIALIZER_CONTRACTID_PREFIX); |
|
1058 AppendUTF16toUTF8(mMimeType, progId); |
|
1059 |
|
1060 mSerializer = do_CreateInstance(progId.get()); |
|
1061 NS_ENSURE_TRUE(mSerializer, NS_ERROR_NOT_IMPLEMENTED); |
|
1062 } |
|
1063 |
|
1064 nsresult rv = NS_OK; |
|
1065 |
|
1066 nsCOMPtr<nsIAtom> charsetAtom; |
|
1067 if (!mCharset.IsEmpty()) { |
|
1068 if (!mCharsetConverterManager) { |
|
1069 mCharsetConverterManager = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); |
|
1070 NS_ENSURE_SUCCESS(rv, rv); |
|
1071 } |
|
1072 } |
|
1073 |
|
1074 bool rewriteEncodingDeclaration = !(mSelection || mRange || mNode) && !(mFlags & OutputDontRewriteEncodingDeclaration); |
|
1075 mSerializer->Init(mFlags, mWrapColumn, mCharset.get(), mIsCopying, rewriteEncodingDeclaration); |
|
1076 |
|
1077 if (mSelection) { |
|
1078 nsCOMPtr<nsIDOMRange> range; |
|
1079 int32_t i, count = 0; |
|
1080 |
|
1081 rv = mSelection->GetRangeCount(&count); |
|
1082 NS_ENSURE_SUCCESS(rv, rv); |
|
1083 |
|
1084 nsCOMPtr<nsIDOMNode> node, prevNode; |
|
1085 for (i = 0; i < count; i++) { |
|
1086 mSelection->GetRangeAt(i, getter_AddRefs(range)); |
|
1087 |
|
1088 // Bug 236546: newlines not added when copying table cells into clipboard |
|
1089 // Each selected cell shows up as a range containing a row with a single cell |
|
1090 // get the row, compare it to previous row and emit </tr><tr> as needed |
|
1091 // Bug 137450: Problem copying/pasting a table from a web page to Excel. |
|
1092 // Each separate block of <tr></tr> produced above will be wrapped by the |
|
1093 // immediate context. This assumes that you can't select cells that are |
|
1094 // multiple selections from two tables simultaneously. |
|
1095 range->GetStartContainer(getter_AddRefs(node)); |
|
1096 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); |
|
1097 if (node != prevNode) { |
|
1098 nsCOMPtr<nsINode> p; |
|
1099 if (prevNode) { |
|
1100 p = do_QueryInterface(prevNode); |
|
1101 rv = SerializeNodeEnd(p, output); |
|
1102 NS_ENSURE_SUCCESS(rv, rv); |
|
1103 } |
|
1104 nsCOMPtr<nsIContent> content = do_QueryInterface(node); |
|
1105 if (content && content->IsHTML(nsGkAtoms::tr)) { |
|
1106 nsINode* n = content; |
|
1107 if (!prevNode) { |
|
1108 // Went from a non-<tr> to a <tr> |
|
1109 mCommonAncestors.Clear(); |
|
1110 nsContentUtils::GetAncestors(n->GetParentNode(), mCommonAncestors); |
|
1111 rv = SerializeRangeContextStart(mCommonAncestors, output); |
|
1112 NS_ENSURE_SUCCESS(rv, rv); |
|
1113 // Don't let SerializeRangeToString serialize the context again |
|
1114 mDisableContextSerialize = true; |
|
1115 } |
|
1116 |
|
1117 rv = SerializeNodeStart(n, 0, -1, output); |
|
1118 NS_ENSURE_SUCCESS(rv, rv); |
|
1119 prevNode = node; |
|
1120 } else if (prevNode) { |
|
1121 // Went from a <tr> to a non-<tr> |
|
1122 mCommonAncestors.Clear(); |
|
1123 nsContentUtils::GetAncestors(p->GetParentNode(), mCommonAncestors); |
|
1124 mDisableContextSerialize = false; |
|
1125 rv = SerializeRangeContextEnd(mCommonAncestors, output); |
|
1126 NS_ENSURE_SUCCESS(rv, rv); |
|
1127 prevNode = nullptr; |
|
1128 } |
|
1129 } |
|
1130 |
|
1131 nsRange* r = static_cast<nsRange*>(range.get()); |
|
1132 rv = SerializeRangeToString(r, output); |
|
1133 NS_ENSURE_SUCCESS(rv, rv); |
|
1134 } |
|
1135 |
|
1136 if (prevNode) { |
|
1137 nsCOMPtr<nsINode> p = do_QueryInterface(prevNode); |
|
1138 rv = SerializeNodeEnd(p, output); |
|
1139 NS_ENSURE_SUCCESS(rv, rv); |
|
1140 mCommonAncestors.Clear(); |
|
1141 nsContentUtils::GetAncestors(p->GetParentNode(), mCommonAncestors); |
|
1142 mDisableContextSerialize = false; |
|
1143 rv = SerializeRangeContextEnd(mCommonAncestors, output); |
|
1144 NS_ENSURE_SUCCESS(rv, rv); |
|
1145 } |
|
1146 |
|
1147 // Just to be safe |
|
1148 mDisableContextSerialize = false; |
|
1149 |
|
1150 mSelection = nullptr; |
|
1151 } else if (mRange) { |
|
1152 rv = SerializeRangeToString(mRange, output); |
|
1153 |
|
1154 mRange = nullptr; |
|
1155 } else if (mNode) { |
|
1156 if (!mNodeFixup && !(mFlags & SkipInvisibleContent) && !mStream && |
|
1157 mNodeIsContainer) { |
|
1158 rv = SerializeToStringIterative(mNode, output); |
|
1159 } else { |
|
1160 rv = SerializeToStringRecursive(mNode, output, mNodeIsContainer); |
|
1161 } |
|
1162 mNode = nullptr; |
|
1163 } else { |
|
1164 rv = mSerializer->AppendDocumentStart(mDocument, output); |
|
1165 |
|
1166 if (NS_SUCCEEDED(rv)) { |
|
1167 rv = SerializeToStringRecursive(mDocument, output, false, aMaxLength); |
|
1168 } |
|
1169 } |
|
1170 |
|
1171 NS_ENSURE_SUCCESS(rv, rv); |
|
1172 rv = mSerializer->Flush(output); |
|
1173 |
|
1174 mCachedBuffer = nsStringBuffer::FromString(output); |
|
1175 // We have to be careful how we set aOutputString, because we don't |
|
1176 // want it to end up sharing mCachedBuffer if we plan to reuse it. |
|
1177 bool setOutput = false; |
|
1178 // Try to cache the buffer. |
|
1179 if (mCachedBuffer) { |
|
1180 if (mCachedBuffer->StorageSize() == bufferSize && |
|
1181 !mCachedBuffer->IsReadonly()) { |
|
1182 mCachedBuffer->AddRef(); |
|
1183 } else { |
|
1184 if (NS_SUCCEEDED(rv)) { |
|
1185 mCachedBuffer->ToString(output.Length(), aOutputString); |
|
1186 setOutput = true; |
|
1187 } |
|
1188 mCachedBuffer = nullptr; |
|
1189 } |
|
1190 } |
|
1191 |
|
1192 if (!setOutput && NS_SUCCEEDED(rv)) { |
|
1193 aOutputString.Append(output.get(), output.Length()); |
|
1194 } |
|
1195 |
|
1196 return rv; |
|
1197 } |
|
1198 |
|
1199 NS_IMETHODIMP |
|
1200 nsDocumentEncoder::EncodeToStream(nsIOutputStream* aStream) |
|
1201 { |
|
1202 nsresult rv = NS_OK; |
|
1203 |
|
1204 if (!mDocument) |
|
1205 return NS_ERROR_NOT_INITIALIZED; |
|
1206 |
|
1207 if (!mCharsetConverterManager) { |
|
1208 mCharsetConverterManager = do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); |
|
1209 NS_ENSURE_SUCCESS(rv, rv); |
|
1210 } |
|
1211 |
|
1212 rv = mCharsetConverterManager->GetUnicodeEncoder(mCharset.get(), |
|
1213 getter_AddRefs(mUnicodeEncoder)); |
|
1214 NS_ENSURE_SUCCESS(rv, rv); |
|
1215 |
|
1216 if (mMimeType.LowerCaseEqualsLiteral("text/plain")) { |
|
1217 rv = mUnicodeEncoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Replace, nullptr, '?'); |
|
1218 NS_ENSURE_SUCCESS(rv, rv); |
|
1219 } |
|
1220 |
|
1221 mStream = aStream; |
|
1222 |
|
1223 nsAutoString buf; |
|
1224 |
|
1225 rv = EncodeToString(buf); |
|
1226 |
|
1227 // Force a flush of the last chunk of data. |
|
1228 FlushText(buf, true); |
|
1229 |
|
1230 mStream = nullptr; |
|
1231 mUnicodeEncoder = nullptr; |
|
1232 |
|
1233 return rv; |
|
1234 } |
|
1235 |
|
1236 NS_IMETHODIMP |
|
1237 nsDocumentEncoder::EncodeToStringWithContext(nsAString& aContextString, |
|
1238 nsAString& aInfoString, |
|
1239 nsAString& aEncodedString) |
|
1240 { |
|
1241 return NS_ERROR_NOT_IMPLEMENTED; |
|
1242 } |
|
1243 |
|
1244 NS_IMETHODIMP |
|
1245 nsDocumentEncoder::SetNodeFixup(nsIDocumentEncoderNodeFixup *aFixup) |
|
1246 { |
|
1247 mNodeFixup = aFixup; |
|
1248 return NS_OK; |
|
1249 } |
|
1250 |
|
1251 |
|
1252 nsresult NS_NewTextEncoder(nsIDocumentEncoder** aResult); // make mac compiler happy |
|
1253 |
|
1254 nsresult |
|
1255 NS_NewTextEncoder(nsIDocumentEncoder** aResult) |
|
1256 { |
|
1257 *aResult = new nsDocumentEncoder; |
|
1258 if (!*aResult) |
|
1259 return NS_ERROR_OUT_OF_MEMORY; |
|
1260 NS_ADDREF(*aResult); |
|
1261 return NS_OK; |
|
1262 } |
|
1263 |
|
1264 class nsHTMLCopyEncoder : public nsDocumentEncoder |
|
1265 { |
|
1266 public: |
|
1267 |
|
1268 nsHTMLCopyEncoder(); |
|
1269 virtual ~nsHTMLCopyEncoder(); |
|
1270 |
|
1271 NS_IMETHOD Init(nsIDOMDocument* aDocument, const nsAString& aMimeType, uint32_t aFlags); |
|
1272 |
|
1273 // overridden methods from nsDocumentEncoder |
|
1274 NS_IMETHOD SetSelection(nsISelection* aSelection); |
|
1275 NS_IMETHOD EncodeToStringWithContext(nsAString& aContextString, |
|
1276 nsAString& aInfoString, |
|
1277 nsAString& aEncodedString); |
|
1278 NS_IMETHOD EncodeToString(nsAString& aOutputString); |
|
1279 |
|
1280 protected: |
|
1281 |
|
1282 enum Endpoint |
|
1283 { |
|
1284 kStart, |
|
1285 kEnd |
|
1286 }; |
|
1287 |
|
1288 nsresult PromoteRange(nsIDOMRange *inRange); |
|
1289 nsresult PromoteAncestorChain(nsCOMPtr<nsIDOMNode> *ioNode, |
|
1290 int32_t *ioStartOffset, |
|
1291 int32_t *ioEndOffset); |
|
1292 nsresult GetPromotedPoint(Endpoint aWhere, nsIDOMNode *aNode, int32_t aOffset, |
|
1293 nsCOMPtr<nsIDOMNode> *outNode, int32_t *outOffset, nsIDOMNode *aCommon); |
|
1294 nsCOMPtr<nsIDOMNode> GetChildAt(nsIDOMNode *aParent, int32_t aOffset); |
|
1295 bool IsMozBR(nsIDOMNode* aNode); |
|
1296 nsresult GetNodeLocation(nsIDOMNode *inChild, nsCOMPtr<nsIDOMNode> *outParent, int32_t *outOffset); |
|
1297 bool IsRoot(nsIDOMNode* aNode); |
|
1298 bool IsFirstNode(nsIDOMNode *aNode); |
|
1299 bool IsLastNode(nsIDOMNode *aNode); |
|
1300 bool IsEmptyTextContent(nsIDOMNode* aNode); |
|
1301 virtual bool IncludeInContext(nsINode *aNode); |
|
1302 virtual int32_t |
|
1303 GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray); |
|
1304 |
|
1305 bool mIsTextWidget; |
|
1306 }; |
|
1307 |
|
1308 nsHTMLCopyEncoder::nsHTMLCopyEncoder() |
|
1309 { |
|
1310 mIsTextWidget = false; |
|
1311 } |
|
1312 |
|
1313 nsHTMLCopyEncoder::~nsHTMLCopyEncoder() |
|
1314 { |
|
1315 } |
|
1316 |
|
1317 NS_IMETHODIMP |
|
1318 nsHTMLCopyEncoder::Init(nsIDOMDocument* aDocument, |
|
1319 const nsAString& aMimeType, |
|
1320 uint32_t aFlags) |
|
1321 { |
|
1322 if (!aDocument) |
|
1323 return NS_ERROR_INVALID_ARG; |
|
1324 |
|
1325 mIsTextWidget = false; |
|
1326 Initialize(); |
|
1327 |
|
1328 mIsCopying = true; |
|
1329 mDocument = do_QueryInterface(aDocument); |
|
1330 NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE); |
|
1331 |
|
1332 // Hack, hack! Traditionally, the caller passes text/unicode, which is |
|
1333 // treated as "guess text/html or text/plain" in this context. (It has a |
|
1334 // different meaning in other contexts. Sigh.) From now on, "text/plain" |
|
1335 // means forcing text/plain instead of guessing. |
|
1336 if (aMimeType.EqualsLiteral("text/plain")) { |
|
1337 mMimeType.AssignLiteral("text/plain"); |
|
1338 } else { |
|
1339 mMimeType.AssignLiteral("text/html"); |
|
1340 } |
|
1341 |
|
1342 // Make all links absolute when copying |
|
1343 // (see related bugs #57296, #41924, #58646, #32768) |
|
1344 mFlags = aFlags | OutputAbsoluteLinks; |
|
1345 |
|
1346 if (!mDocument->IsScriptEnabled()) |
|
1347 mFlags |= OutputNoScriptContent; |
|
1348 |
|
1349 return NS_OK; |
|
1350 } |
|
1351 |
|
1352 NS_IMETHODIMP |
|
1353 nsHTMLCopyEncoder::SetSelection(nsISelection* aSelection) |
|
1354 { |
|
1355 // check for text widgets: we need to recognize these so that |
|
1356 // we don't tweak the selection to be outside of the magic |
|
1357 // div that ender-lite text widgets are embedded in. |
|
1358 |
|
1359 if (!aSelection) |
|
1360 return NS_ERROR_NULL_POINTER; |
|
1361 |
|
1362 nsCOMPtr<nsIDOMRange> range; |
|
1363 nsCOMPtr<nsIDOMNode> commonParent; |
|
1364 Selection* selection = static_cast<Selection*>(aSelection); |
|
1365 uint32_t rangeCount = selection->GetRangeCount(); |
|
1366 |
|
1367 // if selection is uninitialized return |
|
1368 if (!rangeCount) |
|
1369 return NS_ERROR_FAILURE; |
|
1370 |
|
1371 // we'll just use the common parent of the first range. Implicit assumption |
|
1372 // here that multi-range selections are table cell selections, in which case |
|
1373 // the common parent is somewhere in the table and we don't really care where. |
|
1374 nsresult rv = aSelection->GetRangeAt(0, getter_AddRefs(range)); |
|
1375 NS_ENSURE_SUCCESS(rv, rv); |
|
1376 if (!range) |
|
1377 return NS_ERROR_NULL_POINTER; |
|
1378 range->GetCommonAncestorContainer(getter_AddRefs(commonParent)); |
|
1379 |
|
1380 for (nsCOMPtr<nsIContent> selContent(do_QueryInterface(commonParent)); |
|
1381 selContent; |
|
1382 selContent = selContent->GetParent()) |
|
1383 { |
|
1384 // checking for selection inside a plaintext form widget |
|
1385 nsIAtom *atom = selContent->Tag(); |
|
1386 if (atom == nsGkAtoms::input || |
|
1387 atom == nsGkAtoms::textarea) |
|
1388 { |
|
1389 mIsTextWidget = true; |
|
1390 break; |
|
1391 } |
|
1392 else if (atom == nsGkAtoms::body) |
|
1393 { |
|
1394 // check for moz prewrap style on body. If it's there we are |
|
1395 // in a plaintext editor. This is pretty cheezy but I haven't |
|
1396 // found a good way to tell if we are in a plaintext editor. |
|
1397 nsCOMPtr<nsIDOMElement> bodyElem = do_QueryInterface(selContent); |
|
1398 nsAutoString wsVal; |
|
1399 rv = bodyElem->GetAttribute(NS_LITERAL_STRING("style"), wsVal); |
|
1400 if (NS_SUCCEEDED(rv) && (kNotFound != wsVal.Find(NS_LITERAL_STRING("pre-wrap")))) |
|
1401 { |
|
1402 mIsTextWidget = true; |
|
1403 break; |
|
1404 } |
|
1405 } |
|
1406 } |
|
1407 |
|
1408 // normalize selection if we are not in a widget |
|
1409 if (mIsTextWidget) |
|
1410 { |
|
1411 mSelection = aSelection; |
|
1412 mMimeType.AssignLiteral("text/plain"); |
|
1413 return NS_OK; |
|
1414 } |
|
1415 |
|
1416 // also consider ourselves in a text widget if we can't find an html document |
|
1417 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mDocument); |
|
1418 if (!(htmlDoc && mDocument->IsHTML())) { |
|
1419 mIsTextWidget = true; |
|
1420 mSelection = aSelection; |
|
1421 // mMimeType is set to text/plain when encoding starts. |
|
1422 return NS_OK; |
|
1423 } |
|
1424 |
|
1425 // there's no Clone() for selection! fix... |
|
1426 //nsresult rv = aSelection->Clone(getter_AddRefs(mSelection); |
|
1427 //NS_ENSURE_SUCCESS(rv, rv); |
|
1428 NS_NewDomSelection(getter_AddRefs(mSelection)); |
|
1429 NS_ENSURE_TRUE(mSelection, NS_ERROR_FAILURE); |
|
1430 |
|
1431 // loop thru the ranges in the selection |
|
1432 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { |
|
1433 range = selection->GetRangeAt(rangeIdx); |
|
1434 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); |
|
1435 nsCOMPtr<nsIDOMRange> myRange; |
|
1436 range->CloneRange(getter_AddRefs(myRange)); |
|
1437 NS_ENSURE_TRUE(myRange, NS_ERROR_FAILURE); |
|
1438 |
|
1439 // adjust range to include any ancestors who's children are entirely selected |
|
1440 rv = PromoteRange(myRange); |
|
1441 NS_ENSURE_SUCCESS(rv, rv); |
|
1442 |
|
1443 rv = mSelection->AddRange(myRange); |
|
1444 NS_ENSURE_SUCCESS(rv, rv); |
|
1445 } |
|
1446 |
|
1447 return NS_OK; |
|
1448 } |
|
1449 |
|
1450 NS_IMETHODIMP |
|
1451 nsHTMLCopyEncoder::EncodeToString(nsAString& aOutputString) |
|
1452 { |
|
1453 if (mIsTextWidget) { |
|
1454 mMimeType.AssignLiteral("text/plain"); |
|
1455 } |
|
1456 return nsDocumentEncoder::EncodeToString(aOutputString); |
|
1457 } |
|
1458 |
|
1459 NS_IMETHODIMP |
|
1460 nsHTMLCopyEncoder::EncodeToStringWithContext(nsAString& aContextString, |
|
1461 nsAString& aInfoString, |
|
1462 nsAString& aEncodedString) |
|
1463 { |
|
1464 nsresult rv = EncodeToString(aEncodedString); |
|
1465 NS_ENSURE_SUCCESS(rv, rv); |
|
1466 |
|
1467 // do not encode any context info or range hints if we are in a text widget. |
|
1468 if (mIsTextWidget) return NS_OK; |
|
1469 |
|
1470 // now encode common ancestors into aContextString. Note that the common ancestors |
|
1471 // will be for the last range in the selection in the case of multirange selections. |
|
1472 // encoding ancestors every range in a multirange selection in a way that could be |
|
1473 // understood by the paste code would be a lot more work to do. As a practical matter, |
|
1474 // selections are single range, and the ones that aren't are table cell selections |
|
1475 // where all the cells are in the same table. |
|
1476 |
|
1477 // leaf of ancestors might be text node. If so discard it. |
|
1478 int32_t count = mCommonAncestors.Length(); |
|
1479 int32_t i; |
|
1480 nsCOMPtr<nsINode> node; |
|
1481 if (count > 0) |
|
1482 node = mCommonAncestors.ElementAt(0); |
|
1483 |
|
1484 if (node && IsTextNode(node)) |
|
1485 { |
|
1486 mCommonAncestors.RemoveElementAt(0); |
|
1487 // don't forget to adjust range depth info |
|
1488 if (mStartDepth) mStartDepth--; |
|
1489 if (mEndDepth) mEndDepth--; |
|
1490 // and the count |
|
1491 count--; |
|
1492 } |
|
1493 |
|
1494 i = count; |
|
1495 while (i > 0) |
|
1496 { |
|
1497 node = mCommonAncestors.ElementAt(--i); |
|
1498 SerializeNodeStart(node, 0, -1, aContextString); |
|
1499 } |
|
1500 //i = 0; guaranteed by above |
|
1501 while (i < count) |
|
1502 { |
|
1503 node = mCommonAncestors.ElementAt(i++); |
|
1504 SerializeNodeEnd(node, aContextString); |
|
1505 } |
|
1506 |
|
1507 // encode range info : the start and end depth of the selection, where the depth is |
|
1508 // distance down in the parent hierarchy. Later we will need to add leading/trailing |
|
1509 // whitespace info to this. |
|
1510 nsAutoString infoString; |
|
1511 infoString.AppendInt(mStartDepth); |
|
1512 infoString.Append(char16_t(',')); |
|
1513 infoString.AppendInt(mEndDepth); |
|
1514 aInfoString = infoString; |
|
1515 |
|
1516 return NS_OK; |
|
1517 } |
|
1518 |
|
1519 |
|
1520 bool |
|
1521 nsHTMLCopyEncoder::IncludeInContext(nsINode *aNode) |
|
1522 { |
|
1523 nsCOMPtr<nsIContent> content(do_QueryInterface(aNode)); |
|
1524 |
|
1525 if (!content) |
|
1526 return false; |
|
1527 |
|
1528 nsIAtom *tag = content->Tag(); |
|
1529 |
|
1530 return (tag == nsGkAtoms::b || |
|
1531 tag == nsGkAtoms::i || |
|
1532 tag == nsGkAtoms::u || |
|
1533 tag == nsGkAtoms::a || |
|
1534 tag == nsGkAtoms::tt || |
|
1535 tag == nsGkAtoms::s || |
|
1536 tag == nsGkAtoms::big || |
|
1537 tag == nsGkAtoms::small || |
|
1538 tag == nsGkAtoms::strike || |
|
1539 tag == nsGkAtoms::em || |
|
1540 tag == nsGkAtoms::strong || |
|
1541 tag == nsGkAtoms::dfn || |
|
1542 tag == nsGkAtoms::code || |
|
1543 tag == nsGkAtoms::cite || |
|
1544 tag == nsGkAtoms::var || |
|
1545 tag == nsGkAtoms::abbr || |
|
1546 tag == nsGkAtoms::font || |
|
1547 tag == nsGkAtoms::script || |
|
1548 tag == nsGkAtoms::span || |
|
1549 tag == nsGkAtoms::pre || |
|
1550 tag == nsGkAtoms::h1 || |
|
1551 tag == nsGkAtoms::h2 || |
|
1552 tag == nsGkAtoms::h3 || |
|
1553 tag == nsGkAtoms::h4 || |
|
1554 tag == nsGkAtoms::h5 || |
|
1555 tag == nsGkAtoms::h6); |
|
1556 } |
|
1557 |
|
1558 |
|
1559 nsresult |
|
1560 nsHTMLCopyEncoder::PromoteRange(nsIDOMRange *inRange) |
|
1561 { |
|
1562 if (!inRange) return NS_ERROR_NULL_POINTER; |
|
1563 nsresult rv; |
|
1564 nsCOMPtr<nsIDOMNode> startNode, endNode, common; |
|
1565 int32_t startOffset, endOffset; |
|
1566 |
|
1567 rv = inRange->GetCommonAncestorContainer(getter_AddRefs(common)); |
|
1568 NS_ENSURE_SUCCESS(rv, rv); |
|
1569 rv = inRange->GetStartContainer(getter_AddRefs(startNode)); |
|
1570 NS_ENSURE_SUCCESS(rv, rv); |
|
1571 rv = inRange->GetStartOffset(&startOffset); |
|
1572 NS_ENSURE_SUCCESS(rv, rv); |
|
1573 rv = inRange->GetEndContainer(getter_AddRefs(endNode)); |
|
1574 NS_ENSURE_SUCCESS(rv, rv); |
|
1575 rv = inRange->GetEndOffset(&endOffset); |
|
1576 NS_ENSURE_SUCCESS(rv, rv); |
|
1577 |
|
1578 nsCOMPtr<nsIDOMNode> opStartNode; |
|
1579 nsCOMPtr<nsIDOMNode> opEndNode; |
|
1580 int32_t opStartOffset, opEndOffset; |
|
1581 nsCOMPtr<nsIDOMRange> opRange; |
|
1582 |
|
1583 // examine range endpoints. |
|
1584 rv = GetPromotedPoint( kStart, startNode, startOffset, address_of(opStartNode), &opStartOffset, common); |
|
1585 NS_ENSURE_SUCCESS(rv, rv); |
|
1586 rv = GetPromotedPoint( kEnd, endNode, endOffset, address_of(opEndNode), &opEndOffset, common); |
|
1587 NS_ENSURE_SUCCESS(rv, rv); |
|
1588 |
|
1589 // if both range endpoints are at the common ancestor, check for possible inclusion of ancestors |
|
1590 if ( (opStartNode == common) && (opEndNode == common) ) |
|
1591 { |
|
1592 rv = PromoteAncestorChain(address_of(opStartNode), &opStartOffset, &opEndOffset); |
|
1593 NS_ENSURE_SUCCESS(rv, rv); |
|
1594 opEndNode = opStartNode; |
|
1595 } |
|
1596 |
|
1597 // set the range to the new values |
|
1598 rv = inRange->SetStart(opStartNode, opStartOffset); |
|
1599 NS_ENSURE_SUCCESS(rv, rv); |
|
1600 rv = inRange->SetEnd(opEndNode, opEndOffset); |
|
1601 return rv; |
|
1602 } |
|
1603 |
|
1604 |
|
1605 // PromoteAncestorChain will promote a range represented by [{*ioNode,*ioStartOffset} , {*ioNode,*ioEndOffset}] |
|
1606 // The promotion is different from that found in getPromotedPoint: it will only promote one endpoint if it can |
|
1607 // promote the other. Thus, instead of having a startnode/endNode, there is just the one ioNode. |
|
1608 nsresult |
|
1609 nsHTMLCopyEncoder::PromoteAncestorChain(nsCOMPtr<nsIDOMNode> *ioNode, |
|
1610 int32_t *ioStartOffset, |
|
1611 int32_t *ioEndOffset) |
|
1612 { |
|
1613 if (!ioNode || !ioStartOffset || !ioEndOffset) return NS_ERROR_NULL_POINTER; |
|
1614 |
|
1615 nsresult rv = NS_OK; |
|
1616 bool done = false; |
|
1617 |
|
1618 nsCOMPtr<nsIDOMNode> frontNode, endNode, parent; |
|
1619 int32_t frontOffset, endOffset; |
|
1620 |
|
1621 //save the editable state of the ioNode, so we don't promote an ancestor if it has different editable state |
|
1622 nsCOMPtr<nsINode> node = do_QueryInterface(*ioNode); |
|
1623 bool isEditable = node->IsEditable(); |
|
1624 |
|
1625 // loop for as long as we can promote both endpoints |
|
1626 while (!done) |
|
1627 { |
|
1628 rv = (*ioNode)->GetParentNode(getter_AddRefs(parent)); |
|
1629 if ((NS_FAILED(rv)) || !parent) |
|
1630 done = true; |
|
1631 else |
|
1632 { |
|
1633 // passing parent as last param to GetPromotedPoint() allows it to promote only one level |
|
1634 // up the hierarchy. |
|
1635 rv = GetPromotedPoint( kStart, *ioNode, *ioStartOffset, address_of(frontNode), &frontOffset, parent); |
|
1636 NS_ENSURE_SUCCESS(rv, rv); |
|
1637 // then we make the same attempt with the endpoint |
|
1638 rv = GetPromotedPoint( kEnd, *ioNode, *ioEndOffset, address_of(endNode), &endOffset, parent); |
|
1639 NS_ENSURE_SUCCESS(rv, rv); |
|
1640 |
|
1641 nsCOMPtr<nsINode> frontINode = do_QueryInterface(frontNode); |
|
1642 // if both endpoints were promoted one level and isEditable is the same as the original node, |
|
1643 // keep looping - otherwise we are done. |
|
1644 if ( (frontNode != parent) || (endNode != parent) || (frontINode->IsEditable() != isEditable) ) |
|
1645 done = true; |
|
1646 else |
|
1647 { |
|
1648 *ioNode = frontNode; |
|
1649 *ioStartOffset = frontOffset; |
|
1650 *ioEndOffset = endOffset; |
|
1651 } |
|
1652 } |
|
1653 } |
|
1654 return rv; |
|
1655 } |
|
1656 |
|
1657 nsresult |
|
1658 nsHTMLCopyEncoder::GetPromotedPoint(Endpoint aWhere, nsIDOMNode *aNode, int32_t aOffset, |
|
1659 nsCOMPtr<nsIDOMNode> *outNode, int32_t *outOffset, nsIDOMNode *common) |
|
1660 { |
|
1661 nsresult rv = NS_OK; |
|
1662 nsCOMPtr<nsIDOMNode> node = aNode; |
|
1663 nsCOMPtr<nsIDOMNode> parent = aNode; |
|
1664 int32_t offset = aOffset; |
|
1665 bool bResetPromotion = false; |
|
1666 |
|
1667 // default values |
|
1668 *outNode = node; |
|
1669 *outOffset = offset; |
|
1670 |
|
1671 if (common == node) |
|
1672 return NS_OK; |
|
1673 |
|
1674 if (aWhere == kStart) |
|
1675 { |
|
1676 // some special casing for text nodes |
|
1677 nsCOMPtr<nsINode> t = do_QueryInterface(aNode); |
|
1678 if (IsTextNode(t)) |
|
1679 { |
|
1680 // if not at beginning of text node, we are done |
|
1681 if (offset > 0) |
|
1682 { |
|
1683 // unless everything before us in just whitespace. NOTE: we need a more |
|
1684 // general solution that truly detects all cases of non-significant |
|
1685 // whitesace with no false alarms. |
|
1686 nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode); |
|
1687 nsAutoString text; |
|
1688 nodeAsText->SubstringData(0, offset, text); |
|
1689 text.CompressWhitespace(); |
|
1690 if (!text.IsEmpty()) |
|
1691 return NS_OK; |
|
1692 bResetPromotion = true; |
|
1693 } |
|
1694 // else |
|
1695 rv = GetNodeLocation(aNode, address_of(parent), &offset); |
|
1696 NS_ENSURE_SUCCESS(rv, rv); |
|
1697 } |
|
1698 else |
|
1699 { |
|
1700 node = GetChildAt(parent,offset); |
|
1701 } |
|
1702 if (!node) node = parent; |
|
1703 |
|
1704 // finding the real start for this point. look up the tree for as long as we are the |
|
1705 // first node in the container, and as long as we haven't hit the body node. |
|
1706 if (!IsRoot(node) && (parent != common)) |
|
1707 { |
|
1708 rv = GetNodeLocation(node, address_of(parent), &offset); |
|
1709 NS_ENSURE_SUCCESS(rv, rv); |
|
1710 if (offset == -1) return NS_OK; // we hit generated content; STOP |
|
1711 nsIParserService *parserService = nsContentUtils::GetParserService(); |
|
1712 if (!parserService) |
|
1713 return NS_ERROR_OUT_OF_MEMORY; |
|
1714 while ((IsFirstNode(node)) && (!IsRoot(parent)) && (parent != common)) |
|
1715 { |
|
1716 if (bResetPromotion) |
|
1717 { |
|
1718 nsCOMPtr<nsIContent> content = do_QueryInterface(parent); |
|
1719 if (content) |
|
1720 { |
|
1721 bool isBlock = false; |
|
1722 parserService->IsBlock(parserService->HTMLAtomTagToId(content->Tag()), isBlock); |
|
1723 if (isBlock) |
|
1724 { |
|
1725 bResetPromotion = false; |
|
1726 } |
|
1727 } |
|
1728 } |
|
1729 |
|
1730 node = parent; |
|
1731 rv = GetNodeLocation(node, address_of(parent), &offset); |
|
1732 NS_ENSURE_SUCCESS(rv, rv); |
|
1733 if (offset == -1) // we hit generated content; STOP |
|
1734 { |
|
1735 // back up a bit |
|
1736 parent = node; |
|
1737 offset = 0; |
|
1738 break; |
|
1739 } |
|
1740 } |
|
1741 if (bResetPromotion) |
|
1742 { |
|
1743 *outNode = aNode; |
|
1744 *outOffset = aOffset; |
|
1745 } |
|
1746 else |
|
1747 { |
|
1748 *outNode = parent; |
|
1749 *outOffset = offset; |
|
1750 } |
|
1751 return rv; |
|
1752 } |
|
1753 } |
|
1754 |
|
1755 if (aWhere == kEnd) |
|
1756 { |
|
1757 // some special casing for text nodes |
|
1758 nsCOMPtr<nsINode> n = do_QueryInterface(aNode); |
|
1759 if (IsTextNode(n)) |
|
1760 { |
|
1761 // if not at end of text node, we are done |
|
1762 uint32_t len = n->Length(); |
|
1763 if (offset < (int32_t)len) |
|
1764 { |
|
1765 // unless everything after us in just whitespace. NOTE: we need a more |
|
1766 // general solution that truly detects all cases of non-significant |
|
1767 // whitespace with no false alarms. |
|
1768 nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(aNode); |
|
1769 nsAutoString text; |
|
1770 nodeAsText->SubstringData(offset, len-offset, text); |
|
1771 text.CompressWhitespace(); |
|
1772 if (!text.IsEmpty()) |
|
1773 return NS_OK; |
|
1774 bResetPromotion = true; |
|
1775 } |
|
1776 rv = GetNodeLocation(aNode, address_of(parent), &offset); |
|
1777 NS_ENSURE_SUCCESS(rv, rv); |
|
1778 } |
|
1779 else |
|
1780 { |
|
1781 if (offset) offset--; // we want node _before_ offset |
|
1782 node = GetChildAt(parent,offset); |
|
1783 } |
|
1784 if (!node) node = parent; |
|
1785 |
|
1786 // finding the real end for this point. look up the tree for as long as we are the |
|
1787 // last node in the container, and as long as we haven't hit the body node. |
|
1788 if (!IsRoot(node) && (parent != common)) |
|
1789 { |
|
1790 rv = GetNodeLocation(node, address_of(parent), &offset); |
|
1791 NS_ENSURE_SUCCESS(rv, rv); |
|
1792 if (offset == -1) return NS_OK; // we hit generated content; STOP |
|
1793 nsIParserService *parserService = nsContentUtils::GetParserService(); |
|
1794 if (!parserService) |
|
1795 return NS_ERROR_OUT_OF_MEMORY; |
|
1796 while ((IsLastNode(node)) && (!IsRoot(parent)) && (parent != common)) |
|
1797 { |
|
1798 if (bResetPromotion) |
|
1799 { |
|
1800 nsCOMPtr<nsIContent> content = do_QueryInterface(parent); |
|
1801 if (content) |
|
1802 { |
|
1803 bool isBlock = false; |
|
1804 parserService->IsBlock(parserService->HTMLAtomTagToId(content->Tag()), isBlock); |
|
1805 if (isBlock) |
|
1806 { |
|
1807 bResetPromotion = false; |
|
1808 } |
|
1809 } |
|
1810 } |
|
1811 |
|
1812 node = parent; |
|
1813 rv = GetNodeLocation(node, address_of(parent), &offset); |
|
1814 NS_ENSURE_SUCCESS(rv, rv); |
|
1815 if (offset == -1) // we hit generated content; STOP |
|
1816 { |
|
1817 // back up a bit |
|
1818 parent = node; |
|
1819 offset = 0; |
|
1820 break; |
|
1821 } |
|
1822 } |
|
1823 if (bResetPromotion) |
|
1824 { |
|
1825 *outNode = aNode; |
|
1826 *outOffset = aOffset; |
|
1827 } |
|
1828 else |
|
1829 { |
|
1830 *outNode = parent; |
|
1831 offset++; // add one since this in an endpoint - want to be AFTER node. |
|
1832 *outOffset = offset; |
|
1833 } |
|
1834 return rv; |
|
1835 } |
|
1836 } |
|
1837 |
|
1838 return rv; |
|
1839 } |
|
1840 |
|
1841 nsCOMPtr<nsIDOMNode> |
|
1842 nsHTMLCopyEncoder::GetChildAt(nsIDOMNode *aParent, int32_t aOffset) |
|
1843 { |
|
1844 nsCOMPtr<nsIDOMNode> resultNode; |
|
1845 |
|
1846 if (!aParent) |
|
1847 return resultNode; |
|
1848 |
|
1849 nsCOMPtr<nsIContent> content = do_QueryInterface(aParent); |
|
1850 NS_PRECONDITION(content, "null content in nsHTMLCopyEncoder::GetChildAt"); |
|
1851 |
|
1852 resultNode = do_QueryInterface(content->GetChildAt(aOffset)); |
|
1853 |
|
1854 return resultNode; |
|
1855 } |
|
1856 |
|
1857 bool |
|
1858 nsHTMLCopyEncoder::IsMozBR(nsIDOMNode* aNode) |
|
1859 { |
|
1860 MOZ_ASSERT(aNode); |
|
1861 nsCOMPtr<Element> element = do_QueryInterface(aNode); |
|
1862 return element && |
|
1863 element->IsHTML(nsGkAtoms::br) && |
|
1864 element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, |
|
1865 NS_LITERAL_STRING("_moz"), eIgnoreCase); |
|
1866 } |
|
1867 |
|
1868 nsresult |
|
1869 nsHTMLCopyEncoder::GetNodeLocation(nsIDOMNode *inChild, |
|
1870 nsCOMPtr<nsIDOMNode> *outParent, |
|
1871 int32_t *outOffset) |
|
1872 { |
|
1873 NS_ASSERTION((inChild && outParent && outOffset), "bad args"); |
|
1874 nsresult result = NS_ERROR_NULL_POINTER; |
|
1875 if (inChild && outParent && outOffset) |
|
1876 { |
|
1877 result = inChild->GetParentNode(getter_AddRefs(*outParent)); |
|
1878 if ((NS_SUCCEEDED(result)) && (*outParent)) |
|
1879 { |
|
1880 nsCOMPtr<nsIContent> content = do_QueryInterface(*outParent); |
|
1881 nsCOMPtr<nsIContent> cChild = do_QueryInterface(inChild); |
|
1882 if (!cChild || !content) |
|
1883 return NS_ERROR_NULL_POINTER; |
|
1884 |
|
1885 *outOffset = content->IndexOf(cChild); |
|
1886 } |
|
1887 } |
|
1888 return result; |
|
1889 } |
|
1890 |
|
1891 bool |
|
1892 nsHTMLCopyEncoder::IsRoot(nsIDOMNode* aNode) |
|
1893 { |
|
1894 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); |
|
1895 if (content) |
|
1896 { |
|
1897 if (mIsTextWidget) |
|
1898 return (IsTag(content, nsGkAtoms::div)); |
|
1899 |
|
1900 return (IsTag(content, nsGkAtoms::body) || |
|
1901 IsTag(content, nsGkAtoms::td) || |
|
1902 IsTag(content, nsGkAtoms::th)); |
|
1903 } |
|
1904 return false; |
|
1905 } |
|
1906 |
|
1907 bool |
|
1908 nsHTMLCopyEncoder::IsFirstNode(nsIDOMNode *aNode) |
|
1909 { |
|
1910 nsCOMPtr<nsIDOMNode> parent; |
|
1911 int32_t offset, j=0; |
|
1912 nsresult rv = GetNodeLocation(aNode, address_of(parent), &offset); |
|
1913 if (NS_FAILED(rv)) |
|
1914 { |
|
1915 NS_NOTREACHED("failure in IsFirstNode"); |
|
1916 return false; |
|
1917 } |
|
1918 if (offset == 0) // easy case, we are first dom child |
|
1919 return true; |
|
1920 if (!parent) |
|
1921 return true; |
|
1922 |
|
1923 // need to check if any nodes before us are really visible. |
|
1924 // Mike wrote something for me along these lines in nsSelectionController, |
|
1925 // but I don't think it's ready for use yet - revisit. |
|
1926 // HACK: for now, simply consider all whitespace text nodes to be |
|
1927 // invisible formatting nodes. |
|
1928 nsCOMPtr<nsIDOMNodeList> childList; |
|
1929 nsCOMPtr<nsIDOMNode> child; |
|
1930 |
|
1931 rv = parent->GetChildNodes(getter_AddRefs(childList)); |
|
1932 if (NS_FAILED(rv) || !childList) |
|
1933 { |
|
1934 NS_NOTREACHED("failure in IsFirstNode"); |
|
1935 return true; |
|
1936 } |
|
1937 while (j < offset) |
|
1938 { |
|
1939 childList->Item(j, getter_AddRefs(child)); |
|
1940 if (!IsEmptyTextContent(child)) |
|
1941 return false; |
|
1942 j++; |
|
1943 } |
|
1944 return true; |
|
1945 } |
|
1946 |
|
1947 |
|
1948 bool |
|
1949 nsHTMLCopyEncoder::IsLastNode(nsIDOMNode *aNode) |
|
1950 { |
|
1951 nsCOMPtr<nsIDOMNode> parent; |
|
1952 int32_t offset,j; |
|
1953 nsresult rv = GetNodeLocation(aNode, address_of(parent), &offset); |
|
1954 if (NS_FAILED(rv)) |
|
1955 { |
|
1956 NS_NOTREACHED("failure in IsLastNode"); |
|
1957 return false; |
|
1958 } |
|
1959 nsCOMPtr<nsINode> parentNode = do_QueryInterface(parent); |
|
1960 if (!parentNode) { |
|
1961 return true; |
|
1962 } |
|
1963 |
|
1964 uint32_t numChildren = parentNode->Length(); |
|
1965 if (offset+1 == (int32_t)numChildren) // easy case, we are last dom child |
|
1966 return true; |
|
1967 // need to check if any nodes after us are really visible. |
|
1968 // Mike wrote something for me along these lines in nsSelectionController, |
|
1969 // but I don't think it's ready for use yet - revisit. |
|
1970 // HACK: for now, simply consider all whitespace text nodes to be |
|
1971 // invisible formatting nodes. |
|
1972 j = (int32_t)numChildren-1; |
|
1973 nsCOMPtr<nsIDOMNodeList>childList; |
|
1974 nsCOMPtr<nsIDOMNode> child; |
|
1975 rv = parent->GetChildNodes(getter_AddRefs(childList)); |
|
1976 if (NS_FAILED(rv) || !childList) |
|
1977 { |
|
1978 NS_NOTREACHED("failure in IsLastNode"); |
|
1979 return true; |
|
1980 } |
|
1981 while (j > offset) |
|
1982 { |
|
1983 childList->Item(j, getter_AddRefs(child)); |
|
1984 j--; |
|
1985 if (IsMozBR(child)) // we ignore trailing moz BRs. |
|
1986 continue; |
|
1987 if (!IsEmptyTextContent(child)) |
|
1988 return false; |
|
1989 } |
|
1990 return true; |
|
1991 } |
|
1992 |
|
1993 bool |
|
1994 nsHTMLCopyEncoder::IsEmptyTextContent(nsIDOMNode* aNode) |
|
1995 { |
|
1996 nsCOMPtr<nsIContent> cont = do_QueryInterface(aNode); |
|
1997 return cont && cont->TextIsOnlyWhitespace(); |
|
1998 } |
|
1999 |
|
2000 nsresult NS_NewHTMLCopyTextEncoder(nsIDocumentEncoder** aResult); // make mac compiler happy |
|
2001 |
|
2002 nsresult |
|
2003 NS_NewHTMLCopyTextEncoder(nsIDocumentEncoder** aResult) |
|
2004 { |
|
2005 *aResult = new nsHTMLCopyEncoder; |
|
2006 if (!*aResult) |
|
2007 return NS_ERROR_OUT_OF_MEMORY; |
|
2008 NS_ADDREF(*aResult); |
|
2009 return NS_OK; |
|
2010 } |
|
2011 |
|
2012 int32_t |
|
2013 nsHTMLCopyEncoder::GetImmediateContextCount(const nsTArray<nsINode*>& aAncestorArray) |
|
2014 { |
|
2015 int32_t i = aAncestorArray.Length(), j = 0; |
|
2016 while (j < i) { |
|
2017 nsINode *node = aAncestorArray.ElementAt(j); |
|
2018 if (!node) { |
|
2019 break; |
|
2020 } |
|
2021 nsCOMPtr<nsIContent> content(do_QueryInterface(node)); |
|
2022 if (!content || !content->IsHTML() || (content->Tag() != nsGkAtoms::tr && |
|
2023 content->Tag() != nsGkAtoms::thead && |
|
2024 content->Tag() != nsGkAtoms::tbody && |
|
2025 content->Tag() != nsGkAtoms::tfoot && |
|
2026 content->Tag() != nsGkAtoms::table)) { |
|
2027 break; |
|
2028 } |
|
2029 ++j; |
|
2030 } |
|
2031 return j; |
|
2032 } |
|
2033 |