|
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 #include "mozilla/DebugOnly.h" |
|
7 #include "mozilla/EventStates.h" |
|
8 #include "mozilla/TextEvents.h" |
|
9 |
|
10 #include "nsCRT.h" |
|
11 |
|
12 #include "nsUnicharUtils.h" |
|
13 |
|
14 #include "nsHTMLEditor.h" |
|
15 #include "nsHTMLEditRules.h" |
|
16 #include "nsTextEditUtils.h" |
|
17 #include "nsHTMLEditUtils.h" |
|
18 |
|
19 #include "nsHTMLEditorEventListener.h" |
|
20 #include "TypeInState.h" |
|
21 |
|
22 #include "nsHTMLURIRefObject.h" |
|
23 |
|
24 #include "nsIDOMText.h" |
|
25 #include "nsIDOMMozNamedAttrMap.h" |
|
26 #include "nsIDOMNodeList.h" |
|
27 #include "nsIDOMDocument.h" |
|
28 #include "nsIDOMAttr.h" |
|
29 #include "nsIDocumentInlines.h" |
|
30 #include "nsIDOMEventTarget.h" |
|
31 #include "nsIDOMKeyEvent.h" |
|
32 #include "nsIDOMMouseEvent.h" |
|
33 #include "nsIDOMHTMLAnchorElement.h" |
|
34 #include "nsISelectionController.h" |
|
35 #include "nsIDOMHTMLDocument.h" |
|
36 #include "nsILinkHandler.h" |
|
37 #include "nsIInlineSpellChecker.h" |
|
38 |
|
39 #include "mozilla/css/Loader.h" |
|
40 #include "nsCSSStyleSheet.h" |
|
41 #include "nsIDOMStyleSheet.h" |
|
42 |
|
43 #include "nsIContent.h" |
|
44 #include "nsIContentIterator.h" |
|
45 #include "nsIDOMRange.h" |
|
46 #include "nsISupportsArray.h" |
|
47 #include "nsContentUtils.h" |
|
48 #include "nsIDocumentEncoder.h" |
|
49 #include "nsIDOMDocumentFragment.h" |
|
50 #include "nsIPresShell.h" |
|
51 #include "nsPresContext.h" |
|
52 #include "SetDocTitleTxn.h" |
|
53 #include "nsFocusManager.h" |
|
54 #include "nsPIDOMWindow.h" |
|
55 |
|
56 // netwerk |
|
57 #include "nsIURI.h" |
|
58 #include "nsNetUtil.h" |
|
59 |
|
60 // Transactionas |
|
61 #include "nsStyleSheetTxns.h" |
|
62 |
|
63 // Misc |
|
64 #include "TextEditorTest.h" |
|
65 #include "nsEditorUtils.h" |
|
66 #include "nsWSRunObject.h" |
|
67 #include "nsGkAtoms.h" |
|
68 #include "nsIWidget.h" |
|
69 |
|
70 #include "nsIFrame.h" |
|
71 #include "nsIParserService.h" |
|
72 #include "mozilla/dom/Selection.h" |
|
73 #include "mozilla/dom/Element.h" |
|
74 #include "mozilla/dom/EventTarget.h" |
|
75 #include "mozilla/dom/HTMLBodyElement.h" |
|
76 #include "nsTextFragment.h" |
|
77 |
|
78 using namespace mozilla; |
|
79 using namespace mozilla::dom; |
|
80 using namespace mozilla::widget; |
|
81 |
|
82 // Some utilities to handle annoying overloading of "A" tag for link and named anchor |
|
83 static char hrefText[] = "href"; |
|
84 static char anchorTxt[] = "anchor"; |
|
85 static char namedanchorText[] = "namedanchor"; |
|
86 |
|
87 #define IsLinkTag(s) (s.EqualsIgnoreCase(hrefText)) |
|
88 #define IsNamedAnchorTag(s) (s.EqualsIgnoreCase(anchorTxt) || s.EqualsIgnoreCase(namedanchorText)) |
|
89 |
|
90 nsHTMLEditor::nsHTMLEditor() |
|
91 : nsPlaintextEditor() |
|
92 , mCRInParagraphCreatesParagraph(false) |
|
93 , mSelectedCellIndex(0) |
|
94 , mIsObjectResizingEnabled(true) |
|
95 , mIsResizing(false) |
|
96 , mIsAbsolutelyPositioningEnabled(true) |
|
97 , mResizedObjectIsAbsolutelyPositioned(false) |
|
98 , mGrabberClicked(false) |
|
99 , mIsMoving(false) |
|
100 , mSnapToGridEnabled(false) |
|
101 , mIsInlineTableEditingEnabled(true) |
|
102 , mInfoXIncrement(20) |
|
103 , mInfoYIncrement(20) |
|
104 , mGridSize(0) |
|
105 { |
|
106 } |
|
107 |
|
108 nsHTMLEditor::~nsHTMLEditor() |
|
109 { |
|
110 // remove the rules as an action listener. Else we get a bad |
|
111 // ownership loop later on. it's ok if the rules aren't a listener; |
|
112 // we ignore the error. |
|
113 nsCOMPtr<nsIEditActionListener> mListener = do_QueryInterface(mRules); |
|
114 RemoveEditActionListener(mListener); |
|
115 |
|
116 //the autopointers will clear themselves up. |
|
117 //but we need to also remove the listeners or we have a leak |
|
118 nsCOMPtr<nsISelection>selection; |
|
119 nsresult result = GetSelection(getter_AddRefs(selection)); |
|
120 // if we don't get the selection, just skip this |
|
121 if (NS_SUCCEEDED(result) && selection) |
|
122 { |
|
123 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection)); |
|
124 nsCOMPtr<nsISelectionListener>listener; |
|
125 listener = do_QueryInterface(mTypeInState); |
|
126 if (listener) |
|
127 { |
|
128 selPriv->RemoveSelectionListener(listener); |
|
129 } |
|
130 listener = do_QueryInterface(mSelectionListenerP); |
|
131 if (listener) |
|
132 { |
|
133 selPriv->RemoveSelectionListener(listener); |
|
134 } |
|
135 } |
|
136 |
|
137 mTypeInState = nullptr; |
|
138 mSelectionListenerP = nullptr; |
|
139 |
|
140 // free any default style propItems |
|
141 RemoveAllDefaultProperties(); |
|
142 |
|
143 if (mLinkHandler && mDocWeak) |
|
144 { |
|
145 nsCOMPtr<nsIPresShell> ps = GetPresShell(); |
|
146 |
|
147 if (ps && ps->GetPresContext()) |
|
148 { |
|
149 ps->GetPresContext()->SetLinkHandler(mLinkHandler); |
|
150 } |
|
151 } |
|
152 |
|
153 RemoveEventListeners(); |
|
154 } |
|
155 |
|
156 void |
|
157 nsHTMLEditor::HideAnonymousEditingUIs() |
|
158 { |
|
159 if (mAbsolutelyPositionedObject) |
|
160 HideGrabber(); |
|
161 if (mInlineEditedCell) |
|
162 HideInlineTableEditingUI(); |
|
163 if (mResizedObject) |
|
164 HideResizers(); |
|
165 } |
|
166 |
|
167 NS_IMPL_CYCLE_COLLECTION_CLASS(nsHTMLEditor) |
|
168 |
|
169 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHTMLEditor, nsPlaintextEditor) |
|
170 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTypeInState) |
|
171 NS_IMPL_CYCLE_COLLECTION_UNLINK(mStyleSheets) |
|
172 |
|
173 tmp->HideAnonymousEditingUIs(); |
|
174 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
175 |
|
176 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHTMLEditor, nsPlaintextEditor) |
|
177 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTypeInState) |
|
178 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets) |
|
179 |
|
180 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopLeftHandle) |
|
181 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopHandle) |
|
182 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopRightHandle) |
|
183 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLeftHandle) |
|
184 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRightHandle) |
|
185 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomLeftHandle) |
|
186 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomHandle) |
|
187 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomRightHandle) |
|
188 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActivatedHandle) |
|
189 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizingShadow) |
|
190 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizingInfo) |
|
191 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizedObject) |
|
192 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMouseMotionListenerP) |
|
193 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListenerP) |
|
194 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizeEventListenerP) |
|
195 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(objectResizeEventListeners) |
|
196 |
|
197 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbsolutelyPositionedObject) |
|
198 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGrabber) |
|
199 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPositioningShadow) |
|
200 |
|
201 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineEditedCell) |
|
202 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddColumnBeforeButton) |
|
203 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRemoveColumnButton) |
|
204 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddColumnAfterButton) |
|
205 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddRowBeforeButton) |
|
206 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRemoveRowButton) |
|
207 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddRowAfterButton) |
|
208 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
209 |
|
210 NS_IMPL_ADDREF_INHERITED(nsHTMLEditor, nsEditor) |
|
211 NS_IMPL_RELEASE_INHERITED(nsHTMLEditor, nsEditor) |
|
212 |
|
213 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsHTMLEditor) |
|
214 NS_INTERFACE_MAP_ENTRY(nsIHTMLEditor) |
|
215 NS_INTERFACE_MAP_ENTRY(nsIHTMLObjectResizer) |
|
216 NS_INTERFACE_MAP_ENTRY(nsIHTMLAbsPosEditor) |
|
217 NS_INTERFACE_MAP_ENTRY(nsIHTMLInlineTableEditor) |
|
218 NS_INTERFACE_MAP_ENTRY(nsITableEditor) |
|
219 NS_INTERFACE_MAP_ENTRY(nsIEditorStyleSheets) |
|
220 NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver) |
|
221 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) |
|
222 NS_INTERFACE_MAP_END_INHERITING(nsPlaintextEditor) |
|
223 |
|
224 |
|
225 NS_IMETHODIMP |
|
226 nsHTMLEditor::Init(nsIDOMDocument *aDoc, |
|
227 nsIContent *aRoot, |
|
228 nsISelectionController *aSelCon, |
|
229 uint32_t aFlags, |
|
230 const nsAString& aInitialValue) |
|
231 { |
|
232 NS_PRECONDITION(aDoc && !aSelCon, "bad arg"); |
|
233 NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER); |
|
234 MOZ_ASSERT(aInitialValue.IsEmpty(), "Non-empty initial values not supported"); |
|
235 |
|
236 nsresult result = NS_OK, rulesRes = NS_OK; |
|
237 |
|
238 if (1) |
|
239 { |
|
240 // block to scope nsAutoEditInitRulesTrigger |
|
241 nsAutoEditInitRulesTrigger rulesTrigger(static_cast<nsPlaintextEditor*>(this), rulesRes); |
|
242 |
|
243 // Init the plaintext editor |
|
244 result = nsPlaintextEditor::Init(aDoc, aRoot, nullptr, aFlags, aInitialValue); |
|
245 if (NS_FAILED(result)) { return result; } |
|
246 |
|
247 // Init mutation observer |
|
248 nsCOMPtr<nsINode> document = do_QueryInterface(aDoc); |
|
249 document->AddMutationObserverUnlessExists(this); |
|
250 |
|
251 // disable Composer-only features |
|
252 if (IsMailEditor()) |
|
253 { |
|
254 SetAbsolutePositioningEnabled(false); |
|
255 SetSnapToGridEnabled(false); |
|
256 } |
|
257 |
|
258 // Init the HTML-CSS utils |
|
259 mHTMLCSSUtils = new nsHTMLCSSUtils(this); |
|
260 |
|
261 // disable links |
|
262 nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
|
263 NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); |
|
264 nsPresContext *context = presShell->GetPresContext(); |
|
265 NS_ENSURE_TRUE(context, NS_ERROR_NULL_POINTER); |
|
266 if (!IsPlaintextEditor() && !IsInteractionAllowed()) { |
|
267 mLinkHandler = context->GetLinkHandler(); |
|
268 |
|
269 context->SetLinkHandler(nullptr); |
|
270 } |
|
271 |
|
272 // init the type-in state |
|
273 mTypeInState = new TypeInState(); |
|
274 |
|
275 // init the selection listener for image resizing |
|
276 mSelectionListenerP = new ResizerSelectionListener(this); |
|
277 |
|
278 if (!IsInteractionAllowed()) { |
|
279 // ignore any errors from this in case the file is missing |
|
280 AddOverrideStyleSheet(NS_LITERAL_STRING("resource://gre/res/EditorOverride.css")); |
|
281 } |
|
282 |
|
283 nsCOMPtr<nsISelection>selection; |
|
284 result = GetSelection(getter_AddRefs(selection)); |
|
285 if (NS_FAILED(result)) { return result; } |
|
286 if (selection) |
|
287 { |
|
288 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection)); |
|
289 nsCOMPtr<nsISelectionListener>listener; |
|
290 listener = do_QueryInterface(mTypeInState); |
|
291 if (listener) { |
|
292 selPriv->AddSelectionListener(listener); |
|
293 } |
|
294 listener = do_QueryInterface(mSelectionListenerP); |
|
295 if (listener) { |
|
296 selPriv->AddSelectionListener(listener); |
|
297 } |
|
298 } |
|
299 } |
|
300 |
|
301 NS_ENSURE_SUCCESS(rulesRes, rulesRes); |
|
302 return result; |
|
303 } |
|
304 |
|
305 NS_IMETHODIMP |
|
306 nsHTMLEditor::PreDestroy(bool aDestroyingFrames) |
|
307 { |
|
308 if (mDidPreDestroy) { |
|
309 return NS_OK; |
|
310 } |
|
311 |
|
312 nsCOMPtr<nsINode> document = do_QueryReferent(mDocWeak); |
|
313 if (document) { |
|
314 document->RemoveMutationObserver(this); |
|
315 } |
|
316 |
|
317 while (mStyleSheetURLs.Length()) |
|
318 { |
|
319 RemoveOverrideStyleSheet(mStyleSheetURLs[0]); |
|
320 } |
|
321 |
|
322 // Clean up after our anonymous content -- we don't want these nodes to |
|
323 // stay around (which they would, since the frames have an owning reference). |
|
324 HideAnonymousEditingUIs(); |
|
325 |
|
326 return nsPlaintextEditor::PreDestroy(aDestroyingFrames); |
|
327 } |
|
328 |
|
329 NS_IMETHODIMP |
|
330 nsHTMLEditor::GetRootElement(nsIDOMElement **aRootElement) |
|
331 { |
|
332 NS_ENSURE_ARG_POINTER(aRootElement); |
|
333 |
|
334 if (mRootElement) { |
|
335 return nsEditor::GetRootElement(aRootElement); |
|
336 } |
|
337 |
|
338 *aRootElement = nullptr; |
|
339 |
|
340 // Use the HTML documents body element as the editor root if we didn't |
|
341 // get a root element during initialization. |
|
342 |
|
343 nsCOMPtr<nsIDOMElement> rootElement; |
|
344 nsCOMPtr<nsIDOMHTMLElement> bodyElement; |
|
345 nsresult rv = GetBodyElement(getter_AddRefs(bodyElement)); |
|
346 NS_ENSURE_SUCCESS(rv, rv); |
|
347 |
|
348 if (bodyElement) { |
|
349 rootElement = bodyElement; |
|
350 } else { |
|
351 // If there is no HTML body element, |
|
352 // we should use the document root element instead. |
|
353 nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak); |
|
354 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); |
|
355 |
|
356 rv = doc->GetDocumentElement(getter_AddRefs(rootElement)); |
|
357 NS_ENSURE_SUCCESS(rv, rv); |
|
358 // Document can have no elements |
|
359 if (!rootElement) { |
|
360 return NS_ERROR_NOT_AVAILABLE; |
|
361 } |
|
362 } |
|
363 |
|
364 mRootElement = do_QueryInterface(rootElement); |
|
365 rootElement.forget(aRootElement); |
|
366 |
|
367 return NS_OK; |
|
368 } |
|
369 |
|
370 already_AddRefed<nsIContent> |
|
371 nsHTMLEditor::FindSelectionRoot(nsINode *aNode) |
|
372 { |
|
373 NS_PRECONDITION(aNode->IsNodeOfType(nsINode::eDOCUMENT) || |
|
374 aNode->IsNodeOfType(nsINode::eCONTENT), |
|
375 "aNode must be content or document node"); |
|
376 |
|
377 nsCOMPtr<nsIDocument> doc = aNode->GetCurrentDoc(); |
|
378 if (!doc) { |
|
379 return nullptr; |
|
380 } |
|
381 |
|
382 nsCOMPtr<nsIContent> content; |
|
383 if (doc->HasFlag(NODE_IS_EDITABLE) || !aNode->IsContent()) { |
|
384 content = doc->GetRootElement(); |
|
385 return content.forget(); |
|
386 } |
|
387 content = aNode->AsContent(); |
|
388 |
|
389 // XXX If we have readonly flag, shouldn't return the element which has |
|
390 // contenteditable="true"? However, such case isn't there without chrome |
|
391 // permission script. |
|
392 if (IsReadonly()) { |
|
393 // We still want to allow selection in a readonly editor. |
|
394 content = do_QueryInterface(GetRoot()); |
|
395 return content.forget(); |
|
396 } |
|
397 |
|
398 if (!content->HasFlag(NODE_IS_EDITABLE)) { |
|
399 // If the content is in read-write state but is not editable itself, |
|
400 // return it as the selection root. |
|
401 if (content->IsElement() && |
|
402 content->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) { |
|
403 return content.forget(); |
|
404 } |
|
405 return nullptr; |
|
406 } |
|
407 |
|
408 // For non-readonly editors we want to find the root of the editable subtree |
|
409 // containing aContent. |
|
410 content = content->GetEditingHost(); |
|
411 return content.forget(); |
|
412 } |
|
413 |
|
414 /* virtual */ |
|
415 void |
|
416 nsHTMLEditor::CreateEventListeners() |
|
417 { |
|
418 // Don't create the handler twice |
|
419 if (!mEventListener) { |
|
420 mEventListener = new nsHTMLEditorEventListener(); |
|
421 } |
|
422 } |
|
423 |
|
424 nsresult |
|
425 nsHTMLEditor::InstallEventListeners() |
|
426 { |
|
427 NS_ENSURE_TRUE(mDocWeak && mEventListener, |
|
428 NS_ERROR_NOT_INITIALIZED); |
|
429 |
|
430 // NOTE: nsHTMLEditor doesn't need to initialize mEventTarget here because |
|
431 // the target must be document node and it must be referenced as weak pointer. |
|
432 |
|
433 nsHTMLEditorEventListener* listener = |
|
434 reinterpret_cast<nsHTMLEditorEventListener*>(mEventListener.get()); |
|
435 return listener->Connect(this); |
|
436 } |
|
437 |
|
438 void |
|
439 nsHTMLEditor::RemoveEventListeners() |
|
440 { |
|
441 if (!mDocWeak) |
|
442 { |
|
443 return; |
|
444 } |
|
445 |
|
446 nsCOMPtr<nsIDOMEventTarget> target = GetDOMEventTarget(); |
|
447 |
|
448 if (target) |
|
449 { |
|
450 // Both mMouseMotionListenerP and mResizeEventListenerP can be |
|
451 // registerd with other targets than the DOM event receiver that |
|
452 // we can reach from here. But nonetheless, unregister the event |
|
453 // listeners with the DOM event reveiver (if it's registerd with |
|
454 // other targets, it'll get unregisterd once the target goes |
|
455 // away). |
|
456 |
|
457 if (mMouseMotionListenerP) |
|
458 { |
|
459 // mMouseMotionListenerP might be registerd either as bubbling or |
|
460 // capturing, unregister by both. |
|
461 target->RemoveEventListener(NS_LITERAL_STRING("mousemove"), |
|
462 mMouseMotionListenerP, false); |
|
463 target->RemoveEventListener(NS_LITERAL_STRING("mousemove"), |
|
464 mMouseMotionListenerP, true); |
|
465 } |
|
466 |
|
467 if (mResizeEventListenerP) |
|
468 { |
|
469 target->RemoveEventListener(NS_LITERAL_STRING("resize"), |
|
470 mResizeEventListenerP, false); |
|
471 } |
|
472 } |
|
473 |
|
474 mMouseMotionListenerP = nullptr; |
|
475 mResizeEventListenerP = nullptr; |
|
476 |
|
477 nsPlaintextEditor::RemoveEventListeners(); |
|
478 } |
|
479 |
|
480 NS_IMETHODIMP |
|
481 nsHTMLEditor::SetFlags(uint32_t aFlags) |
|
482 { |
|
483 nsresult rv = nsPlaintextEditor::SetFlags(aFlags); |
|
484 NS_ENSURE_SUCCESS(rv, rv); |
|
485 |
|
486 // Sets mCSSAware to correspond to aFlags. This toggles whether CSS is |
|
487 // used to style elements in the editor. Note that the editor is only CSS |
|
488 // aware by default in Composer and in the mail editor. |
|
489 mCSSAware = !NoCSS() && !IsMailEditor(); |
|
490 |
|
491 return NS_OK; |
|
492 } |
|
493 |
|
494 NS_IMETHODIMP |
|
495 nsHTMLEditor::InitRules() |
|
496 { |
|
497 if (!mRules) { |
|
498 // instantiate the rules for the html editor |
|
499 mRules = new nsHTMLEditRules(); |
|
500 } |
|
501 return mRules->Init(static_cast<nsPlaintextEditor*>(this)); |
|
502 } |
|
503 |
|
504 NS_IMETHODIMP |
|
505 nsHTMLEditor::BeginningOfDocument() |
|
506 { |
|
507 if (!mDocWeak) { return NS_ERROR_NOT_INITIALIZED; } |
|
508 |
|
509 // get the selection |
|
510 nsCOMPtr<nsISelection> selection; |
|
511 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
512 NS_ENSURE_SUCCESS(res, res); |
|
513 NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED); |
|
514 |
|
515 // Get the root element. |
|
516 nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(GetRoot()); |
|
517 if (!rootElement) { |
|
518 NS_WARNING("GetRoot() returned a null pointer (mRootElement is null)"); |
|
519 return NS_OK; |
|
520 } |
|
521 |
|
522 // find first editable thingy |
|
523 bool done = false; |
|
524 nsCOMPtr<nsIDOMNode> curNode(rootElement), selNode; |
|
525 int32_t curOffset = 0, selOffset; |
|
526 while (!done) |
|
527 { |
|
528 nsWSRunObject wsObj(this, curNode, curOffset); |
|
529 nsCOMPtr<nsIDOMNode> visNode; |
|
530 int32_t visOffset=0; |
|
531 WSType visType; |
|
532 wsObj.NextVisibleNode(curNode, curOffset, address_of(visNode), &visOffset, &visType); |
|
533 if (visType == WSType::normalWS || visType == WSType::text) { |
|
534 selNode = visNode; |
|
535 selOffset = visOffset; |
|
536 done = true; |
|
537 } else if (visType == WSType::br || visType == WSType::special) { |
|
538 selNode = GetNodeLocation(visNode, &selOffset); |
|
539 done = true; |
|
540 } else if (visType == WSType::otherBlock) { |
|
541 // By definition of nsWSRunObject, a block element terminates |
|
542 // a whitespace run. That is, although we are calling a method |
|
543 // that is named "NextVisibleNode", the node returned |
|
544 // might not be visible/editable! |
|
545 // If the given block does not contain any visible/editable items, |
|
546 // we want to skip it and continue our search. |
|
547 |
|
548 if (!IsContainer(visNode)) |
|
549 { |
|
550 // However, we were given a block that is not a container. |
|
551 // Since the block can not contain anything that's visible, |
|
552 // such a block only makes sense if it is visible by itself, |
|
553 // like a <hr> |
|
554 // We want to place the caret in front of that block. |
|
555 |
|
556 selNode = GetNodeLocation(visNode, &selOffset); |
|
557 done = true; |
|
558 } |
|
559 else |
|
560 { |
|
561 bool isEmptyBlock; |
|
562 if (NS_SUCCEEDED(IsEmptyNode(visNode, &isEmptyBlock)) && |
|
563 isEmptyBlock) |
|
564 { |
|
565 // skip the empty block |
|
566 curNode = GetNodeLocation(visNode, &curOffset); |
|
567 ++curOffset; |
|
568 } |
|
569 else |
|
570 { |
|
571 curNode = visNode; |
|
572 curOffset = 0; |
|
573 } |
|
574 // keep looping |
|
575 } |
|
576 } |
|
577 else |
|
578 { |
|
579 // else we found nothing useful |
|
580 selNode = curNode; |
|
581 selOffset = curOffset; |
|
582 done = true; |
|
583 } |
|
584 } |
|
585 return selection->Collapse(selNode, selOffset); |
|
586 } |
|
587 |
|
588 nsresult |
|
589 nsHTMLEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent) |
|
590 { |
|
591 // NOTE: When you change this method, you should also change: |
|
592 // * editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html |
|
593 |
|
594 if (IsReadonly() || IsDisabled()) { |
|
595 // When we're not editable, the events are handled on nsEditor, so, we can |
|
596 // bypass nsPlaintextEditor. |
|
597 return nsEditor::HandleKeyPressEvent(aKeyEvent); |
|
598 } |
|
599 |
|
600 WidgetKeyboardEvent* nativeKeyEvent = |
|
601 aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); |
|
602 NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED); |
|
603 NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS, |
|
604 "HandleKeyPressEvent gets non-keypress event"); |
|
605 |
|
606 switch (nativeKeyEvent->keyCode) { |
|
607 case nsIDOMKeyEvent::DOM_VK_META: |
|
608 case nsIDOMKeyEvent::DOM_VK_WIN: |
|
609 case nsIDOMKeyEvent::DOM_VK_SHIFT: |
|
610 case nsIDOMKeyEvent::DOM_VK_CONTROL: |
|
611 case nsIDOMKeyEvent::DOM_VK_ALT: |
|
612 case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: |
|
613 case nsIDOMKeyEvent::DOM_VK_DELETE: |
|
614 // These keys are handled on nsEditor, so, we can bypass |
|
615 // nsPlaintextEditor. |
|
616 return nsEditor::HandleKeyPressEvent(aKeyEvent); |
|
617 case nsIDOMKeyEvent::DOM_VK_TAB: { |
|
618 if (IsPlaintextEditor()) { |
|
619 // If this works as plain text editor, e.g., mail editor for plain |
|
620 // text, should be handled on nsPlaintextEditor. |
|
621 return nsPlaintextEditor::HandleKeyPressEvent(aKeyEvent); |
|
622 } |
|
623 |
|
624 if (IsTabbable()) { |
|
625 return NS_OK; // let it be used for focus switching |
|
626 } |
|
627 |
|
628 if (nativeKeyEvent->IsControl() || nativeKeyEvent->IsAlt() || |
|
629 nativeKeyEvent->IsMeta() || nativeKeyEvent->IsOS()) { |
|
630 return NS_OK; |
|
631 } |
|
632 |
|
633 nsCOMPtr<nsISelection> selection; |
|
634 nsresult rv = GetSelection(getter_AddRefs(selection)); |
|
635 NS_ENSURE_SUCCESS(rv, rv); |
|
636 int32_t offset; |
|
637 nsCOMPtr<nsIDOMNode> node, blockParent; |
|
638 rv = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); |
|
639 NS_ENSURE_SUCCESS(rv, rv); |
|
640 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); |
|
641 |
|
642 bool isBlock = false; |
|
643 NodeIsBlock(node, &isBlock); |
|
644 if (isBlock) { |
|
645 blockParent = node; |
|
646 } else { |
|
647 blockParent = GetBlockNodeParent(node); |
|
648 } |
|
649 |
|
650 if (!blockParent) { |
|
651 break; |
|
652 } |
|
653 |
|
654 bool handled = false; |
|
655 if (nsHTMLEditUtils::IsTableElement(blockParent)) { |
|
656 rv = TabInTable(nativeKeyEvent->IsShift(), &handled); |
|
657 if (handled) { |
|
658 ScrollSelectionIntoView(false); |
|
659 } |
|
660 } else if (nsHTMLEditUtils::IsListItem(blockParent)) { |
|
661 rv = Indent(nativeKeyEvent->IsShift() ? |
|
662 NS_LITERAL_STRING("outdent") : |
|
663 NS_LITERAL_STRING("indent")); |
|
664 handled = true; |
|
665 } |
|
666 NS_ENSURE_SUCCESS(rv, rv); |
|
667 if (handled) { |
|
668 return aKeyEvent->PreventDefault(); // consumed |
|
669 } |
|
670 if (nativeKeyEvent->IsShift()) { |
|
671 return NS_OK; // don't type text for shift tabs |
|
672 } |
|
673 aKeyEvent->PreventDefault(); |
|
674 return TypedText(NS_LITERAL_STRING("\t"), eTypedText); |
|
675 } |
|
676 case nsIDOMKeyEvent::DOM_VK_RETURN: |
|
677 if (nativeKeyEvent->IsControl() || nativeKeyEvent->IsAlt() || |
|
678 nativeKeyEvent->IsMeta() || nativeKeyEvent->IsOS()) { |
|
679 return NS_OK; |
|
680 } |
|
681 aKeyEvent->PreventDefault(); // consumed |
|
682 if (nativeKeyEvent->IsShift() && !IsPlaintextEditor()) { |
|
683 // only inserts a br node |
|
684 return TypedText(EmptyString(), eTypedBR); |
|
685 } |
|
686 // uses rules to figure out what to insert |
|
687 return TypedText(EmptyString(), eTypedBreak); |
|
688 } |
|
689 |
|
690 // NOTE: On some keyboard layout, some characters are inputted with Control |
|
691 // key or Alt key, but at that time, widget sets FALSE to these keys. |
|
692 if (nativeKeyEvent->charCode == 0 || nativeKeyEvent->IsControl() || |
|
693 nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta() || |
|
694 nativeKeyEvent->IsOS()) { |
|
695 // we don't PreventDefault() here or keybindings like control-x won't work |
|
696 return NS_OK; |
|
697 } |
|
698 aKeyEvent->PreventDefault(); |
|
699 nsAutoString str(nativeKeyEvent->charCode); |
|
700 return TypedText(str, eTypedText); |
|
701 } |
|
702 |
|
703 static void |
|
704 AssertParserServiceIsCorrect(nsIAtom* aTag, bool aIsBlock) |
|
705 { |
|
706 #ifdef DEBUG |
|
707 // Check this against what we would have said with the old code: |
|
708 if (aTag==nsEditProperty::p || |
|
709 aTag==nsEditProperty::div || |
|
710 aTag==nsEditProperty::blockquote || |
|
711 aTag==nsEditProperty::h1 || |
|
712 aTag==nsEditProperty::h2 || |
|
713 aTag==nsEditProperty::h3 || |
|
714 aTag==nsEditProperty::h4 || |
|
715 aTag==nsEditProperty::h5 || |
|
716 aTag==nsEditProperty::h6 || |
|
717 aTag==nsEditProperty::ul || |
|
718 aTag==nsEditProperty::ol || |
|
719 aTag==nsEditProperty::dl || |
|
720 aTag==nsEditProperty::noscript || |
|
721 aTag==nsEditProperty::form || |
|
722 aTag==nsEditProperty::hr || |
|
723 aTag==nsEditProperty::table || |
|
724 aTag==nsEditProperty::fieldset || |
|
725 aTag==nsEditProperty::address || |
|
726 aTag==nsEditProperty::col || |
|
727 aTag==nsEditProperty::colgroup || |
|
728 aTag==nsEditProperty::li || |
|
729 aTag==nsEditProperty::dt || |
|
730 aTag==nsEditProperty::dd || |
|
731 aTag==nsEditProperty::legend ) |
|
732 { |
|
733 if (!aIsBlock) { |
|
734 nsAutoString assertmsg (NS_LITERAL_STRING("Parser and editor disagree on blockness: ")); |
|
735 |
|
736 nsAutoString tagName; |
|
737 aTag->ToString(tagName); |
|
738 assertmsg.Append(tagName); |
|
739 char* assertstr = ToNewCString(assertmsg); |
|
740 NS_ASSERTION(aIsBlock, assertstr); |
|
741 NS_Free(assertstr); |
|
742 } |
|
743 } |
|
744 #endif // DEBUG |
|
745 } |
|
746 |
|
747 /** |
|
748 * Returns true if the id represents an element of block type. |
|
749 * Can be used to determine if a new paragraph should be started. |
|
750 */ |
|
751 bool |
|
752 nsHTMLEditor::NodeIsBlockStatic(const dom::Element* aElement) |
|
753 { |
|
754 MOZ_ASSERT(aElement); |
|
755 |
|
756 nsIAtom* tagAtom = aElement->Tag(); |
|
757 MOZ_ASSERT(tagAtom); |
|
758 |
|
759 // Nodes we know we want to treat as block |
|
760 // even though the parser says they're not: |
|
761 if (tagAtom==nsEditProperty::body || |
|
762 tagAtom==nsEditProperty::head || |
|
763 tagAtom==nsEditProperty::tbody || |
|
764 tagAtom==nsEditProperty::thead || |
|
765 tagAtom==nsEditProperty::tfoot || |
|
766 tagAtom==nsEditProperty::tr || |
|
767 tagAtom==nsEditProperty::th || |
|
768 tagAtom==nsEditProperty::td || |
|
769 tagAtom==nsEditProperty::li || |
|
770 tagAtom==nsEditProperty::dt || |
|
771 tagAtom==nsEditProperty::dd || |
|
772 tagAtom==nsEditProperty::pre) |
|
773 { |
|
774 return true; |
|
775 } |
|
776 |
|
777 bool isBlock; |
|
778 #ifdef DEBUG |
|
779 // XXX we can't use DebugOnly here because VC++ is stupid (bug 802884) |
|
780 nsresult rv = |
|
781 #endif |
|
782 nsContentUtils::GetParserService()-> |
|
783 IsBlock(nsContentUtils::GetParserService()->HTMLAtomTagToId(tagAtom), |
|
784 isBlock); |
|
785 MOZ_ASSERT(rv == NS_OK); |
|
786 |
|
787 AssertParserServiceIsCorrect(tagAtom, isBlock); |
|
788 |
|
789 return isBlock; |
|
790 } |
|
791 |
|
792 nsresult |
|
793 nsHTMLEditor::NodeIsBlockStatic(nsIDOMNode *aNode, bool *aIsBlock) |
|
794 { |
|
795 if (!aNode || !aIsBlock) { return NS_ERROR_NULL_POINTER; } |
|
796 |
|
797 nsCOMPtr<dom::Element> element = do_QueryInterface(aNode); |
|
798 *aIsBlock = element && NodeIsBlockStatic(element); |
|
799 return NS_OK; |
|
800 } |
|
801 |
|
802 NS_IMETHODIMP |
|
803 nsHTMLEditor::NodeIsBlock(nsIDOMNode *aNode, bool *aIsBlock) |
|
804 { |
|
805 return NodeIsBlockStatic(aNode, aIsBlock); |
|
806 } |
|
807 |
|
808 bool |
|
809 nsHTMLEditor::IsBlockNode(nsINode *aNode) |
|
810 { |
|
811 return aNode && aNode->IsElement() && NodeIsBlockStatic(aNode->AsElement()); |
|
812 } |
|
813 |
|
814 // Non-static version for the nsIEditor interface and JavaScript |
|
815 NS_IMETHODIMP |
|
816 nsHTMLEditor::SetDocumentTitle(const nsAString &aTitle) |
|
817 { |
|
818 nsRefPtr<SetDocTitleTxn> txn = new SetDocTitleTxn(); |
|
819 NS_ENSURE_TRUE(txn, NS_ERROR_OUT_OF_MEMORY); |
|
820 |
|
821 nsresult result = txn->Init(this, &aTitle); |
|
822 NS_ENSURE_SUCCESS(result, result); |
|
823 |
|
824 //Don't let Rules System change the selection |
|
825 nsAutoTxnsConserveSelection dontChangeSelection(this); |
|
826 return nsEditor::DoTransaction(txn); |
|
827 } |
|
828 |
|
829 /* ------------ Block methods moved from nsEditor -------------- */ |
|
830 /////////////////////////////////////////////////////////////////////////// |
|
831 // GetBlockNodeParent: returns enclosing block level ancestor, if any |
|
832 // |
|
833 already_AddRefed<nsIDOMNode> |
|
834 nsHTMLEditor::GetBlockNodeParent(nsIDOMNode *aNode) |
|
835 { |
|
836 if (!aNode) |
|
837 { |
|
838 NS_NOTREACHED("null node passed to GetBlockNodeParent()"); |
|
839 return nullptr; |
|
840 } |
|
841 |
|
842 nsCOMPtr<nsIDOMNode> p; |
|
843 if (NS_FAILED(aNode->GetParentNode(getter_AddRefs(p)))) // no parent, ran off top of tree |
|
844 return nullptr; |
|
845 |
|
846 nsCOMPtr<nsIDOMNode> tmp; |
|
847 while (p) |
|
848 { |
|
849 bool isBlock; |
|
850 if (NS_FAILED(NodeIsBlockStatic(p, &isBlock)) || isBlock) |
|
851 break; |
|
852 if (NS_FAILED(p->GetParentNode(getter_AddRefs(tmp))) || !tmp) // no parent, ran off top of tree |
|
853 break; |
|
854 |
|
855 p = tmp; |
|
856 } |
|
857 return p.forget(); |
|
858 } |
|
859 |
|
860 static const char16_t nbsp = 160; |
|
861 |
|
862 /////////////////////////////////////////////////////////////////////////////// |
|
863 // IsNextCharInNodeWhitespace: checks the adjacent content in the same node to |
|
864 // see if following selection is whitespace or nbsp |
|
865 void |
|
866 nsHTMLEditor::IsNextCharInNodeWhitespace(nsIContent* aContent, |
|
867 int32_t aOffset, |
|
868 bool* outIsSpace, |
|
869 bool* outIsNBSP, |
|
870 nsIContent** outNode, |
|
871 int32_t* outOffset) |
|
872 { |
|
873 MOZ_ASSERT(aContent && outIsSpace && outIsNBSP); |
|
874 MOZ_ASSERT((outNode && outOffset) || (!outNode && !outOffset)); |
|
875 *outIsSpace = false; |
|
876 *outIsNBSP = false; |
|
877 if (outNode && outOffset) { |
|
878 *outNode = nullptr; |
|
879 *outOffset = -1; |
|
880 } |
|
881 |
|
882 if (aContent->IsNodeOfType(nsINode::eTEXT) && |
|
883 (uint32_t)aOffset < aContent->Length()) { |
|
884 char16_t ch = aContent->GetText()->CharAt(aOffset); |
|
885 *outIsSpace = nsCRT::IsAsciiSpace(ch); |
|
886 *outIsNBSP = (ch == nbsp); |
|
887 if (outNode && outOffset) { |
|
888 NS_IF_ADDREF(*outNode = aContent); |
|
889 // yes, this is _past_ the character |
|
890 *outOffset = aOffset + 1; |
|
891 } |
|
892 } |
|
893 } |
|
894 |
|
895 |
|
896 /////////////////////////////////////////////////////////////////////////////// |
|
897 // IsPrevCharInNodeWhitespace: checks the adjacent content in the same node to |
|
898 // see if following selection is whitespace |
|
899 void |
|
900 nsHTMLEditor::IsPrevCharInNodeWhitespace(nsIContent* aContent, |
|
901 int32_t aOffset, |
|
902 bool* outIsSpace, |
|
903 bool* outIsNBSP, |
|
904 nsIContent** outNode, |
|
905 int32_t* outOffset) |
|
906 { |
|
907 MOZ_ASSERT(aContent && outIsSpace && outIsNBSP); |
|
908 MOZ_ASSERT((outNode && outOffset) || (!outNode && !outOffset)); |
|
909 *outIsSpace = false; |
|
910 *outIsNBSP = false; |
|
911 if (outNode && outOffset) { |
|
912 *outNode = nullptr; |
|
913 *outOffset = -1; |
|
914 } |
|
915 |
|
916 if (aContent->IsNodeOfType(nsINode::eTEXT) && aOffset > 0) { |
|
917 char16_t ch = aContent->GetText()->CharAt(aOffset - 1); |
|
918 *outIsSpace = nsCRT::IsAsciiSpace(ch); |
|
919 *outIsNBSP = (ch == nbsp); |
|
920 if (outNode && outOffset) { |
|
921 NS_IF_ADDREF(*outNode = aContent); |
|
922 *outOffset = aOffset - 1; |
|
923 } |
|
924 } |
|
925 } |
|
926 |
|
927 |
|
928 |
|
929 /* ------------ End Block methods -------------- */ |
|
930 |
|
931 |
|
932 bool nsHTMLEditor::IsVisBreak(nsIDOMNode *aNode) |
|
933 { |
|
934 NS_ENSURE_TRUE(aNode, false); |
|
935 if (!nsTextEditUtils::IsBreak(aNode)) |
|
936 return false; |
|
937 // check if there is a later node in block after br |
|
938 nsCOMPtr<nsIDOMNode> priorNode, nextNode; |
|
939 GetPriorHTMLNode(aNode, address_of(priorNode), true); |
|
940 GetNextHTMLNode(aNode, address_of(nextNode), true); |
|
941 // if we are next to another break, we are visible |
|
942 if (priorNode && nsTextEditUtils::IsBreak(priorNode)) |
|
943 return true; |
|
944 if (nextNode && nsTextEditUtils::IsBreak(nextNode)) |
|
945 return true; |
|
946 |
|
947 // if we are right before block boundary, then br not visible |
|
948 NS_ENSURE_TRUE(nextNode, false); // this break is trailer in block, it's not visible |
|
949 if (IsBlockNode(nextNode)) |
|
950 return false; // break is right before a block, it's not visible |
|
951 |
|
952 // sigh. We have to use expensive whitespace calculation code to |
|
953 // determine what is going on |
|
954 nsCOMPtr<nsIDOMNode> selNode, tmp; |
|
955 int32_t selOffset; |
|
956 selNode = GetNodeLocation(aNode, &selOffset); |
|
957 selOffset++; // lets look after the break |
|
958 nsWSRunObject wsObj(this, selNode, selOffset); |
|
959 nsCOMPtr<nsIDOMNode> visNode; |
|
960 int32_t visOffset=0; |
|
961 WSType visType; |
|
962 wsObj.NextVisibleNode(selNode, selOffset, address_of(visNode), &visOffset, &visType); |
|
963 if (visType & WSType::block) { |
|
964 return false; |
|
965 } |
|
966 |
|
967 return true; |
|
968 } |
|
969 |
|
970 NS_IMETHODIMP |
|
971 nsHTMLEditor::BreakIsVisible(nsIDOMNode *aNode, bool *aIsVisible) |
|
972 { |
|
973 NS_ENSURE_ARG_POINTER(aNode && aIsVisible); |
|
974 |
|
975 *aIsVisible = IsVisBreak(aNode); |
|
976 |
|
977 return NS_OK; |
|
978 } |
|
979 |
|
980 |
|
981 NS_IMETHODIMP |
|
982 nsHTMLEditor::GetIsDocumentEditable(bool *aIsDocumentEditable) |
|
983 { |
|
984 NS_ENSURE_ARG_POINTER(aIsDocumentEditable); |
|
985 |
|
986 nsCOMPtr<nsIDOMDocument> doc = GetDOMDocument(); |
|
987 *aIsDocumentEditable = doc && IsModifiable(); |
|
988 |
|
989 return NS_OK; |
|
990 } |
|
991 |
|
992 bool nsHTMLEditor::IsModifiable() |
|
993 { |
|
994 return !IsReadonly(); |
|
995 } |
|
996 |
|
997 NS_IMETHODIMP |
|
998 nsHTMLEditor::UpdateBaseURL() |
|
999 { |
|
1000 nsCOMPtr<nsIDOMDocument> domDoc = GetDOMDocument(); |
|
1001 NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE); |
|
1002 |
|
1003 // Look for an HTML <base> tag |
|
1004 nsCOMPtr<nsIDOMNodeList> nodeList; |
|
1005 nsresult rv = domDoc->GetElementsByTagName(NS_LITERAL_STRING("base"), getter_AddRefs(nodeList)); |
|
1006 NS_ENSURE_SUCCESS(rv, rv); |
|
1007 |
|
1008 nsCOMPtr<nsIDOMNode> baseNode; |
|
1009 if (nodeList) |
|
1010 { |
|
1011 uint32_t count; |
|
1012 nodeList->GetLength(&count); |
|
1013 if (count >= 1) |
|
1014 { |
|
1015 rv = nodeList->Item(0, getter_AddRefs(baseNode)); |
|
1016 NS_ENSURE_SUCCESS(rv, rv); |
|
1017 } |
|
1018 } |
|
1019 // If no base tag, then set baseURL to the document's URL |
|
1020 // This is very important, else relative URLs for links and images are wrong |
|
1021 if (!baseNode) |
|
1022 { |
|
1023 nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); |
|
1024 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); |
|
1025 |
|
1026 return doc->SetBaseURI(doc->GetDocumentURI()); |
|
1027 } |
|
1028 return NS_OK; |
|
1029 } |
|
1030 |
|
1031 /* This routine is needed to provide a bottleneck for typing for logging |
|
1032 purposes. Can't use HandleKeyPress() (above) for that since it takes |
|
1033 a nsIDOMKeyEvent* parameter. So instead we pass enough info through |
|
1034 to TypedText() to determine what action to take, but without passing |
|
1035 an event. |
|
1036 */ |
|
1037 NS_IMETHODIMP |
|
1038 nsHTMLEditor::TypedText(const nsAString& aString, ETypingAction aAction) |
|
1039 { |
|
1040 nsAutoPlaceHolderBatch batch(this, nsGkAtoms::TypingTxnName); |
|
1041 |
|
1042 if (aAction == eTypedBR) { |
|
1043 // only inserts a br node |
|
1044 nsCOMPtr<nsIDOMNode> brNode; |
|
1045 return InsertBR(address_of(brNode)); |
|
1046 } |
|
1047 |
|
1048 return nsPlaintextEditor::TypedText(aString, aAction); |
|
1049 } |
|
1050 |
|
1051 NS_IMETHODIMP nsHTMLEditor::TabInTable(bool inIsShift, bool *outHandled) |
|
1052 { |
|
1053 NS_ENSURE_TRUE(outHandled, NS_ERROR_NULL_POINTER); |
|
1054 *outHandled = false; |
|
1055 |
|
1056 // Find enclosing table cell from the selection (cell may be the selected element) |
|
1057 nsCOMPtr<nsIDOMElement> cellElement; |
|
1058 // can't use |NS_LITERAL_STRING| here until |GetElementOrParentByTagName| is fixed to accept readables |
|
1059 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr, getter_AddRefs(cellElement)); |
|
1060 NS_ENSURE_SUCCESS(res, res); |
|
1061 // Do nothing -- we didn't find a table cell |
|
1062 NS_ENSURE_TRUE(cellElement, NS_OK); |
|
1063 |
|
1064 // find enclosing table |
|
1065 nsCOMPtr<nsIDOMNode> tbl = GetEnclosingTable(cellElement); |
|
1066 NS_ENSURE_TRUE(tbl, res); |
|
1067 |
|
1068 // advance to next cell |
|
1069 // first create an iterator over the table |
|
1070 nsCOMPtr<nsIContentIterator> iter = |
|
1071 do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res); |
|
1072 NS_ENSURE_SUCCESS(res, res); |
|
1073 NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); |
|
1074 nsCOMPtr<nsIContent> cTbl = do_QueryInterface(tbl); |
|
1075 nsCOMPtr<nsIContent> cBlock = do_QueryInterface(cellElement); |
|
1076 res = iter->Init(cTbl); |
|
1077 NS_ENSURE_SUCCESS(res, res); |
|
1078 // position iter at block |
|
1079 res = iter->PositionAt(cBlock); |
|
1080 NS_ENSURE_SUCCESS(res, res); |
|
1081 |
|
1082 nsCOMPtr<nsIDOMNode> node; |
|
1083 do |
|
1084 { |
|
1085 if (inIsShift) |
|
1086 iter->Prev(); |
|
1087 else |
|
1088 iter->Next(); |
|
1089 |
|
1090 node = do_QueryInterface(iter->GetCurrentNode()); |
|
1091 |
|
1092 if (node && nsHTMLEditUtils::IsTableCell(node) && |
|
1093 GetEnclosingTable(node) == tbl) |
|
1094 { |
|
1095 res = CollapseSelectionToDeepestNonTableFirstChild(nullptr, node); |
|
1096 NS_ENSURE_SUCCESS(res, res); |
|
1097 *outHandled = true; |
|
1098 return NS_OK; |
|
1099 } |
|
1100 } while (!iter->IsDone()); |
|
1101 |
|
1102 if (!(*outHandled) && !inIsShift) |
|
1103 { |
|
1104 // if we havent handled it yet then we must have run off the end of |
|
1105 // the table. Insert a new row. |
|
1106 res = InsertTableRow(1, true); |
|
1107 NS_ENSURE_SUCCESS(res, res); |
|
1108 *outHandled = true; |
|
1109 // put selection in right place |
|
1110 // Use table code to get selection and index to new row... |
|
1111 nsCOMPtr<nsISelection>selection; |
|
1112 nsCOMPtr<nsIDOMElement> tblElement; |
|
1113 nsCOMPtr<nsIDOMElement> cell; |
|
1114 int32_t row; |
|
1115 res = GetCellContext(getter_AddRefs(selection), |
|
1116 getter_AddRefs(tblElement), |
|
1117 getter_AddRefs(cell), |
|
1118 nullptr, nullptr, |
|
1119 &row, nullptr); |
|
1120 NS_ENSURE_SUCCESS(res, res); |
|
1121 // ...so that we can ask for first cell in that row... |
|
1122 res = GetCellAt(tblElement, row, 0, getter_AddRefs(cell)); |
|
1123 NS_ENSURE_SUCCESS(res, res); |
|
1124 // ...and then set selection there. |
|
1125 // (Note that normally you should use CollapseSelectionToDeepestNonTableFirstChild(), |
|
1126 // but we know cell is an empty new cell, so this works fine) |
|
1127 node = do_QueryInterface(cell); |
|
1128 if (node) selection->Collapse(node,0); |
|
1129 return NS_OK; |
|
1130 } |
|
1131 |
|
1132 return res; |
|
1133 } |
|
1134 |
|
1135 NS_IMETHODIMP nsHTMLEditor::CreateBR(nsIDOMNode *aNode, int32_t aOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect) |
|
1136 { |
|
1137 nsCOMPtr<nsIDOMNode> parent = aNode; |
|
1138 int32_t offset = aOffset; |
|
1139 return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect); |
|
1140 } |
|
1141 |
|
1142 nsresult |
|
1143 nsHTMLEditor::CollapseSelectionToDeepestNonTableFirstChild(nsISelection *aSelection, nsIDOMNode *aNode) |
|
1144 { |
|
1145 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); |
|
1146 nsresult res; |
|
1147 |
|
1148 nsCOMPtr<nsISelection> selection; |
|
1149 if (aSelection) |
|
1150 { |
|
1151 selection = aSelection; |
|
1152 } else { |
|
1153 res = GetSelection(getter_AddRefs(selection)); |
|
1154 NS_ENSURE_SUCCESS(res, res); |
|
1155 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
|
1156 } |
|
1157 nsCOMPtr<nsIDOMNode> node = aNode; |
|
1158 nsCOMPtr<nsIDOMNode> child; |
|
1159 |
|
1160 do { |
|
1161 node->GetFirstChild(getter_AddRefs(child)); |
|
1162 |
|
1163 if (child) |
|
1164 { |
|
1165 // Stop if we find a table |
|
1166 // don't want to go into nested tables |
|
1167 if (nsHTMLEditUtils::IsTable(child)) break; |
|
1168 // hey, it'g gotta be a container too! |
|
1169 if (!IsContainer(child)) break; |
|
1170 node = child; |
|
1171 } |
|
1172 } |
|
1173 while (child); |
|
1174 |
|
1175 selection->Collapse(node,0); |
|
1176 return NS_OK; |
|
1177 } |
|
1178 |
|
1179 |
|
1180 // This is mostly like InsertHTMLWithCharsetAndContext, |
|
1181 // but we can't use that because it is selection-based and |
|
1182 // the rules code won't let us edit under the <head> node |
|
1183 NS_IMETHODIMP |
|
1184 nsHTMLEditor::ReplaceHeadContentsWithHTML(const nsAString& aSourceToInsert) |
|
1185 { |
|
1186 nsAutoRules beginRulesSniffing(this, EditAction::ignore, nsIEditor::eNone); // don't do any post processing, rules get confused |
|
1187 nsCOMPtr<nsISelection> selection; |
|
1188 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
1189 NS_ENSURE_SUCCESS(res, res); |
|
1190 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
1191 |
|
1192 ForceCompositionEnd(); |
|
1193 |
|
1194 // Do not use nsAutoRules -- rules code won't let us insert in <head> |
|
1195 // Use the head node as a parent and delete/insert directly |
|
1196 nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak); |
|
1197 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); |
|
1198 |
|
1199 nsCOMPtr<nsIDOMNodeList>nodeList; |
|
1200 res = doc->GetElementsByTagName(NS_LITERAL_STRING("head"), getter_AddRefs(nodeList)); |
|
1201 NS_ENSURE_SUCCESS(res, res); |
|
1202 NS_ENSURE_TRUE(nodeList, NS_ERROR_NULL_POINTER); |
|
1203 |
|
1204 uint32_t count; |
|
1205 nodeList->GetLength(&count); |
|
1206 if (count < 1) return NS_ERROR_FAILURE; |
|
1207 |
|
1208 nsCOMPtr<nsIDOMNode> headNode; |
|
1209 res = nodeList->Item(0, getter_AddRefs(headNode)); |
|
1210 NS_ENSURE_SUCCESS(res, res); |
|
1211 NS_ENSURE_TRUE(headNode, NS_ERROR_NULL_POINTER); |
|
1212 |
|
1213 // First, make sure there are no return chars in the source. |
|
1214 // Bad things happen if you insert returns (instead of dom newlines, \n) |
|
1215 // into an editor document. |
|
1216 nsAutoString inputString (aSourceToInsert); // hope this does copy-on-write |
|
1217 |
|
1218 // Windows linebreaks: Map CRLF to LF: |
|
1219 inputString.ReplaceSubstring(MOZ_UTF16("\r\n"), |
|
1220 MOZ_UTF16("\n")); |
|
1221 |
|
1222 // Mac linebreaks: Map any remaining CR to LF: |
|
1223 inputString.ReplaceSubstring(MOZ_UTF16("\r"), |
|
1224 MOZ_UTF16("\n")); |
|
1225 |
|
1226 nsAutoEditBatch beginBatching(this); |
|
1227 |
|
1228 res = GetSelection(getter_AddRefs(selection)); |
|
1229 NS_ENSURE_SUCCESS(res, res); |
|
1230 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
1231 |
|
1232 // Get the first range in the selection, for context: |
|
1233 nsCOMPtr<nsIDOMRange> range; |
|
1234 res = selection->GetRangeAt(0, getter_AddRefs(range)); |
|
1235 NS_ENSURE_SUCCESS(res, res); |
|
1236 |
|
1237 nsCOMPtr<nsIDOMDocumentFragment> docfrag; |
|
1238 res = range->CreateContextualFragment(inputString, |
|
1239 getter_AddRefs(docfrag)); |
|
1240 |
|
1241 //XXXX BUG 50965: This is not returning the text between <title> ... </title> |
|
1242 // Special code is needed in JS to handle title anyway, so it really doesn't matter! |
|
1243 |
|
1244 if (NS_FAILED(res)) |
|
1245 { |
|
1246 #ifdef DEBUG |
|
1247 printf("Couldn't create contextual fragment: error was %X\n", |
|
1248 static_cast<uint32_t>(res)); |
|
1249 #endif |
|
1250 return res; |
|
1251 } |
|
1252 NS_ENSURE_TRUE(docfrag, NS_ERROR_NULL_POINTER); |
|
1253 |
|
1254 nsCOMPtr<nsIDOMNode> child; |
|
1255 |
|
1256 // First delete all children in head |
|
1257 do { |
|
1258 res = headNode->GetFirstChild(getter_AddRefs(child)); |
|
1259 NS_ENSURE_SUCCESS(res, res); |
|
1260 if (child) |
|
1261 { |
|
1262 res = DeleteNode(child); |
|
1263 NS_ENSURE_SUCCESS(res, res); |
|
1264 } |
|
1265 } while (child); |
|
1266 |
|
1267 // Now insert the new nodes |
|
1268 int32_t offsetOfNewNode = 0; |
|
1269 nsCOMPtr<nsIDOMNode> fragmentAsNode (do_QueryInterface(docfrag)); |
|
1270 |
|
1271 // Loop over the contents of the fragment and move into the document |
|
1272 do { |
|
1273 res = fragmentAsNode->GetFirstChild(getter_AddRefs(child)); |
|
1274 NS_ENSURE_SUCCESS(res, res); |
|
1275 if (child) |
|
1276 { |
|
1277 res = InsertNode(child, headNode, offsetOfNewNode++); |
|
1278 NS_ENSURE_SUCCESS(res, res); |
|
1279 } |
|
1280 } while (child); |
|
1281 |
|
1282 return res; |
|
1283 } |
|
1284 |
|
1285 NS_IMETHODIMP |
|
1286 nsHTMLEditor::RebuildDocumentFromSource(const nsAString& aSourceString) |
|
1287 { |
|
1288 ForceCompositionEnd(); |
|
1289 |
|
1290 nsCOMPtr<nsISelection>selection; |
|
1291 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
1292 NS_ENSURE_SUCCESS(res, res); |
|
1293 |
|
1294 nsCOMPtr<nsIDOMElement> bodyElement = do_QueryInterface(GetRoot()); |
|
1295 NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER); |
|
1296 |
|
1297 // Find where the <body> tag starts. |
|
1298 nsReadingIterator<char16_t> beginbody; |
|
1299 nsReadingIterator<char16_t> endbody; |
|
1300 aSourceString.BeginReading(beginbody); |
|
1301 aSourceString.EndReading(endbody); |
|
1302 bool foundbody = CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<body"), |
|
1303 beginbody, endbody); |
|
1304 |
|
1305 nsReadingIterator<char16_t> beginhead; |
|
1306 nsReadingIterator<char16_t> endhead; |
|
1307 aSourceString.BeginReading(beginhead); |
|
1308 aSourceString.EndReading(endhead); |
|
1309 bool foundhead = CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<head"), |
|
1310 beginhead, endhead); |
|
1311 // a valid head appears before the body |
|
1312 if (foundbody && beginhead.get() > beginbody.get()) |
|
1313 foundhead = false; |
|
1314 |
|
1315 nsReadingIterator<char16_t> beginclosehead; |
|
1316 nsReadingIterator<char16_t> endclosehead; |
|
1317 aSourceString.BeginReading(beginclosehead); |
|
1318 aSourceString.EndReading(endclosehead); |
|
1319 |
|
1320 // Find the index after "<head>" |
|
1321 bool foundclosehead = CaseInsensitiveFindInReadable( |
|
1322 NS_LITERAL_STRING("</head>"), beginclosehead, endclosehead); |
|
1323 // a valid close head appears after a found head |
|
1324 if (foundhead && beginhead.get() > beginclosehead.get()) |
|
1325 foundclosehead = false; |
|
1326 // a valid close head appears before a found body |
|
1327 if (foundbody && beginclosehead.get() > beginbody.get()) |
|
1328 foundclosehead = false; |
|
1329 |
|
1330 // Time to change the document |
|
1331 nsAutoEditBatch beginBatching(this); |
|
1332 |
|
1333 nsReadingIterator<char16_t> endtotal; |
|
1334 aSourceString.EndReading(endtotal); |
|
1335 |
|
1336 if (foundhead) { |
|
1337 if (foundclosehead) |
|
1338 res = ReplaceHeadContentsWithHTML(Substring(beginhead, beginclosehead)); |
|
1339 else if (foundbody) |
|
1340 res = ReplaceHeadContentsWithHTML(Substring(beginhead, beginbody)); |
|
1341 else |
|
1342 // XXX Without recourse to some parser/content sink/docshell hackery |
|
1343 // we don't really know where the head ends and the body begins |
|
1344 // so we assume that there is no body |
|
1345 res = ReplaceHeadContentsWithHTML(Substring(beginhead, endtotal)); |
|
1346 } else { |
|
1347 nsReadingIterator<char16_t> begintotal; |
|
1348 aSourceString.BeginReading(begintotal); |
|
1349 NS_NAMED_LITERAL_STRING(head, "<head>"); |
|
1350 if (foundclosehead) |
|
1351 res = ReplaceHeadContentsWithHTML(head + Substring(begintotal, beginclosehead)); |
|
1352 else if (foundbody) |
|
1353 res = ReplaceHeadContentsWithHTML(head + Substring(begintotal, beginbody)); |
|
1354 else |
|
1355 // XXX Without recourse to some parser/content sink/docshell hackery |
|
1356 // we don't really know where the head ends and the body begins |
|
1357 // so we assume that there is no head |
|
1358 res = ReplaceHeadContentsWithHTML(head); |
|
1359 } |
|
1360 NS_ENSURE_SUCCESS(res, res); |
|
1361 |
|
1362 res = SelectAll(); |
|
1363 NS_ENSURE_SUCCESS(res, res); |
|
1364 |
|
1365 if (!foundbody) { |
|
1366 NS_NAMED_LITERAL_STRING(body, "<body>"); |
|
1367 // XXX Without recourse to some parser/content sink/docshell hackery |
|
1368 // we don't really know where the head ends and the body begins |
|
1369 if (foundclosehead) // assume body starts after the head ends |
|
1370 res = LoadHTML(body + Substring(endclosehead, endtotal)); |
|
1371 else if (foundhead) // assume there is no body |
|
1372 res = LoadHTML(body); |
|
1373 else // assume there is no head, the entire source is body |
|
1374 res = LoadHTML(body + aSourceString); |
|
1375 NS_ENSURE_SUCCESS(res, res); |
|
1376 |
|
1377 nsCOMPtr<nsIDOMElement> divElement; |
|
1378 res = CreateElementWithDefaults(NS_LITERAL_STRING("div"), getter_AddRefs(divElement)); |
|
1379 NS_ENSURE_SUCCESS(res, res); |
|
1380 |
|
1381 res = CloneAttributes(bodyElement, divElement); |
|
1382 NS_ENSURE_SUCCESS(res, res); |
|
1383 |
|
1384 return BeginningOfDocument(); |
|
1385 } |
|
1386 |
|
1387 res = LoadHTML(Substring(beginbody, endtotal)); |
|
1388 NS_ENSURE_SUCCESS(res, res); |
|
1389 |
|
1390 // Now we must copy attributes user might have edited on the <body> tag |
|
1391 // because InsertHTML (actually, CreateContextualFragment()) |
|
1392 // will never return a body node in the DOM fragment |
|
1393 |
|
1394 // We already know where "<body" begins |
|
1395 nsReadingIterator<char16_t> beginclosebody = beginbody; |
|
1396 nsReadingIterator<char16_t> endclosebody; |
|
1397 aSourceString.EndReading(endclosebody); |
|
1398 if (!FindInReadable(NS_LITERAL_STRING(">"),beginclosebody,endclosebody)) |
|
1399 return NS_ERROR_FAILURE; |
|
1400 |
|
1401 // Truncate at the end of the body tag |
|
1402 // Kludge of the year: fool the parser by replacing "body" with "div" so we get a node |
|
1403 nsAutoString bodyTag; |
|
1404 bodyTag.AssignLiteral("<div "); |
|
1405 bodyTag.Append(Substring(endbody, endclosebody)); |
|
1406 |
|
1407 nsCOMPtr<nsIDOMRange> range; |
|
1408 res = selection->GetRangeAt(0, getter_AddRefs(range)); |
|
1409 NS_ENSURE_SUCCESS(res, res); |
|
1410 |
|
1411 nsCOMPtr<nsIDOMDocumentFragment> docfrag; |
|
1412 res = range->CreateContextualFragment(bodyTag, getter_AddRefs(docfrag)); |
|
1413 NS_ENSURE_SUCCESS(res, res); |
|
1414 |
|
1415 nsCOMPtr<nsIDOMNode> fragmentAsNode (do_QueryInterface(docfrag)); |
|
1416 NS_ENSURE_TRUE(fragmentAsNode, NS_ERROR_NULL_POINTER); |
|
1417 |
|
1418 nsCOMPtr<nsIDOMNode> child; |
|
1419 res = fragmentAsNode->GetFirstChild(getter_AddRefs(child)); |
|
1420 NS_ENSURE_SUCCESS(res, res); |
|
1421 NS_ENSURE_TRUE(child, NS_ERROR_NULL_POINTER); |
|
1422 |
|
1423 // Copy all attributes from the div child to current body element |
|
1424 res = CloneAttributes(bodyElement, child); |
|
1425 NS_ENSURE_SUCCESS(res, res); |
|
1426 |
|
1427 // place selection at first editable content |
|
1428 return BeginningOfDocument(); |
|
1429 } |
|
1430 |
|
1431 void |
|
1432 nsHTMLEditor::NormalizeEOLInsertPosition(nsIDOMNode *firstNodeToInsert, |
|
1433 nsCOMPtr<nsIDOMNode> *insertParentNode, |
|
1434 int32_t *insertOffset) |
|
1435 { |
|
1436 /* |
|
1437 This function will either correct the position passed in, |
|
1438 or leave the position unchanged. |
|
1439 |
|
1440 When the (first) item to insert is a block level element, |
|
1441 and our insertion position is after the last visible item in a line, |
|
1442 i.e. the insertion position is just before a visible line break <br>, |
|
1443 we want to skip to the position just after the line break (see bug 68767) |
|
1444 |
|
1445 However, our logic to detect whether we should skip or not |
|
1446 needs to be more clever. |
|
1447 We must not skip when the caret appears to be positioned at the beginning |
|
1448 of a block, in that case skipping the <br> would not insert the <br> |
|
1449 at the caret position, but after the current empty line. |
|
1450 |
|
1451 So we have several cases to test: |
|
1452 |
|
1453 1) We only ever want to skip, if the next visible thing after the current position is a break |
|
1454 |
|
1455 2) We do not want to skip if there is no previous visible thing at all |
|
1456 That is detected if the call to PriorVisibleNode gives us an offset of zero. |
|
1457 Because PriorVisibleNode always positions after the prior node, we would |
|
1458 see an offset > 0, if there were a prior node. |
|
1459 |
|
1460 3) We do not want to skip, if both the next and the previous visible things are breaks. |
|
1461 |
|
1462 4) We do not want to skip if the previous visible thing is in a different block |
|
1463 than the insertion position. |
|
1464 */ |
|
1465 |
|
1466 if (!IsBlockNode(firstNodeToInsert)) |
|
1467 return; |
|
1468 |
|
1469 nsWSRunObject wsObj(this, *insertParentNode, *insertOffset); |
|
1470 nsCOMPtr<nsIDOMNode> nextVisNode; |
|
1471 nsCOMPtr<nsIDOMNode> prevVisNode; |
|
1472 int32_t nextVisOffset=0; |
|
1473 WSType nextVisType; |
|
1474 int32_t prevVisOffset=0; |
|
1475 WSType prevVisType; |
|
1476 |
|
1477 wsObj.NextVisibleNode(*insertParentNode, *insertOffset, address_of(nextVisNode), &nextVisOffset, &nextVisType); |
|
1478 if (!nextVisNode) |
|
1479 return; |
|
1480 |
|
1481 if (!(nextVisType & WSType::br)) { |
|
1482 return; |
|
1483 } |
|
1484 |
|
1485 wsObj.PriorVisibleNode(*insertParentNode, *insertOffset, address_of(prevVisNode), &prevVisOffset, &prevVisType); |
|
1486 if (!prevVisNode) |
|
1487 return; |
|
1488 |
|
1489 if (prevVisType & WSType::br) { |
|
1490 return; |
|
1491 } |
|
1492 |
|
1493 if (prevVisType & WSType::thisBlock) { |
|
1494 return; |
|
1495 } |
|
1496 |
|
1497 int32_t brOffset=0; |
|
1498 nsCOMPtr<nsIDOMNode> brNode = GetNodeLocation(nextVisNode, &brOffset); |
|
1499 |
|
1500 *insertParentNode = brNode; |
|
1501 *insertOffset = brOffset + 1; |
|
1502 } |
|
1503 |
|
1504 NS_IMETHODIMP |
|
1505 nsHTMLEditor::InsertElementAtSelection(nsIDOMElement* aElement, bool aDeleteSelection) |
|
1506 { |
|
1507 // Protect the edit rules object from dying |
|
1508 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
1509 |
|
1510 nsresult res = NS_ERROR_NOT_INITIALIZED; |
|
1511 |
|
1512 NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); |
|
1513 |
|
1514 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement); |
|
1515 |
|
1516 ForceCompositionEnd(); |
|
1517 nsAutoEditBatch beginBatching(this); |
|
1518 nsAutoRules beginRulesSniffing(this, EditAction::insertElement, nsIEditor::eNext); |
|
1519 |
|
1520 nsRefPtr<Selection> selection = GetSelection(); |
|
1521 if (!selection) { |
|
1522 return NS_ERROR_FAILURE; |
|
1523 } |
|
1524 |
|
1525 // hand off to the rules system, see if it has anything to say about this |
|
1526 bool cancel, handled; |
|
1527 nsTextRulesInfo ruleInfo(EditAction::insertElement); |
|
1528 ruleInfo.insertElement = aElement; |
|
1529 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); |
|
1530 if (cancel || (NS_FAILED(res))) return res; |
|
1531 |
|
1532 if (!handled) |
|
1533 { |
|
1534 if (aDeleteSelection) |
|
1535 { |
|
1536 if (!IsBlockNode(aElement)) { |
|
1537 // E.g., inserting an image. In this case we don't need to delete any |
|
1538 // inline wrappers before we do the insertion. Otherwise we let |
|
1539 // DeleteSelectionAndPrepareToCreateNode do the deletion for us, which |
|
1540 // calls DeleteSelection with aStripWrappers = eStrip. |
|
1541 res = DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip); |
|
1542 NS_ENSURE_SUCCESS(res, res); |
|
1543 } |
|
1544 |
|
1545 nsresult result = DeleteSelectionAndPrepareToCreateNode(); |
|
1546 NS_ENSURE_SUCCESS(result, result); |
|
1547 } |
|
1548 |
|
1549 // If deleting, selection will be collapsed. |
|
1550 // so if not, we collapse it |
|
1551 if (!aDeleteSelection) |
|
1552 { |
|
1553 // Named Anchor is a special case, |
|
1554 // We collapse to insert element BEFORE the selection |
|
1555 // For all other tags, we insert AFTER the selection |
|
1556 if (nsHTMLEditUtils::IsNamedAnchor(node)) |
|
1557 { |
|
1558 selection->CollapseToStart(); |
|
1559 } else { |
|
1560 selection->CollapseToEnd(); |
|
1561 } |
|
1562 } |
|
1563 |
|
1564 nsCOMPtr<nsIDOMNode> parentSelectedNode; |
|
1565 int32_t offsetForInsert; |
|
1566 res = selection->GetAnchorNode(getter_AddRefs(parentSelectedNode)); |
|
1567 // XXX: ERROR_HANDLING bad XPCOM usage |
|
1568 if (NS_SUCCEEDED(res) && NS_SUCCEEDED(selection->GetAnchorOffset(&offsetForInsert)) && parentSelectedNode) |
|
1569 { |
|
1570 #ifdef DEBUG_cmanske |
|
1571 { |
|
1572 nsAutoString name; |
|
1573 parentSelectedNode->GetNodeName(name); |
|
1574 printf("InsertElement: Anchor node of selection: "); |
|
1575 wprintf(name.get()); |
|
1576 printf(" Offset: %d\n", offsetForInsert); |
|
1577 } |
|
1578 #endif |
|
1579 |
|
1580 // Adjust position based on the node we are going to insert. |
|
1581 NormalizeEOLInsertPosition(node, address_of(parentSelectedNode), &offsetForInsert); |
|
1582 |
|
1583 res = InsertNodeAtPoint(node, address_of(parentSelectedNode), &offsetForInsert, false); |
|
1584 NS_ENSURE_SUCCESS(res, res); |
|
1585 // Set caret after element, but check for special case |
|
1586 // of inserting table-related elements: set in first cell instead |
|
1587 if (!SetCaretInTableCell(aElement)) |
|
1588 { |
|
1589 res = SetCaretAfterElement(aElement); |
|
1590 NS_ENSURE_SUCCESS(res, res); |
|
1591 } |
|
1592 // check for inserting a whole table at the end of a block. If so insert a br after it. |
|
1593 if (nsHTMLEditUtils::IsTable(node)) |
|
1594 { |
|
1595 bool isLast; |
|
1596 res = IsLastEditableChild(node, &isLast); |
|
1597 NS_ENSURE_SUCCESS(res, res); |
|
1598 if (isLast) |
|
1599 { |
|
1600 nsCOMPtr<nsIDOMNode> brNode; |
|
1601 res = CreateBR(parentSelectedNode, offsetForInsert+1, address_of(brNode)); |
|
1602 NS_ENSURE_SUCCESS(res, res); |
|
1603 selection->Collapse(parentSelectedNode, offsetForInsert+1); |
|
1604 } |
|
1605 } |
|
1606 } |
|
1607 } |
|
1608 res = mRules->DidDoAction(selection, &ruleInfo, res); |
|
1609 return res; |
|
1610 } |
|
1611 |
|
1612 |
|
1613 /* |
|
1614 InsertNodeAtPoint: attempts to insert aNode into the document, at a point specified by |
|
1615 {*ioParent,*ioOffset}. Checks with strict dtd to see if containment is allowed. If not |
|
1616 allowed, will attempt to find a parent in the parent hierarchy of *ioParent that will |
|
1617 accept aNode as a child. If such a parent is found, will split the document tree from |
|
1618 {*ioParent,*ioOffset} up to parent, and then insert aNode. ioParent & ioOffset are then |
|
1619 adjusted to point to the actual location that aNode was inserted at. aNoEmptyNodes |
|
1620 specifies if the splitting process is allowed to reslt in empty nodes. |
|
1621 nsIDOMNode *aNode node to insert |
|
1622 nsCOMPtr<nsIDOMNode> *ioParent insertion parent |
|
1623 int32_t *ioOffset insertion offset |
|
1624 bool aNoEmptyNodes splitting can result in empty nodes? |
|
1625 */ |
|
1626 nsresult |
|
1627 nsHTMLEditor::InsertNodeAtPoint(nsIDOMNode *aNode, |
|
1628 nsCOMPtr<nsIDOMNode> *ioParent, |
|
1629 int32_t *ioOffset, |
|
1630 bool aNoEmptyNodes) |
|
1631 { |
|
1632 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); |
|
1633 NS_ENSURE_TRUE(ioParent, NS_ERROR_NULL_POINTER); |
|
1634 NS_ENSURE_TRUE(*ioParent, NS_ERROR_NULL_POINTER); |
|
1635 NS_ENSURE_TRUE(ioOffset, NS_ERROR_NULL_POINTER); |
|
1636 |
|
1637 nsresult res = NS_OK; |
|
1638 nsCOMPtr<nsIDOMNode> parent = *ioParent; |
|
1639 nsCOMPtr<nsIDOMNode> topChild = *ioParent; |
|
1640 nsCOMPtr<nsIDOMNode> tmp; |
|
1641 int32_t offsetOfInsert = *ioOffset; |
|
1642 |
|
1643 // Search up the parent chain to find a suitable container |
|
1644 while (!CanContain(parent, aNode)) { |
|
1645 // If the current parent is a root (body or table element) |
|
1646 // then go no further - we can't insert |
|
1647 if (nsTextEditUtils::IsBody(parent) || nsHTMLEditUtils::IsTableElement(parent)) |
|
1648 return NS_ERROR_FAILURE; |
|
1649 // Get the next parent |
|
1650 parent->GetParentNode(getter_AddRefs(tmp)); |
|
1651 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); |
|
1652 topChild = parent; |
|
1653 parent = tmp; |
|
1654 } |
|
1655 if (parent != topChild) |
|
1656 { |
|
1657 // we need to split some levels above the original selection parent |
|
1658 res = SplitNodeDeep(topChild, *ioParent, *ioOffset, &offsetOfInsert, aNoEmptyNodes); |
|
1659 NS_ENSURE_SUCCESS(res, res); |
|
1660 *ioParent = parent; |
|
1661 *ioOffset = offsetOfInsert; |
|
1662 } |
|
1663 // Now we can insert the new node |
|
1664 res = InsertNode(aNode, parent, offsetOfInsert); |
|
1665 return res; |
|
1666 } |
|
1667 |
|
1668 NS_IMETHODIMP |
|
1669 nsHTMLEditor::SelectElement(nsIDOMElement* aElement) |
|
1670 { |
|
1671 nsresult res = NS_ERROR_NULL_POINTER; |
|
1672 |
|
1673 // Must be sure that element is contained in the document body |
|
1674 if (IsDescendantOfEditorRoot(aElement)) { |
|
1675 nsCOMPtr<nsISelection> selection; |
|
1676 res = GetSelection(getter_AddRefs(selection)); |
|
1677 NS_ENSURE_SUCCESS(res, res); |
|
1678 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
1679 nsCOMPtr<nsIDOMNode>parent; |
|
1680 res = aElement->GetParentNode(getter_AddRefs(parent)); |
|
1681 if (NS_SUCCEEDED(res) && parent) |
|
1682 { |
|
1683 int32_t offsetInParent = GetChildOffset(aElement, parent); |
|
1684 |
|
1685 // Collapse selection to just before desired element, |
|
1686 res = selection->Collapse(parent, offsetInParent); |
|
1687 if (NS_SUCCEEDED(res)) { |
|
1688 // then extend it to just after |
|
1689 res = selection->Extend(parent, offsetInParent + 1); |
|
1690 } |
|
1691 } |
|
1692 } |
|
1693 return res; |
|
1694 } |
|
1695 |
|
1696 NS_IMETHODIMP |
|
1697 nsHTMLEditor::SetCaretAfterElement(nsIDOMElement* aElement) |
|
1698 { |
|
1699 nsresult res = NS_ERROR_NULL_POINTER; |
|
1700 |
|
1701 // Be sure the element is contained in the document body |
|
1702 if (aElement && IsDescendantOfEditorRoot(aElement)) { |
|
1703 nsCOMPtr<nsISelection> selection; |
|
1704 res = GetSelection(getter_AddRefs(selection)); |
|
1705 NS_ENSURE_SUCCESS(res, res); |
|
1706 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
1707 nsCOMPtr<nsIDOMNode>parent; |
|
1708 res = aElement->GetParentNode(getter_AddRefs(parent)); |
|
1709 NS_ENSURE_SUCCESS(res, res); |
|
1710 NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); |
|
1711 int32_t offsetInParent = GetChildOffset(aElement, parent); |
|
1712 // Collapse selection to just after desired element, |
|
1713 res = selection->Collapse(parent, offsetInParent + 1); |
|
1714 } |
|
1715 return res; |
|
1716 } |
|
1717 |
|
1718 NS_IMETHODIMP |
|
1719 nsHTMLEditor::SetParagraphFormat(const nsAString& aParagraphFormat) |
|
1720 { |
|
1721 nsAutoString tag; tag.Assign(aParagraphFormat); |
|
1722 ToLowerCase(tag); |
|
1723 if (tag.EqualsLiteral("dd") || tag.EqualsLiteral("dt")) |
|
1724 return MakeDefinitionItem(tag); |
|
1725 else |
|
1726 return InsertBasicBlock(tag); |
|
1727 } |
|
1728 |
|
1729 NS_IMETHODIMP |
|
1730 nsHTMLEditor::GetParagraphState(bool *aMixed, nsAString &outFormat) |
|
1731 { |
|
1732 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } |
|
1733 NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER); |
|
1734 nsRefPtr<nsHTMLEditRules> htmlRules = static_cast<nsHTMLEditRules*>(mRules.get()); |
|
1735 |
|
1736 return htmlRules->GetParagraphState(aMixed, outFormat); |
|
1737 } |
|
1738 |
|
1739 NS_IMETHODIMP |
|
1740 nsHTMLEditor::GetBackgroundColorState(bool *aMixed, nsAString &aOutColor) |
|
1741 { |
|
1742 nsresult res; |
|
1743 if (IsCSSEnabled()) { |
|
1744 // if we are in CSS mode, we have to check if the containing block defines |
|
1745 // a background color |
|
1746 res = GetCSSBackgroundColorState(aMixed, aOutColor, true); |
|
1747 } |
|
1748 else { |
|
1749 // in HTML mode, we look only at page's background |
|
1750 res = GetHTMLBackgroundColorState(aMixed, aOutColor); |
|
1751 } |
|
1752 return res; |
|
1753 } |
|
1754 |
|
1755 NS_IMETHODIMP |
|
1756 nsHTMLEditor::GetHighlightColorState(bool *aMixed, nsAString &aOutColor) |
|
1757 { |
|
1758 nsresult res = NS_OK; |
|
1759 *aMixed = false; |
|
1760 aOutColor.AssignLiteral("transparent"); |
|
1761 if (IsCSSEnabled()) { |
|
1762 // in CSS mode, text background can be added by the Text Highlight button |
|
1763 // we need to query the background of the selection without looking for |
|
1764 // the block container of the ranges in the selection |
|
1765 res = GetCSSBackgroundColorState(aMixed, aOutColor, false); |
|
1766 } |
|
1767 return res; |
|
1768 } |
|
1769 |
|
1770 nsresult |
|
1771 nsHTMLEditor::GetCSSBackgroundColorState(bool *aMixed, nsAString &aOutColor, bool aBlockLevel) |
|
1772 { |
|
1773 NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER); |
|
1774 *aMixed = false; |
|
1775 // the default background color is transparent |
|
1776 aOutColor.AssignLiteral("transparent"); |
|
1777 |
|
1778 // get selection |
|
1779 nsCOMPtr<nsISelection>selection; |
|
1780 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
1781 NS_ENSURE_SUCCESS(res, res); |
|
1782 |
|
1783 // get selection location |
|
1784 nsCOMPtr<nsIDOMNode> parent; |
|
1785 int32_t offset; |
|
1786 res = GetStartNodeAndOffset(selection, getter_AddRefs(parent), &offset); |
|
1787 NS_ENSURE_SUCCESS(res, res); |
|
1788 NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); |
|
1789 |
|
1790 // is the selection collapsed? |
|
1791 nsCOMPtr<nsIDOMNode> nodeToExamine; |
|
1792 if (selection->Collapsed() || IsTextNode(parent)) { |
|
1793 // we want to look at the parent and ancestors |
|
1794 nodeToExamine = parent; |
|
1795 } else { |
|
1796 // otherwise we want to look at the first editable node after |
|
1797 // {parent,offset} and its ancestors for divs with alignment on them |
|
1798 nodeToExamine = GetChildAt(parent, offset); |
|
1799 //GetNextNode(parent, offset, true, address_of(nodeToExamine)); |
|
1800 } |
|
1801 |
|
1802 NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER); |
|
1803 |
|
1804 // is the node to examine a block ? |
|
1805 bool isBlock; |
|
1806 res = NodeIsBlockStatic(nodeToExamine, &isBlock); |
|
1807 NS_ENSURE_SUCCESS(res, res); |
|
1808 |
|
1809 nsCOMPtr<nsIDOMNode> tmp; |
|
1810 |
|
1811 if (aBlockLevel) { |
|
1812 // we are querying the block background (and not the text background), let's |
|
1813 // climb to the block container |
|
1814 nsCOMPtr<nsIDOMNode> blockParent = nodeToExamine; |
|
1815 if (!isBlock) { |
|
1816 blockParent = GetBlockNodeParent(nodeToExamine); |
|
1817 NS_ENSURE_TRUE(blockParent, NS_OK); |
|
1818 } |
|
1819 |
|
1820 // Make sure to not walk off onto the Document node |
|
1821 nsCOMPtr<nsIDOMElement> element; |
|
1822 do { |
|
1823 // retrieve the computed style of background-color for blockParent |
|
1824 mHTMLCSSUtils->GetComputedProperty(blockParent, |
|
1825 nsEditProperty::cssBackgroundColor, |
|
1826 aOutColor); |
|
1827 tmp.swap(blockParent); |
|
1828 res = tmp->GetParentNode(getter_AddRefs(blockParent)); |
|
1829 element = do_QueryInterface(blockParent); |
|
1830 // look at parent if the queried color is transparent and if the node to |
|
1831 // examine is not the root of the document |
|
1832 } while (aOutColor.EqualsLiteral("transparent") && element); |
|
1833 if (aOutColor.EqualsLiteral("transparent")) { |
|
1834 // we have hit the root of the document and the color is still transparent ! |
|
1835 // Grumble... Let's look at the default background color because that's the |
|
1836 // color we are looking for |
|
1837 mHTMLCSSUtils->GetDefaultBackgroundColor(aOutColor); |
|
1838 } |
|
1839 } |
|
1840 else { |
|
1841 // no, we are querying the text background for the Text Highlight button |
|
1842 if (IsTextNode(nodeToExamine)) { |
|
1843 // if the node of interest is a text node, let's climb a level |
|
1844 res = nodeToExamine->GetParentNode(getter_AddRefs(parent)); |
|
1845 NS_ENSURE_SUCCESS(res, res); |
|
1846 nodeToExamine = parent; |
|
1847 } |
|
1848 do { |
|
1849 // is the node to examine a block ? |
|
1850 res = NodeIsBlockStatic(nodeToExamine, &isBlock); |
|
1851 NS_ENSURE_SUCCESS(res, res); |
|
1852 if (isBlock) { |
|
1853 // yes it is a block; in that case, the text background color is transparent |
|
1854 aOutColor.AssignLiteral("transparent"); |
|
1855 break; |
|
1856 } |
|
1857 else { |
|
1858 // no, it's not; let's retrieve the computed style of background-color for the |
|
1859 // node to examine |
|
1860 mHTMLCSSUtils->GetComputedProperty(nodeToExamine, nsEditProperty::cssBackgroundColor, |
|
1861 aOutColor); |
|
1862 if (!aOutColor.EqualsLiteral("transparent")) { |
|
1863 break; |
|
1864 } |
|
1865 } |
|
1866 tmp.swap(nodeToExamine); |
|
1867 res = tmp->GetParentNode(getter_AddRefs(nodeToExamine)); |
|
1868 NS_ENSURE_SUCCESS(res, res); |
|
1869 } while ( aOutColor.EqualsLiteral("transparent") && nodeToExamine ); |
|
1870 } |
|
1871 return NS_OK; |
|
1872 } |
|
1873 |
|
1874 NS_IMETHODIMP |
|
1875 nsHTMLEditor::GetHTMLBackgroundColorState(bool *aMixed, nsAString &aOutColor) |
|
1876 { |
|
1877 //TODO: We don't handle "mixed" correctly! |
|
1878 NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER); |
|
1879 *aMixed = false; |
|
1880 aOutColor.Truncate(); |
|
1881 |
|
1882 nsCOMPtr<nsIDOMElement> domElement; |
|
1883 int32_t selectedCount; |
|
1884 nsAutoString tagName; |
|
1885 nsresult res = GetSelectedOrParentTableElement(tagName, |
|
1886 &selectedCount, |
|
1887 getter_AddRefs(domElement)); |
|
1888 NS_ENSURE_SUCCESS(res, res); |
|
1889 |
|
1890 nsCOMPtr<dom::Element> element = do_QueryInterface(domElement); |
|
1891 |
|
1892 while (element) { |
|
1893 // We are in a cell or selected table |
|
1894 element->GetAttr(kNameSpaceID_None, nsGkAtoms::bgcolor, aOutColor); |
|
1895 |
|
1896 // Done if we have a color explicitly set |
|
1897 if (!aOutColor.IsEmpty()) { |
|
1898 return NS_OK; |
|
1899 } |
|
1900 |
|
1901 // Once we hit the body, we're done |
|
1902 if (element->IsHTML(nsGkAtoms::body)) { |
|
1903 return NS_OK; |
|
1904 } |
|
1905 |
|
1906 // No color is set, but we need to report visible color inherited |
|
1907 // from nested cells/tables, so search up parent chain |
|
1908 element = element->GetParentElement(); |
|
1909 } |
|
1910 |
|
1911 // If no table or cell found, get page body |
|
1912 dom::Element* bodyElement = GetRoot(); |
|
1913 NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER); |
|
1914 |
|
1915 bodyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::bgcolor, aOutColor); |
|
1916 return NS_OK; |
|
1917 } |
|
1918 |
|
1919 NS_IMETHODIMP |
|
1920 nsHTMLEditor::GetListState(bool *aMixed, bool *aOL, bool *aUL, bool *aDL) |
|
1921 { |
|
1922 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } |
|
1923 NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER); |
|
1924 nsRefPtr<nsHTMLEditRules> htmlRules = static_cast<nsHTMLEditRules*>(mRules.get()); |
|
1925 |
|
1926 return htmlRules->GetListState(aMixed, aOL, aUL, aDL); |
|
1927 } |
|
1928 |
|
1929 NS_IMETHODIMP |
|
1930 nsHTMLEditor::GetListItemState(bool *aMixed, bool *aLI, bool *aDT, bool *aDD) |
|
1931 { |
|
1932 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } |
|
1933 NS_ENSURE_TRUE(aMixed && aLI && aDT && aDD, NS_ERROR_NULL_POINTER); |
|
1934 |
|
1935 nsRefPtr<nsHTMLEditRules> htmlRules = static_cast<nsHTMLEditRules*>(mRules.get()); |
|
1936 |
|
1937 return htmlRules->GetListItemState(aMixed, aLI, aDT, aDD); |
|
1938 } |
|
1939 |
|
1940 NS_IMETHODIMP |
|
1941 nsHTMLEditor::GetAlignment(bool *aMixed, nsIHTMLEditor::EAlignment *aAlign) |
|
1942 { |
|
1943 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } |
|
1944 NS_ENSURE_TRUE(aMixed && aAlign, NS_ERROR_NULL_POINTER); |
|
1945 nsRefPtr<nsHTMLEditRules> htmlRules = static_cast<nsHTMLEditRules*>(mRules.get()); |
|
1946 |
|
1947 return htmlRules->GetAlignment(aMixed, aAlign); |
|
1948 } |
|
1949 |
|
1950 |
|
1951 NS_IMETHODIMP |
|
1952 nsHTMLEditor::GetIndentState(bool *aCanIndent, bool *aCanOutdent) |
|
1953 { |
|
1954 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } |
|
1955 NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_NULL_POINTER); |
|
1956 |
|
1957 nsRefPtr<nsHTMLEditRules> htmlRules = static_cast<nsHTMLEditRules*>(mRules.get()); |
|
1958 |
|
1959 return htmlRules->GetIndentState(aCanIndent, aCanOutdent); |
|
1960 } |
|
1961 |
|
1962 NS_IMETHODIMP |
|
1963 nsHTMLEditor::MakeOrChangeList(const nsAString& aListType, bool entireList, const nsAString& aBulletType) |
|
1964 { |
|
1965 nsresult res; |
|
1966 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } |
|
1967 |
|
1968 // Protect the edit rules object from dying |
|
1969 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
1970 |
|
1971 bool cancel, handled; |
|
1972 |
|
1973 nsAutoEditBatch beginBatching(this); |
|
1974 nsAutoRules beginRulesSniffing(this, EditAction::makeList, nsIEditor::eNext); |
|
1975 |
|
1976 // pre-process |
|
1977 nsRefPtr<Selection> selection = GetSelection(); |
|
1978 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
1979 |
|
1980 nsTextRulesInfo ruleInfo(EditAction::makeList); |
|
1981 ruleInfo.blockType = &aListType; |
|
1982 ruleInfo.entireList = entireList; |
|
1983 ruleInfo.bulletType = &aBulletType; |
|
1984 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); |
|
1985 if (cancel || (NS_FAILED(res))) return res; |
|
1986 |
|
1987 if (!handled) |
|
1988 { |
|
1989 // Find out if the selection is collapsed: |
|
1990 bool isCollapsed = selection->Collapsed(); |
|
1991 |
|
1992 nsCOMPtr<nsIDOMNode> node; |
|
1993 int32_t offset; |
|
1994 res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); |
|
1995 if (!node) res = NS_ERROR_FAILURE; |
|
1996 NS_ENSURE_SUCCESS(res, res); |
|
1997 |
|
1998 if (isCollapsed) |
|
1999 { |
|
2000 // have to find a place to put the list |
|
2001 nsCOMPtr<nsIDOMNode> parent = node; |
|
2002 nsCOMPtr<nsIDOMNode> topChild = node; |
|
2003 nsCOMPtr<nsIDOMNode> tmp; |
|
2004 |
|
2005 nsCOMPtr<nsIAtom> listAtom = do_GetAtom(aListType); |
|
2006 while (!CanContainTag(parent, listAtom)) { |
|
2007 parent->GetParentNode(getter_AddRefs(tmp)); |
|
2008 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); |
|
2009 topChild = parent; |
|
2010 parent = tmp; |
|
2011 } |
|
2012 |
|
2013 if (parent != node) |
|
2014 { |
|
2015 // we need to split up to the child of parent |
|
2016 res = SplitNodeDeep(topChild, node, offset, &offset); |
|
2017 NS_ENSURE_SUCCESS(res, res); |
|
2018 } |
|
2019 |
|
2020 // make a list |
|
2021 nsCOMPtr<nsIDOMNode> newList; |
|
2022 res = CreateNode(aListType, parent, offset, getter_AddRefs(newList)); |
|
2023 NS_ENSURE_SUCCESS(res, res); |
|
2024 // make a list item |
|
2025 nsCOMPtr<nsIDOMNode> newItem; |
|
2026 res = CreateNode(NS_LITERAL_STRING("li"), newList, 0, getter_AddRefs(newItem)); |
|
2027 NS_ENSURE_SUCCESS(res, res); |
|
2028 res = selection->Collapse(newItem,0); |
|
2029 NS_ENSURE_SUCCESS(res, res); |
|
2030 } |
|
2031 } |
|
2032 |
|
2033 res = mRules->DidDoAction(selection, &ruleInfo, res); |
|
2034 return res; |
|
2035 } |
|
2036 |
|
2037 |
|
2038 NS_IMETHODIMP |
|
2039 nsHTMLEditor::RemoveList(const nsAString& aListType) |
|
2040 { |
|
2041 nsresult res; |
|
2042 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } |
|
2043 |
|
2044 // Protect the edit rules object from dying |
|
2045 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
2046 |
|
2047 bool cancel, handled; |
|
2048 |
|
2049 nsAutoEditBatch beginBatching(this); |
|
2050 nsAutoRules beginRulesSniffing(this, EditAction::removeList, nsIEditor::eNext); |
|
2051 |
|
2052 // pre-process |
|
2053 nsRefPtr<Selection> selection = GetSelection(); |
|
2054 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
2055 |
|
2056 nsTextRulesInfo ruleInfo(EditAction::removeList); |
|
2057 if (aListType.LowerCaseEqualsLiteral("ol")) |
|
2058 ruleInfo.bOrdered = true; |
|
2059 else ruleInfo.bOrdered = false; |
|
2060 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); |
|
2061 if (cancel || (NS_FAILED(res))) return res; |
|
2062 |
|
2063 // no default behavior for this yet. what would it mean? |
|
2064 |
|
2065 res = mRules->DidDoAction(selection, &ruleInfo, res); |
|
2066 return res; |
|
2067 } |
|
2068 |
|
2069 nsresult |
|
2070 nsHTMLEditor::MakeDefinitionItem(const nsAString& aItemType) |
|
2071 { |
|
2072 nsresult res; |
|
2073 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } |
|
2074 |
|
2075 // Protect the edit rules object from dying |
|
2076 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
2077 |
|
2078 bool cancel, handled; |
|
2079 |
|
2080 nsAutoEditBatch beginBatching(this); |
|
2081 nsAutoRules beginRulesSniffing(this, EditAction::makeDefListItem, nsIEditor::eNext); |
|
2082 |
|
2083 // pre-process |
|
2084 nsRefPtr<Selection> selection = GetSelection(); |
|
2085 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
2086 nsTextRulesInfo ruleInfo(EditAction::makeDefListItem); |
|
2087 ruleInfo.blockType = &aItemType; |
|
2088 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); |
|
2089 if (cancel || (NS_FAILED(res))) return res; |
|
2090 |
|
2091 if (!handled) |
|
2092 { |
|
2093 // todo: no default for now. we count on rules to handle it. |
|
2094 } |
|
2095 |
|
2096 res = mRules->DidDoAction(selection, &ruleInfo, res); |
|
2097 return res; |
|
2098 } |
|
2099 |
|
2100 nsresult |
|
2101 nsHTMLEditor::InsertBasicBlock(const nsAString& aBlockType) |
|
2102 { |
|
2103 nsresult res; |
|
2104 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } |
|
2105 |
|
2106 // Protect the edit rules object from dying |
|
2107 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
2108 |
|
2109 bool cancel, handled; |
|
2110 |
|
2111 nsAutoEditBatch beginBatching(this); |
|
2112 nsAutoRules beginRulesSniffing(this, EditAction::makeBasicBlock, nsIEditor::eNext); |
|
2113 |
|
2114 // pre-process |
|
2115 nsRefPtr<Selection> selection = GetSelection(); |
|
2116 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
2117 nsTextRulesInfo ruleInfo(EditAction::makeBasicBlock); |
|
2118 ruleInfo.blockType = &aBlockType; |
|
2119 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); |
|
2120 if (cancel || (NS_FAILED(res))) return res; |
|
2121 |
|
2122 if (!handled) |
|
2123 { |
|
2124 // Find out if the selection is collapsed: |
|
2125 bool isCollapsed = selection->Collapsed(); |
|
2126 |
|
2127 nsCOMPtr<nsIDOMNode> node; |
|
2128 int32_t offset; |
|
2129 res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); |
|
2130 if (!node) res = NS_ERROR_FAILURE; |
|
2131 NS_ENSURE_SUCCESS(res, res); |
|
2132 |
|
2133 if (isCollapsed) |
|
2134 { |
|
2135 // have to find a place to put the block |
|
2136 nsCOMPtr<nsIDOMNode> parent = node; |
|
2137 nsCOMPtr<nsIDOMNode> topChild = node; |
|
2138 nsCOMPtr<nsIDOMNode> tmp; |
|
2139 |
|
2140 nsCOMPtr<nsIAtom> blockAtom = do_GetAtom(aBlockType); |
|
2141 while (!CanContainTag(parent, blockAtom)) { |
|
2142 parent->GetParentNode(getter_AddRefs(tmp)); |
|
2143 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); |
|
2144 topChild = parent; |
|
2145 parent = tmp; |
|
2146 } |
|
2147 |
|
2148 if (parent != node) |
|
2149 { |
|
2150 // we need to split up to the child of parent |
|
2151 res = SplitNodeDeep(topChild, node, offset, &offset); |
|
2152 NS_ENSURE_SUCCESS(res, res); |
|
2153 } |
|
2154 |
|
2155 // make a block |
|
2156 nsCOMPtr<nsIDOMNode> newBlock; |
|
2157 res = CreateNode(aBlockType, parent, offset, getter_AddRefs(newBlock)); |
|
2158 NS_ENSURE_SUCCESS(res, res); |
|
2159 |
|
2160 // reposition selection to inside the block |
|
2161 res = selection->Collapse(newBlock,0); |
|
2162 NS_ENSURE_SUCCESS(res, res); |
|
2163 } |
|
2164 } |
|
2165 |
|
2166 res = mRules->DidDoAction(selection, &ruleInfo, res); |
|
2167 return res; |
|
2168 } |
|
2169 |
|
2170 NS_IMETHODIMP |
|
2171 nsHTMLEditor::Indent(const nsAString& aIndent) |
|
2172 { |
|
2173 nsresult res; |
|
2174 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } |
|
2175 |
|
2176 // Protect the edit rules object from dying |
|
2177 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
2178 |
|
2179 bool cancel, handled; |
|
2180 EditAction opID = EditAction::indent; |
|
2181 if (aIndent.LowerCaseEqualsLiteral("outdent")) |
|
2182 { |
|
2183 opID = EditAction::outdent; |
|
2184 } |
|
2185 nsAutoEditBatch beginBatching(this); |
|
2186 nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext); |
|
2187 |
|
2188 // pre-process |
|
2189 nsRefPtr<Selection> selection = GetSelection(); |
|
2190 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
2191 |
|
2192 nsTextRulesInfo ruleInfo(opID); |
|
2193 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); |
|
2194 if (cancel || (NS_FAILED(res))) return res; |
|
2195 |
|
2196 if (!handled) |
|
2197 { |
|
2198 // Do default - insert a blockquote node if selection collapsed |
|
2199 nsCOMPtr<nsIDOMNode> node; |
|
2200 int32_t offset; |
|
2201 bool isCollapsed = selection->Collapsed(); |
|
2202 |
|
2203 res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); |
|
2204 if (!node) res = NS_ERROR_FAILURE; |
|
2205 NS_ENSURE_SUCCESS(res, res); |
|
2206 |
|
2207 if (aIndent.EqualsLiteral("indent")) |
|
2208 { |
|
2209 if (isCollapsed) |
|
2210 { |
|
2211 // have to find a place to put the blockquote |
|
2212 nsCOMPtr<nsIDOMNode> parent = node; |
|
2213 nsCOMPtr<nsIDOMNode> topChild = node; |
|
2214 nsCOMPtr<nsIDOMNode> tmp; |
|
2215 while (!CanContainTag(parent, nsGkAtoms::blockquote)) { |
|
2216 parent->GetParentNode(getter_AddRefs(tmp)); |
|
2217 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); |
|
2218 topChild = parent; |
|
2219 parent = tmp; |
|
2220 } |
|
2221 |
|
2222 if (parent != node) |
|
2223 { |
|
2224 // we need to split up to the child of parent |
|
2225 res = SplitNodeDeep(topChild, node, offset, &offset); |
|
2226 NS_ENSURE_SUCCESS(res, res); |
|
2227 } |
|
2228 |
|
2229 // make a blockquote |
|
2230 nsCOMPtr<nsIDOMNode> newBQ; |
|
2231 res = CreateNode(NS_LITERAL_STRING("blockquote"), parent, offset, |
|
2232 getter_AddRefs(newBQ)); |
|
2233 NS_ENSURE_SUCCESS(res, res); |
|
2234 // put a space in it so layout will draw the list item |
|
2235 res = selection->Collapse(newBQ,0); |
|
2236 NS_ENSURE_SUCCESS(res, res); |
|
2237 res = InsertText(NS_LITERAL_STRING(" ")); |
|
2238 NS_ENSURE_SUCCESS(res, res); |
|
2239 // reposition selection to before the space character |
|
2240 res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); |
|
2241 NS_ENSURE_SUCCESS(res, res); |
|
2242 res = selection->Collapse(node,0); |
|
2243 NS_ENSURE_SUCCESS(res, res); |
|
2244 } |
|
2245 } |
|
2246 } |
|
2247 res = mRules->DidDoAction(selection, &ruleInfo, res); |
|
2248 return res; |
|
2249 } |
|
2250 |
|
2251 //TODO: IMPLEMENT ALIGNMENT! |
|
2252 |
|
2253 NS_IMETHODIMP |
|
2254 nsHTMLEditor::Align(const nsAString& aAlignType) |
|
2255 { |
|
2256 // Protect the edit rules object from dying |
|
2257 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
2258 |
|
2259 nsAutoEditBatch beginBatching(this); |
|
2260 nsAutoRules beginRulesSniffing(this, EditAction::align, nsIEditor::eNext); |
|
2261 |
|
2262 nsCOMPtr<nsIDOMNode> node; |
|
2263 bool cancel, handled; |
|
2264 |
|
2265 // Find out if the selection is collapsed: |
|
2266 nsRefPtr<Selection> selection = GetSelection(); |
|
2267 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
2268 nsTextRulesInfo ruleInfo(EditAction::align); |
|
2269 ruleInfo.alignType = &aAlignType; |
|
2270 nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); |
|
2271 if (cancel || NS_FAILED(res)) |
|
2272 return res; |
|
2273 |
|
2274 res = mRules->DidDoAction(selection, &ruleInfo, res); |
|
2275 return res; |
|
2276 } |
|
2277 |
|
2278 NS_IMETHODIMP |
|
2279 nsHTMLEditor::GetElementOrParentByTagName(const nsAString& aTagName, nsIDOMNode *aNode, nsIDOMElement** aReturn) |
|
2280 { |
|
2281 NS_ENSURE_TRUE(!aTagName.IsEmpty(), NS_ERROR_NULL_POINTER); |
|
2282 NS_ENSURE_TRUE(aReturn, NS_ERROR_NULL_POINTER); |
|
2283 |
|
2284 nsCOMPtr<nsINode> current = do_QueryInterface(aNode); |
|
2285 if (!current) { |
|
2286 // If no node supplied, get it from anchor node of current selection |
|
2287 nsRefPtr<Selection> selection = GetSelection(); |
|
2288 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
2289 |
|
2290 nsCOMPtr<nsINode> anchorNode = selection->GetAnchorNode(); |
|
2291 NS_ENSURE_TRUE(anchorNode, NS_ERROR_FAILURE); |
|
2292 |
|
2293 // Try to get the actual selected node |
|
2294 if (anchorNode->HasChildNodes() && anchorNode->IsContent()) { |
|
2295 uint32_t offset = selection->AnchorOffset(); |
|
2296 current = anchorNode->GetChildAt(offset); |
|
2297 } |
|
2298 // anchor node is probably a text node - just use that |
|
2299 if (!current) { |
|
2300 current = anchorNode; |
|
2301 } |
|
2302 } |
|
2303 |
|
2304 nsCOMPtr<nsIDOMNode> currentNode = current->AsDOMNode(); |
|
2305 |
|
2306 nsAutoString TagName(aTagName); |
|
2307 ToLowerCase(TagName); |
|
2308 bool getLink = IsLinkTag(TagName); |
|
2309 bool getNamedAnchor = IsNamedAnchorTag(TagName); |
|
2310 if ( getLink || getNamedAnchor) |
|
2311 { |
|
2312 TagName.AssignLiteral("a"); |
|
2313 } |
|
2314 bool findTableCell = TagName.EqualsLiteral("td"); |
|
2315 bool findList = TagName.EqualsLiteral("list"); |
|
2316 |
|
2317 // default is null - no element found |
|
2318 *aReturn = nullptr; |
|
2319 |
|
2320 nsCOMPtr<nsIDOMNode> parent; |
|
2321 bool bNodeFound = false; |
|
2322 |
|
2323 while (true) |
|
2324 { |
|
2325 nsAutoString currentTagName; |
|
2326 // Test if we have a link (an anchor with href set) |
|
2327 if ( (getLink && nsHTMLEditUtils::IsLink(currentNode)) || |
|
2328 (getNamedAnchor && nsHTMLEditUtils::IsNamedAnchor(currentNode)) ) |
|
2329 { |
|
2330 bNodeFound = true; |
|
2331 break; |
|
2332 } else { |
|
2333 if (findList) |
|
2334 { |
|
2335 // Match "ol", "ul", or "dl" for lists |
|
2336 if (nsHTMLEditUtils::IsList(currentNode)) |
|
2337 goto NODE_FOUND; |
|
2338 |
|
2339 } else if (findTableCell) |
|
2340 { |
|
2341 // Table cells are another special case: |
|
2342 // Match either "td" or "th" for them |
|
2343 if (nsHTMLEditUtils::IsTableCell(currentNode)) |
|
2344 goto NODE_FOUND; |
|
2345 |
|
2346 } else { |
|
2347 currentNode->GetNodeName(currentTagName); |
|
2348 if (currentTagName.Equals(TagName, nsCaseInsensitiveStringComparator())) |
|
2349 { |
|
2350 NODE_FOUND: |
|
2351 bNodeFound = true; |
|
2352 break; |
|
2353 } |
|
2354 } |
|
2355 } |
|
2356 // Search up the parent chain |
|
2357 // We should never fail because of root test below, but lets be safe |
|
2358 // XXX: ERROR_HANDLING error return code lost |
|
2359 if (NS_FAILED(currentNode->GetParentNode(getter_AddRefs(parent))) || !parent) |
|
2360 break; |
|
2361 |
|
2362 // Stop searching if parent is a body tag |
|
2363 nsAutoString parentTagName; |
|
2364 parent->GetNodeName(parentTagName); |
|
2365 // Note: Originally used IsRoot to stop at table cells, |
|
2366 // but that's too messy when you are trying to find the parent table |
|
2367 if(parentTagName.LowerCaseEqualsLiteral("body")) |
|
2368 break; |
|
2369 |
|
2370 currentNode = parent; |
|
2371 } |
|
2372 |
|
2373 if (!bNodeFound) { |
|
2374 return NS_EDITOR_ELEMENT_NOT_FOUND; |
|
2375 } |
|
2376 |
|
2377 nsCOMPtr<nsIDOMElement> currentElement = do_QueryInterface(currentNode); |
|
2378 currentElement.forget(aReturn); |
|
2379 return NS_OK; |
|
2380 } |
|
2381 |
|
2382 NS_IMETHODIMP |
|
2383 nsHTMLEditor::GetSelectedElement(const nsAString& aTagName, nsIDOMElement** aReturn) |
|
2384 { |
|
2385 NS_ENSURE_TRUE(aReturn , NS_ERROR_NULL_POINTER); |
|
2386 |
|
2387 // default is null - no element found |
|
2388 *aReturn = nullptr; |
|
2389 |
|
2390 // First look for a single element in selection |
|
2391 nsCOMPtr<nsISelection>selection; |
|
2392 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
2393 NS_ENSURE_SUCCESS(res, res); |
|
2394 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
2395 Selection* sel = static_cast<Selection*>(selection.get()); |
|
2396 |
|
2397 bool bNodeFound = false; |
|
2398 bool isCollapsed = selection->Collapsed(); |
|
2399 |
|
2400 nsAutoString domTagName; |
|
2401 nsAutoString TagName(aTagName); |
|
2402 ToLowerCase(TagName); |
|
2403 // Empty string indicates we should match any element tag |
|
2404 bool anyTag = (TagName.IsEmpty()); |
|
2405 bool isLinkTag = IsLinkTag(TagName); |
|
2406 bool isNamedAnchorTag = IsNamedAnchorTag(TagName); |
|
2407 |
|
2408 nsCOMPtr<nsIDOMElement> selectedElement; |
|
2409 nsCOMPtr<nsIDOMRange> range; |
|
2410 res = selection->GetRangeAt(0, getter_AddRefs(range)); |
|
2411 NS_ENSURE_SUCCESS(res, res); |
|
2412 |
|
2413 nsCOMPtr<nsIDOMNode> startParent; |
|
2414 int32_t startOffset, endOffset; |
|
2415 res = range->GetStartContainer(getter_AddRefs(startParent)); |
|
2416 NS_ENSURE_SUCCESS(res, res); |
|
2417 res = range->GetStartOffset(&startOffset); |
|
2418 NS_ENSURE_SUCCESS(res, res); |
|
2419 |
|
2420 nsCOMPtr<nsIDOMNode> endParent; |
|
2421 res = range->GetEndContainer(getter_AddRefs(endParent)); |
|
2422 NS_ENSURE_SUCCESS(res, res); |
|
2423 res = range->GetEndOffset(&endOffset); |
|
2424 NS_ENSURE_SUCCESS(res, res); |
|
2425 |
|
2426 // Optimization for a single selected element |
|
2427 if (startParent && startParent == endParent && (endOffset-startOffset) == 1) |
|
2428 { |
|
2429 nsCOMPtr<nsIDOMNode> selectedNode = GetChildAt(startParent, startOffset); |
|
2430 NS_ENSURE_SUCCESS(res, NS_OK); |
|
2431 if (selectedNode) |
|
2432 { |
|
2433 selectedNode->GetNodeName(domTagName); |
|
2434 ToLowerCase(domTagName); |
|
2435 |
|
2436 // Test for appropriate node type requested |
|
2437 if (anyTag || (TagName == domTagName) || |
|
2438 (isLinkTag && nsHTMLEditUtils::IsLink(selectedNode)) || |
|
2439 (isNamedAnchorTag && nsHTMLEditUtils::IsNamedAnchor(selectedNode))) |
|
2440 { |
|
2441 bNodeFound = true; |
|
2442 selectedElement = do_QueryInterface(selectedNode); |
|
2443 } |
|
2444 } |
|
2445 } |
|
2446 |
|
2447 if (!bNodeFound) |
|
2448 { |
|
2449 if (isLinkTag) |
|
2450 { |
|
2451 // Link tag is a special case - we return the anchor node |
|
2452 // found for any selection that is totally within a link, |
|
2453 // included a collapsed selection (just a caret in a link) |
|
2454 nsCOMPtr<nsIDOMNode> anchorNode; |
|
2455 res = selection->GetAnchorNode(getter_AddRefs(anchorNode)); |
|
2456 NS_ENSURE_SUCCESS(res, res); |
|
2457 int32_t anchorOffset = -1; |
|
2458 if (anchorNode) |
|
2459 selection->GetAnchorOffset(&anchorOffset); |
|
2460 |
|
2461 nsCOMPtr<nsIDOMNode> focusNode; |
|
2462 res = selection->GetFocusNode(getter_AddRefs(focusNode)); |
|
2463 NS_ENSURE_SUCCESS(res, res); |
|
2464 int32_t focusOffset = -1; |
|
2465 if (focusNode) |
|
2466 selection->GetFocusOffset(&focusOffset); |
|
2467 |
|
2468 // Link node must be the same for both ends of selection |
|
2469 if (NS_SUCCEEDED(res) && anchorNode) |
|
2470 { |
|
2471 nsCOMPtr<nsIDOMElement> parentLinkOfAnchor; |
|
2472 res = GetElementOrParentByTagName(NS_LITERAL_STRING("href"), anchorNode, getter_AddRefs(parentLinkOfAnchor)); |
|
2473 // XXX: ERROR_HANDLING can parentLinkOfAnchor be null? |
|
2474 if (NS_SUCCEEDED(res) && parentLinkOfAnchor) |
|
2475 { |
|
2476 if (isCollapsed) |
|
2477 { |
|
2478 // We have just a caret in the link |
|
2479 bNodeFound = true; |
|
2480 } else if(focusNode) |
|
2481 { // Link node must be the same for both ends of selection |
|
2482 nsCOMPtr<nsIDOMElement> parentLinkOfFocus; |
|
2483 res = GetElementOrParentByTagName(NS_LITERAL_STRING("href"), focusNode, getter_AddRefs(parentLinkOfFocus)); |
|
2484 if (NS_SUCCEEDED(res) && parentLinkOfFocus == parentLinkOfAnchor) |
|
2485 bNodeFound = true; |
|
2486 } |
|
2487 |
|
2488 // We found a link node parent |
|
2489 if (bNodeFound) { |
|
2490 // GetElementOrParentByTagName addref'd this, so we don't need to do it here |
|
2491 *aReturn = parentLinkOfAnchor; |
|
2492 NS_IF_ADDREF(*aReturn); |
|
2493 return NS_OK; |
|
2494 } |
|
2495 } |
|
2496 else if (anchorOffset >= 0) // Check if link node is the only thing selected |
|
2497 { |
|
2498 nsCOMPtr<nsIDOMNode> anchorChild; |
|
2499 anchorChild = GetChildAt(anchorNode,anchorOffset); |
|
2500 if (anchorChild && nsHTMLEditUtils::IsLink(anchorChild) && |
|
2501 (anchorNode == focusNode) && focusOffset == (anchorOffset+1)) |
|
2502 { |
|
2503 selectedElement = do_QueryInterface(anchorChild); |
|
2504 bNodeFound = true; |
|
2505 } |
|
2506 } |
|
2507 } |
|
2508 } |
|
2509 |
|
2510 if (!isCollapsed) // Don't bother to examine selection if it is collapsed |
|
2511 { |
|
2512 nsRefPtr<nsRange> currange = sel->GetRangeAt(0); |
|
2513 if (currange) { |
|
2514 nsCOMPtr<nsIContentIterator> iter = |
|
2515 do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res); |
|
2516 NS_ENSURE_SUCCESS(res, res); |
|
2517 |
|
2518 iter->Init(currange); |
|
2519 // loop through the content iterator for each content node |
|
2520 while (!iter->IsDone()) |
|
2521 { |
|
2522 // Query interface to cast nsIContent to nsIDOMNode |
|
2523 // then get tagType to compare to aTagName |
|
2524 // Clone node of each desired type and append it to the aDomFrag |
|
2525 selectedElement = do_QueryInterface(iter->GetCurrentNode()); |
|
2526 if (selectedElement) |
|
2527 { |
|
2528 // If we already found a node, then we have another element, |
|
2529 // thus there's not just one element selected |
|
2530 if (bNodeFound) |
|
2531 { |
|
2532 bNodeFound = false; |
|
2533 break; |
|
2534 } |
|
2535 |
|
2536 selectedElement->GetNodeName(domTagName); |
|
2537 ToLowerCase(domTagName); |
|
2538 |
|
2539 if (anyTag) |
|
2540 { |
|
2541 // Get name of first selected element |
|
2542 selectedElement->GetTagName(TagName); |
|
2543 ToLowerCase(TagName); |
|
2544 anyTag = false; |
|
2545 } |
|
2546 |
|
2547 // The "A" tag is a pain, |
|
2548 // used for both link(href is set) and "Named Anchor" |
|
2549 nsCOMPtr<nsIDOMNode> selectedNode = do_QueryInterface(selectedElement); |
|
2550 if ( (isLinkTag && nsHTMLEditUtils::IsLink(selectedNode)) || |
|
2551 (isNamedAnchorTag && nsHTMLEditUtils::IsNamedAnchor(selectedNode)) ) |
|
2552 { |
|
2553 bNodeFound = true; |
|
2554 } else if (TagName == domTagName) { // All other tag names are handled here |
|
2555 bNodeFound = true; |
|
2556 } |
|
2557 if (!bNodeFound) |
|
2558 { |
|
2559 // Check if node we have is really part of the selection??? |
|
2560 break; |
|
2561 } |
|
2562 } |
|
2563 iter->Next(); |
|
2564 } |
|
2565 } else { |
|
2566 // Should never get here? |
|
2567 isCollapsed = true; |
|
2568 NS_WARNING("isCollapsed was FALSE, but no elements found in selection\n"); |
|
2569 } |
|
2570 } |
|
2571 } |
|
2572 if (bNodeFound) |
|
2573 { |
|
2574 |
|
2575 *aReturn = selectedElement; |
|
2576 if (selectedElement) |
|
2577 { |
|
2578 // Getters must addref |
|
2579 NS_ADDREF(*aReturn); |
|
2580 } |
|
2581 } |
|
2582 else res = NS_EDITOR_ELEMENT_NOT_FOUND; |
|
2583 |
|
2584 return res; |
|
2585 } |
|
2586 |
|
2587 NS_IMETHODIMP |
|
2588 nsHTMLEditor::CreateElementWithDefaults(const nsAString& aTagName, nsIDOMElement** aReturn) |
|
2589 { |
|
2590 nsresult res=NS_ERROR_NOT_INITIALIZED; |
|
2591 if (aReturn) |
|
2592 *aReturn = nullptr; |
|
2593 |
|
2594 // NS_ENSURE_TRUE(aTagName && aReturn, NS_ERROR_NULL_POINTER); |
|
2595 NS_ENSURE_TRUE(!aTagName.IsEmpty() && aReturn, NS_ERROR_NULL_POINTER); |
|
2596 |
|
2597 nsAutoString TagName(aTagName); |
|
2598 ToLowerCase(TagName); |
|
2599 nsAutoString realTagName; |
|
2600 |
|
2601 if (IsLinkTag(TagName) || IsNamedAnchorTag(TagName)) |
|
2602 { |
|
2603 realTagName.AssignLiteral("a"); |
|
2604 } else { |
|
2605 realTagName = TagName; |
|
2606 } |
|
2607 //We don't use editor's CreateElement because we don't want to |
|
2608 // go through the transaction system |
|
2609 |
|
2610 nsCOMPtr<nsIDOMElement>newElement; |
|
2611 nsCOMPtr<dom::Element> newContent; |
|
2612 nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak); |
|
2613 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); |
|
2614 |
|
2615 //new call to use instead to get proper HTML element, bug# 39919 |
|
2616 res = CreateHTMLContent(realTagName, getter_AddRefs(newContent)); |
|
2617 newElement = do_QueryInterface(newContent); |
|
2618 if (NS_FAILED(res) || !newElement) |
|
2619 return NS_ERROR_FAILURE; |
|
2620 |
|
2621 // Mark the new element dirty, so it will be formatted |
|
2622 newElement->SetAttribute(NS_LITERAL_STRING("_moz_dirty"), EmptyString()); |
|
2623 |
|
2624 // Set default values for new elements |
|
2625 if (TagName.EqualsLiteral("table")) { |
|
2626 res = newElement->SetAttribute(NS_LITERAL_STRING("cellpadding"),NS_LITERAL_STRING("2")); |
|
2627 NS_ENSURE_SUCCESS(res, res); |
|
2628 res = newElement->SetAttribute(NS_LITERAL_STRING("cellspacing"),NS_LITERAL_STRING("2")); |
|
2629 NS_ENSURE_SUCCESS(res, res); |
|
2630 res = newElement->SetAttribute(NS_LITERAL_STRING("border"),NS_LITERAL_STRING("1")); |
|
2631 } else if (TagName.EqualsLiteral("td")) |
|
2632 { |
|
2633 res = SetAttributeOrEquivalent(newElement, NS_LITERAL_STRING("valign"), |
|
2634 NS_LITERAL_STRING("top"), true); |
|
2635 } |
|
2636 // ADD OTHER TAGS HERE |
|
2637 |
|
2638 if (NS_SUCCEEDED(res)) |
|
2639 { |
|
2640 *aReturn = newElement; |
|
2641 // Getters must addref |
|
2642 NS_ADDREF(*aReturn); |
|
2643 } |
|
2644 |
|
2645 return res; |
|
2646 } |
|
2647 |
|
2648 NS_IMETHODIMP |
|
2649 nsHTMLEditor::InsertLinkAroundSelection(nsIDOMElement* aAnchorElement) |
|
2650 { |
|
2651 NS_ENSURE_TRUE(aAnchorElement, NS_ERROR_NULL_POINTER); |
|
2652 |
|
2653 // We must have a real selection |
|
2654 nsCOMPtr<nsISelection> selection; |
|
2655 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
2656 if (!selection) |
|
2657 { |
|
2658 res = NS_ERROR_NULL_POINTER; |
|
2659 } |
|
2660 NS_ENSURE_SUCCESS(res, res); |
|
2661 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
2662 |
|
2663 if (selection->Collapsed()) { |
|
2664 NS_WARNING("InsertLinkAroundSelection called but there is no selection!!!"); |
|
2665 return NS_OK; |
|
2666 } |
|
2667 |
|
2668 // Be sure we were given an anchor element |
|
2669 nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = do_QueryInterface(aAnchorElement); |
|
2670 if (!anchor) { |
|
2671 return NS_OK; |
|
2672 } |
|
2673 |
|
2674 nsAutoString href; |
|
2675 res = anchor->GetHref(href); |
|
2676 NS_ENSURE_SUCCESS(res, res); |
|
2677 if (href.IsEmpty()) { |
|
2678 return NS_OK; |
|
2679 } |
|
2680 |
|
2681 nsAutoEditBatch beginBatching(this); |
|
2682 |
|
2683 // Set all attributes found on the supplied anchor element |
|
2684 nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap; |
|
2685 aAnchorElement->GetAttributes(getter_AddRefs(attrMap)); |
|
2686 NS_ENSURE_TRUE(attrMap, NS_ERROR_FAILURE); |
|
2687 |
|
2688 uint32_t count; |
|
2689 attrMap->GetLength(&count); |
|
2690 nsAutoString name, value; |
|
2691 |
|
2692 for (uint32_t i = 0; i < count; ++i) { |
|
2693 nsCOMPtr<nsIDOMAttr> attribute; |
|
2694 res = attrMap->Item(i, getter_AddRefs(attribute)); |
|
2695 NS_ENSURE_SUCCESS(res, res); |
|
2696 |
|
2697 if (attribute) { |
|
2698 // We must clear the string buffers |
|
2699 // because GetName, GetValue appends to previous string! |
|
2700 name.Truncate(); |
|
2701 value.Truncate(); |
|
2702 |
|
2703 res = attribute->GetName(name); |
|
2704 NS_ENSURE_SUCCESS(res, res); |
|
2705 |
|
2706 res = attribute->GetValue(value); |
|
2707 NS_ENSURE_SUCCESS(res, res); |
|
2708 |
|
2709 res = SetInlineProperty(nsEditProperty::a, name, value); |
|
2710 NS_ENSURE_SUCCESS(res, res); |
|
2711 } |
|
2712 } |
|
2713 return NS_OK; |
|
2714 } |
|
2715 |
|
2716 NS_IMETHODIMP |
|
2717 nsHTMLEditor::SetHTMLBackgroundColor(const nsAString& aColor) |
|
2718 { |
|
2719 NS_PRECONDITION(mDocWeak, "Missing Editor DOM Document"); |
|
2720 |
|
2721 // Find a selected or enclosing table element to set background on |
|
2722 nsCOMPtr<nsIDOMElement> element; |
|
2723 int32_t selectedCount; |
|
2724 nsAutoString tagName; |
|
2725 nsresult res = GetSelectedOrParentTableElement(tagName, &selectedCount, |
|
2726 getter_AddRefs(element)); |
|
2727 NS_ENSURE_SUCCESS(res, res); |
|
2728 |
|
2729 bool setColor = !aColor.IsEmpty(); |
|
2730 |
|
2731 NS_NAMED_LITERAL_STRING(bgcolor, "bgcolor"); |
|
2732 if (element) |
|
2733 { |
|
2734 if (selectedCount > 0) |
|
2735 { |
|
2736 // Traverse all selected cells |
|
2737 nsCOMPtr<nsIDOMElement> cell; |
|
2738 res = GetFirstSelectedCell(nullptr, getter_AddRefs(cell)); |
|
2739 if (NS_SUCCEEDED(res) && cell) |
|
2740 { |
|
2741 while(cell) |
|
2742 { |
|
2743 if (setColor) |
|
2744 res = SetAttribute(cell, bgcolor, aColor); |
|
2745 else |
|
2746 res = RemoveAttribute(cell, bgcolor); |
|
2747 if (NS_FAILED(res)) break; |
|
2748 |
|
2749 GetNextSelectedCell(nullptr, getter_AddRefs(cell)); |
|
2750 }; |
|
2751 return res; |
|
2752 } |
|
2753 } |
|
2754 // If we failed to find a cell, fall through to use originally-found element |
|
2755 } else { |
|
2756 // No table element -- set the background color on the body tag |
|
2757 element = do_QueryInterface(GetRoot()); |
|
2758 NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER); |
|
2759 } |
|
2760 // Use the editor method that goes through the transaction system |
|
2761 if (setColor) |
|
2762 res = SetAttribute(element, bgcolor, aColor); |
|
2763 else |
|
2764 res = RemoveAttribute(element, bgcolor); |
|
2765 |
|
2766 return res; |
|
2767 } |
|
2768 |
|
2769 NS_IMETHODIMP nsHTMLEditor::SetBodyAttribute(const nsAString& aAttribute, const nsAString& aValue) |
|
2770 { |
|
2771 // TODO: Check selection for Cell, Row, Column or table and do color on appropriate level |
|
2772 |
|
2773 NS_ASSERTION(mDocWeak, "Missing Editor DOM Document"); |
|
2774 |
|
2775 // Set the background color attribute on the body tag |
|
2776 nsCOMPtr<nsIDOMElement> bodyElement = do_QueryInterface(GetRoot()); |
|
2777 NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER); |
|
2778 |
|
2779 // Use the editor method that goes through the transaction system |
|
2780 return SetAttribute(bodyElement, aAttribute, aValue); |
|
2781 } |
|
2782 |
|
2783 NS_IMETHODIMP |
|
2784 nsHTMLEditor::GetLinkedObjects(nsISupportsArray** aNodeList) |
|
2785 { |
|
2786 NS_ENSURE_TRUE(aNodeList, NS_ERROR_NULL_POINTER); |
|
2787 |
|
2788 nsresult res; |
|
2789 |
|
2790 res = NS_NewISupportsArray(aNodeList); |
|
2791 NS_ENSURE_SUCCESS(res, res); |
|
2792 NS_ENSURE_TRUE(*aNodeList, NS_ERROR_NULL_POINTER); |
|
2793 |
|
2794 nsCOMPtr<nsIContentIterator> iter = |
|
2795 do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res); |
|
2796 NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); |
|
2797 if ((NS_SUCCEEDED(res))) |
|
2798 { |
|
2799 nsCOMPtr<nsIDocument> doc = GetDocument(); |
|
2800 NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); |
|
2801 |
|
2802 iter->Init(doc->GetRootElement()); |
|
2803 |
|
2804 // loop through the content iterator for each content node |
|
2805 while (!iter->IsDone()) |
|
2806 { |
|
2807 nsCOMPtr<nsIDOMNode> node (do_QueryInterface(iter->GetCurrentNode())); |
|
2808 if (node) |
|
2809 { |
|
2810 // Let nsURIRefObject make the hard decisions: |
|
2811 nsCOMPtr<nsIURIRefObject> refObject; |
|
2812 res = NS_NewHTMLURIRefObject(getter_AddRefs(refObject), node); |
|
2813 if (NS_SUCCEEDED(res)) |
|
2814 { |
|
2815 nsCOMPtr<nsISupports> isupp (do_QueryInterface(refObject)); |
|
2816 |
|
2817 (*aNodeList)->AppendElement(isupp); |
|
2818 } |
|
2819 } |
|
2820 iter->Next(); |
|
2821 } |
|
2822 } |
|
2823 |
|
2824 return NS_OK; |
|
2825 } |
|
2826 |
|
2827 |
|
2828 NS_IMETHODIMP |
|
2829 nsHTMLEditor::AddStyleSheet(const nsAString &aURL) |
|
2830 { |
|
2831 // Enable existing sheet if already loaded. |
|
2832 if (EnableExistingStyleSheet(aURL)) |
|
2833 return NS_OK; |
|
2834 |
|
2835 // Lose the previously-loaded sheet so there's nothing to replace |
|
2836 // This pattern is different from Override methods because |
|
2837 // we must wait to remove mLastStyleSheetURL and add new sheet |
|
2838 // at the same time (in StyleSheetLoaded callback) so they are undoable together |
|
2839 mLastStyleSheetURL.Truncate(); |
|
2840 return ReplaceStyleSheet(aURL); |
|
2841 } |
|
2842 |
|
2843 NS_IMETHODIMP |
|
2844 nsHTMLEditor::ReplaceStyleSheet(const nsAString& aURL) |
|
2845 { |
|
2846 // Enable existing sheet if already loaded. |
|
2847 if (EnableExistingStyleSheet(aURL)) |
|
2848 { |
|
2849 // Disable last sheet if not the same as new one |
|
2850 if (!mLastStyleSheetURL.IsEmpty() && !mLastStyleSheetURL.Equals(aURL)) |
|
2851 return EnableStyleSheet(mLastStyleSheetURL, false); |
|
2852 |
|
2853 return NS_OK; |
|
2854 } |
|
2855 |
|
2856 // Make sure the pres shell doesn't disappear during the load. |
|
2857 NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED); |
|
2858 nsCOMPtr<nsIPresShell> ps = GetPresShell(); |
|
2859 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); |
|
2860 |
|
2861 nsCOMPtr<nsIURI> uaURI; |
|
2862 nsresult rv = NS_NewURI(getter_AddRefs(uaURI), aURL); |
|
2863 NS_ENSURE_SUCCESS(rv, rv); |
|
2864 |
|
2865 return ps->GetDocument()->CSSLoader()-> |
|
2866 LoadSheet(uaURI, nullptr, EmptyCString(), this); |
|
2867 } |
|
2868 |
|
2869 NS_IMETHODIMP |
|
2870 nsHTMLEditor::RemoveStyleSheet(const nsAString &aURL) |
|
2871 { |
|
2872 nsRefPtr<nsCSSStyleSheet> sheet; |
|
2873 nsresult rv = GetStyleSheetForURL(aURL, getter_AddRefs(sheet)); |
|
2874 NS_ENSURE_SUCCESS(rv, rv); |
|
2875 NS_ENSURE_TRUE(sheet, NS_ERROR_UNEXPECTED); |
|
2876 |
|
2877 nsRefPtr<RemoveStyleSheetTxn> txn; |
|
2878 rv = CreateTxnForRemoveStyleSheet(sheet, getter_AddRefs(txn)); |
|
2879 if (!txn) rv = NS_ERROR_NULL_POINTER; |
|
2880 if (NS_SUCCEEDED(rv)) |
|
2881 { |
|
2882 rv = DoTransaction(txn); |
|
2883 if (NS_SUCCEEDED(rv)) |
|
2884 mLastStyleSheetURL.Truncate(); // forget it |
|
2885 |
|
2886 // Remove it from our internal list |
|
2887 rv = RemoveStyleSheetFromList(aURL); |
|
2888 } |
|
2889 |
|
2890 return rv; |
|
2891 } |
|
2892 |
|
2893 |
|
2894 NS_IMETHODIMP |
|
2895 nsHTMLEditor::AddOverrideStyleSheet(const nsAString& aURL) |
|
2896 { |
|
2897 // Enable existing sheet if already loaded. |
|
2898 if (EnableExistingStyleSheet(aURL)) |
|
2899 return NS_OK; |
|
2900 |
|
2901 // Make sure the pres shell doesn't disappear during the load. |
|
2902 nsCOMPtr<nsIPresShell> ps = GetPresShell(); |
|
2903 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); |
|
2904 |
|
2905 nsCOMPtr<nsIURI> uaURI; |
|
2906 nsresult rv = NS_NewURI(getter_AddRefs(uaURI), aURL); |
|
2907 NS_ENSURE_SUCCESS(rv, rv); |
|
2908 |
|
2909 // We MUST ONLY load synchronous local files (no @import) |
|
2910 // XXXbz Except this will actually try to load remote files |
|
2911 // synchronously, of course.. |
|
2912 nsRefPtr<nsCSSStyleSheet> sheet; |
|
2913 // Editor override style sheets may want to style Gecko anonymous boxes |
|
2914 rv = ps->GetDocument()->CSSLoader()-> |
|
2915 LoadSheetSync(uaURI, true, true, getter_AddRefs(sheet)); |
|
2916 |
|
2917 // Synchronous loads should ALWAYS return completed |
|
2918 NS_ENSURE_TRUE(sheet, NS_ERROR_NULL_POINTER); |
|
2919 |
|
2920 // Add the override style sheet |
|
2921 // (This checks if already exists) |
|
2922 ps->AddOverrideStyleSheet(sheet); |
|
2923 |
|
2924 ps->ReconstructStyleData(); |
|
2925 |
|
2926 // Save as the last-loaded sheet |
|
2927 mLastOverrideStyleSheetURL = aURL; |
|
2928 |
|
2929 //Add URL and style sheet to our lists |
|
2930 return AddNewStyleSheetToList(aURL, sheet); |
|
2931 } |
|
2932 |
|
2933 NS_IMETHODIMP |
|
2934 nsHTMLEditor::ReplaceOverrideStyleSheet(const nsAString& aURL) |
|
2935 { |
|
2936 // Enable existing sheet if already loaded. |
|
2937 if (EnableExistingStyleSheet(aURL)) |
|
2938 { |
|
2939 // Disable last sheet if not the same as new one |
|
2940 if (!mLastOverrideStyleSheetURL.IsEmpty() && !mLastOverrideStyleSheetURL.Equals(aURL)) |
|
2941 return EnableStyleSheet(mLastOverrideStyleSheetURL, false); |
|
2942 |
|
2943 return NS_OK; |
|
2944 } |
|
2945 // Remove the previous sheet |
|
2946 if (!mLastOverrideStyleSheetURL.IsEmpty()) |
|
2947 RemoveOverrideStyleSheet(mLastOverrideStyleSheetURL); |
|
2948 |
|
2949 return AddOverrideStyleSheet(aURL); |
|
2950 } |
|
2951 |
|
2952 // Do NOT use transaction system for override style sheets |
|
2953 NS_IMETHODIMP |
|
2954 nsHTMLEditor::RemoveOverrideStyleSheet(const nsAString &aURL) |
|
2955 { |
|
2956 nsRefPtr<nsCSSStyleSheet> sheet; |
|
2957 GetStyleSheetForURL(aURL, getter_AddRefs(sheet)); |
|
2958 |
|
2959 // Make sure we remove the stylesheet from our internal list in all |
|
2960 // cases. |
|
2961 nsresult rv = RemoveStyleSheetFromList(aURL); |
|
2962 |
|
2963 NS_ENSURE_TRUE(sheet, NS_OK); /// Don't fail if sheet not found |
|
2964 |
|
2965 NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED); |
|
2966 nsCOMPtr<nsIPresShell> ps = GetPresShell(); |
|
2967 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); |
|
2968 |
|
2969 ps->RemoveOverrideStyleSheet(sheet); |
|
2970 ps->ReconstructStyleData(); |
|
2971 |
|
2972 // Remove it from our internal list |
|
2973 return rv; |
|
2974 } |
|
2975 |
|
2976 NS_IMETHODIMP |
|
2977 nsHTMLEditor::EnableStyleSheet(const nsAString &aURL, bool aEnable) |
|
2978 { |
|
2979 nsRefPtr<nsCSSStyleSheet> sheet; |
|
2980 nsresult rv = GetStyleSheetForURL(aURL, getter_AddRefs(sheet)); |
|
2981 NS_ENSURE_SUCCESS(rv, rv); |
|
2982 NS_ENSURE_TRUE(sheet, NS_OK); // Don't fail if sheet not found |
|
2983 |
|
2984 // Ensure the style sheet is owned by our document. |
|
2985 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
|
2986 sheet->SetOwningDocument(doc); |
|
2987 |
|
2988 return sheet->SetDisabled(!aEnable); |
|
2989 } |
|
2990 |
|
2991 bool |
|
2992 nsHTMLEditor::EnableExistingStyleSheet(const nsAString &aURL) |
|
2993 { |
|
2994 nsRefPtr<nsCSSStyleSheet> sheet; |
|
2995 nsresult rv = GetStyleSheetForURL(aURL, getter_AddRefs(sheet)); |
|
2996 NS_ENSURE_SUCCESS(rv, false); |
|
2997 |
|
2998 // Enable sheet if already loaded. |
|
2999 if (sheet) |
|
3000 { |
|
3001 // Ensure the style sheet is owned by our document. |
|
3002 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
|
3003 sheet->SetOwningDocument(doc); |
|
3004 |
|
3005 sheet->SetDisabled(false); |
|
3006 return true; |
|
3007 } |
|
3008 return false; |
|
3009 } |
|
3010 |
|
3011 nsresult |
|
3012 nsHTMLEditor::AddNewStyleSheetToList(const nsAString &aURL, |
|
3013 nsCSSStyleSheet *aStyleSheet) |
|
3014 { |
|
3015 uint32_t countSS = mStyleSheets.Length(); |
|
3016 uint32_t countU = mStyleSheetURLs.Length(); |
|
3017 |
|
3018 if (countSS != countU) |
|
3019 return NS_ERROR_UNEXPECTED; |
|
3020 |
|
3021 if (!mStyleSheetURLs.AppendElement(aURL)) |
|
3022 return NS_ERROR_UNEXPECTED; |
|
3023 |
|
3024 return mStyleSheets.AppendElement(aStyleSheet) ? NS_OK : NS_ERROR_UNEXPECTED; |
|
3025 } |
|
3026 |
|
3027 nsresult |
|
3028 nsHTMLEditor::RemoveStyleSheetFromList(const nsAString &aURL) |
|
3029 { |
|
3030 // is it already in the list? |
|
3031 uint32_t foundIndex; |
|
3032 foundIndex = mStyleSheetURLs.IndexOf(aURL); |
|
3033 if (foundIndex == mStyleSheetURLs.NoIndex) |
|
3034 return NS_ERROR_FAILURE; |
|
3035 |
|
3036 // Attempt both removals; if one fails there's not much we can do. |
|
3037 mStyleSheets.RemoveElementAt(foundIndex); |
|
3038 mStyleSheetURLs.RemoveElementAt(foundIndex); |
|
3039 |
|
3040 return NS_OK; |
|
3041 } |
|
3042 |
|
3043 NS_IMETHODIMP |
|
3044 nsHTMLEditor::GetStyleSheetForURL(const nsAString &aURL, |
|
3045 nsCSSStyleSheet **aStyleSheet) |
|
3046 { |
|
3047 NS_ENSURE_ARG_POINTER(aStyleSheet); |
|
3048 *aStyleSheet = 0; |
|
3049 |
|
3050 // is it already in the list? |
|
3051 uint32_t foundIndex; |
|
3052 foundIndex = mStyleSheetURLs.IndexOf(aURL); |
|
3053 if (foundIndex == mStyleSheetURLs.NoIndex) |
|
3054 return NS_OK; //No sheet -- don't fail! |
|
3055 |
|
3056 *aStyleSheet = mStyleSheets[foundIndex]; |
|
3057 NS_ENSURE_TRUE(*aStyleSheet, NS_ERROR_FAILURE); |
|
3058 |
|
3059 NS_ADDREF(*aStyleSheet); |
|
3060 |
|
3061 return NS_OK; |
|
3062 } |
|
3063 |
|
3064 NS_IMETHODIMP |
|
3065 nsHTMLEditor::GetURLForStyleSheet(nsCSSStyleSheet *aStyleSheet, |
|
3066 nsAString &aURL) |
|
3067 { |
|
3068 // is it already in the list? |
|
3069 int32_t foundIndex = mStyleSheets.IndexOf(aStyleSheet); |
|
3070 |
|
3071 // Don't fail if we don't find it in our list |
|
3072 // Note: mStyleSheets is nsCOMArray, so its IndexOf() method |
|
3073 // returns -1 on failure. |
|
3074 if (foundIndex == -1) |
|
3075 return NS_OK; |
|
3076 |
|
3077 // Found it in the list! |
|
3078 aURL = mStyleSheetURLs[foundIndex]; |
|
3079 return NS_OK; |
|
3080 } |
|
3081 |
|
3082 /* |
|
3083 * nsIEditorMailSupport methods |
|
3084 */ |
|
3085 |
|
3086 NS_IMETHODIMP |
|
3087 nsHTMLEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList) |
|
3088 { |
|
3089 NS_ENSURE_TRUE(aNodeList, NS_ERROR_NULL_POINTER); |
|
3090 |
|
3091 nsresult rv = NS_NewISupportsArray(aNodeList); |
|
3092 NS_ENSURE_SUCCESS(rv, rv); |
|
3093 NS_ENSURE_TRUE(*aNodeList, NS_ERROR_NULL_POINTER); |
|
3094 |
|
3095 nsCOMPtr<nsIContentIterator> iter = |
|
3096 do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv); |
|
3097 NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); |
|
3098 NS_ENSURE_SUCCESS(rv, rv); |
|
3099 |
|
3100 nsCOMPtr<nsIDocument> doc = GetDocument(); |
|
3101 NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); |
|
3102 |
|
3103 iter->Init(doc->GetRootElement()); |
|
3104 |
|
3105 // Loop through the content iterator for each content node. |
|
3106 while (!iter->IsDone()) { |
|
3107 nsINode* node = iter->GetCurrentNode(); |
|
3108 if (node->IsElement()) { |
|
3109 dom::Element* element = node->AsElement(); |
|
3110 |
|
3111 // See if it's an image or an embed and also include all links. |
|
3112 // Let mail decide which link to send or not |
|
3113 if (element->IsHTML(nsGkAtoms::img) || |
|
3114 element->IsHTML(nsGkAtoms::embed) || |
|
3115 element->IsHTML(nsGkAtoms::a) || |
|
3116 (element->IsHTML(nsGkAtoms::body) && |
|
3117 element->HasAttr(kNameSpaceID_None, nsGkAtoms::background))) { |
|
3118 nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(node); |
|
3119 (*aNodeList)->AppendElement(domNode); |
|
3120 } |
|
3121 } |
|
3122 iter->Next(); |
|
3123 } |
|
3124 |
|
3125 return rv; |
|
3126 } |
|
3127 |
|
3128 |
|
3129 NS_IMETHODIMP |
|
3130 nsHTMLEditor::DeleteSelectionImpl(EDirection aAction, |
|
3131 EStripWrappers aStripWrappers) |
|
3132 { |
|
3133 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); |
|
3134 |
|
3135 nsresult res = nsEditor::DeleteSelectionImpl(aAction, aStripWrappers); |
|
3136 NS_ENSURE_SUCCESS(res, res); |
|
3137 |
|
3138 // If we weren't asked to strip any wrappers, we're done. |
|
3139 if (aStripWrappers == eNoStrip) { |
|
3140 return NS_OK; |
|
3141 } |
|
3142 |
|
3143 nsRefPtr<Selection> selection = GetSelection(); |
|
3144 // Just checking that the selection itself is collapsed doesn't seem to work |
|
3145 // right in the multi-range case |
|
3146 NS_ENSURE_STATE(selection); |
|
3147 NS_ENSURE_STATE(selection->GetAnchorFocusRange()); |
|
3148 NS_ENSURE_STATE(selection->GetAnchorFocusRange()->Collapsed()); |
|
3149 |
|
3150 NS_ENSURE_STATE(selection->GetAnchorNode()->IsContent()); |
|
3151 nsCOMPtr<nsIContent> content = selection->GetAnchorNode()->AsContent(); |
|
3152 |
|
3153 // Don't strip wrappers if this is the only wrapper in the block. Then we'll |
|
3154 // add a <br> later, so it won't be an empty wrapper in the end. |
|
3155 nsCOMPtr<nsIContent> blockParent = content; |
|
3156 while (blockParent && !IsBlockNode(blockParent)) { |
|
3157 blockParent = blockParent->GetParent(); |
|
3158 } |
|
3159 if (!blockParent) { |
|
3160 return NS_OK; |
|
3161 } |
|
3162 bool emptyBlockParent; |
|
3163 res = IsEmptyNode(blockParent, &emptyBlockParent); |
|
3164 NS_ENSURE_SUCCESS(res, res); |
|
3165 if (emptyBlockParent) { |
|
3166 return NS_OK; |
|
3167 } |
|
3168 |
|
3169 if (content && !IsBlockNode(content) && !content->Length() && |
|
3170 content->IsEditable() && content != content->GetEditingHost()) { |
|
3171 while (content->GetParent() && !IsBlockNode(content->GetParent()) && |
|
3172 content->GetParent()->Length() == 1 && |
|
3173 content->GetParent()->IsEditable() && |
|
3174 content->GetParent() != content->GetEditingHost()) { |
|
3175 content = content->GetParent(); |
|
3176 } |
|
3177 res = DeleteNode(content); |
|
3178 NS_ENSURE_SUCCESS(res, res); |
|
3179 } |
|
3180 |
|
3181 return NS_OK; |
|
3182 } |
|
3183 |
|
3184 |
|
3185 nsresult |
|
3186 nsHTMLEditor::DeleteNode(nsINode* aNode) |
|
3187 { |
|
3188 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode); |
|
3189 return DeleteNode(node); |
|
3190 } |
|
3191 |
|
3192 NS_IMETHODIMP |
|
3193 nsHTMLEditor::DeleteNode(nsIDOMNode* aNode) |
|
3194 { |
|
3195 // do nothing if the node is read-only |
|
3196 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); |
|
3197 if (!IsModifiableNode(aNode) && !IsMozEditorBogusNode(content)) { |
|
3198 return NS_ERROR_FAILURE; |
|
3199 } |
|
3200 |
|
3201 return nsEditor::DeleteNode(aNode); |
|
3202 } |
|
3203 |
|
3204 NS_IMETHODIMP nsHTMLEditor::DeleteText(nsIDOMCharacterData *aTextNode, |
|
3205 uint32_t aOffset, |
|
3206 uint32_t aLength) |
|
3207 { |
|
3208 // do nothing if the node is read-only |
|
3209 if (!IsModifiableNode(aTextNode)) { |
|
3210 return NS_ERROR_FAILURE; |
|
3211 } |
|
3212 |
|
3213 return nsEditor::DeleteText(aTextNode, aOffset, aLength); |
|
3214 } |
|
3215 |
|
3216 NS_IMETHODIMP nsHTMLEditor::InsertTextImpl(const nsAString& aStringToInsert, |
|
3217 nsCOMPtr<nsIDOMNode> *aInOutNode, |
|
3218 int32_t *aInOutOffset, |
|
3219 nsIDOMDocument *aDoc) |
|
3220 { |
|
3221 // do nothing if the node is read-only |
|
3222 if (!IsModifiableNode(*aInOutNode)) { |
|
3223 return NS_ERROR_FAILURE; |
|
3224 } |
|
3225 |
|
3226 return nsEditor::InsertTextImpl(aStringToInsert, aInOutNode, aInOutOffset, aDoc); |
|
3227 } |
|
3228 |
|
3229 void |
|
3230 nsHTMLEditor::ContentAppended(nsIDocument *aDocument, nsIContent* aContainer, |
|
3231 nsIContent* aFirstNewContent, |
|
3232 int32_t aIndexInContainer) |
|
3233 { |
|
3234 DoContentInserted(aDocument, aContainer, aFirstNewContent, aIndexInContainer, |
|
3235 eAppended); |
|
3236 } |
|
3237 |
|
3238 void |
|
3239 nsHTMLEditor::ContentInserted(nsIDocument *aDocument, nsIContent* aContainer, |
|
3240 nsIContent* aChild, int32_t aIndexInContainer) |
|
3241 { |
|
3242 DoContentInserted(aDocument, aContainer, aChild, aIndexInContainer, |
|
3243 eInserted); |
|
3244 } |
|
3245 |
|
3246 void |
|
3247 nsHTMLEditor::DoContentInserted(nsIDocument* aDocument, nsIContent* aContainer, |
|
3248 nsIContent* aChild, int32_t aIndexInContainer, |
|
3249 InsertedOrAppended aInsertedOrAppended) |
|
3250 { |
|
3251 if (!aChild) { |
|
3252 return; |
|
3253 } |
|
3254 |
|
3255 nsCOMPtr<nsIHTMLEditor> kungFuDeathGrip(this); |
|
3256 |
|
3257 if (ShouldReplaceRootElement()) { |
|
3258 nsContentUtils::AddScriptRunner(NS_NewRunnableMethod( |
|
3259 this, &nsHTMLEditor::ResetRootElementAndEventTarget)); |
|
3260 } |
|
3261 // We don't need to handle our own modifications |
|
3262 else if (!mAction && (aContainer ? aContainer->IsEditable() : aDocument->IsEditable())) { |
|
3263 if (IsMozEditorBogusNode(aChild)) { |
|
3264 // Ignore insertion of the bogus node |
|
3265 return; |
|
3266 } |
|
3267 // Protect the edit rules object from dying |
|
3268 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
3269 mRules->DocumentModified(); |
|
3270 |
|
3271 // Update spellcheck for only the newly-inserted node (bug 743819) |
|
3272 if (mInlineSpellChecker) { |
|
3273 nsRefPtr<nsRange> range = new nsRange(aChild); |
|
3274 int32_t endIndex = aIndexInContainer + 1; |
|
3275 if (aInsertedOrAppended == eAppended) { |
|
3276 // Count all the appended nodes |
|
3277 nsIContent* sibling = aChild->GetNextSibling(); |
|
3278 while (sibling) { |
|
3279 endIndex++; |
|
3280 sibling = sibling->GetNextSibling(); |
|
3281 } |
|
3282 } |
|
3283 nsresult res = range->Set(aContainer, aIndexInContainer, |
|
3284 aContainer, endIndex); |
|
3285 if (NS_SUCCEEDED(res)) { |
|
3286 mInlineSpellChecker->SpellCheckRange(range); |
|
3287 } |
|
3288 } |
|
3289 } |
|
3290 } |
|
3291 |
|
3292 void |
|
3293 nsHTMLEditor::ContentRemoved(nsIDocument *aDocument, nsIContent* aContainer, |
|
3294 nsIContent* aChild, int32_t aIndexInContainer, |
|
3295 nsIContent* aPreviousSibling) |
|
3296 { |
|
3297 nsCOMPtr<nsIHTMLEditor> kungFuDeathGrip(this); |
|
3298 |
|
3299 if (SameCOMIdentity(aChild, mRootElement)) { |
|
3300 nsContentUtils::AddScriptRunner(NS_NewRunnableMethod( |
|
3301 this, &nsHTMLEditor::ResetRootElementAndEventTarget)); |
|
3302 } |
|
3303 // We don't need to handle our own modifications |
|
3304 else if (!mAction && (aContainer ? aContainer->IsEditable() : aDocument->IsEditable())) { |
|
3305 if (aChild && IsMozEditorBogusNode(aChild)) { |
|
3306 // Ignore removal of the bogus node |
|
3307 return; |
|
3308 } |
|
3309 // Protect the edit rules object from dying |
|
3310 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
3311 mRules->DocumentModified(); |
|
3312 } |
|
3313 } |
|
3314 |
|
3315 NS_IMETHODIMP_(bool) |
|
3316 nsHTMLEditor::IsModifiableNode(nsIDOMNode *aNode) |
|
3317 { |
|
3318 nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
|
3319 return IsModifiableNode(node); |
|
3320 } |
|
3321 |
|
3322 bool |
|
3323 nsHTMLEditor::IsModifiableNode(nsINode *aNode) |
|
3324 { |
|
3325 return !aNode || aNode->IsEditable(); |
|
3326 } |
|
3327 |
|
3328 NS_IMETHODIMP |
|
3329 nsHTMLEditor::GetIsSelectionEditable(bool* aIsSelectionEditable) |
|
3330 { |
|
3331 MOZ_ASSERT(aIsSelectionEditable); |
|
3332 |
|
3333 nsRefPtr<Selection> selection = GetSelection(); |
|
3334 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
3335 |
|
3336 // Per the editing spec as of June 2012: we have to have a selection whose |
|
3337 // start and end nodes are editable, and which share an ancestor editing |
|
3338 // host. (Bug 766387.) |
|
3339 *aIsSelectionEditable = selection->GetRangeCount() && |
|
3340 selection->GetAnchorNode()->IsEditable() && |
|
3341 selection->GetFocusNode()->IsEditable(); |
|
3342 |
|
3343 if (*aIsSelectionEditable) { |
|
3344 nsINode* commonAncestor = |
|
3345 selection->GetAnchorFocusRange()->GetCommonAncestor(); |
|
3346 while (commonAncestor && !commonAncestor->IsEditable()) { |
|
3347 commonAncestor = commonAncestor->GetParentNode(); |
|
3348 } |
|
3349 if (!commonAncestor) { |
|
3350 // No editable common ancestor |
|
3351 *aIsSelectionEditable = false; |
|
3352 } |
|
3353 } |
|
3354 |
|
3355 return NS_OK; |
|
3356 } |
|
3357 |
|
3358 static nsresult |
|
3359 SetSelectionAroundHeadChildren(nsISelection* aSelection, |
|
3360 nsIWeakReference* aDocWeak) |
|
3361 { |
|
3362 // Set selection around <head> node |
|
3363 nsCOMPtr<nsIDocument> doc = do_QueryReferent(aDocWeak); |
|
3364 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); |
|
3365 |
|
3366 dom::Element* headNode = doc->GetHeadElement(); |
|
3367 NS_ENSURE_STATE(headNode); |
|
3368 |
|
3369 // Collapse selection to before first child of the head, |
|
3370 nsresult rv = aSelection->CollapseNative(headNode, 0); |
|
3371 NS_ENSURE_SUCCESS(rv, rv); |
|
3372 |
|
3373 // Then extend it to just after. |
|
3374 uint32_t childCount = headNode->GetChildCount(); |
|
3375 return aSelection->ExtendNative(headNode, childCount + 1); |
|
3376 } |
|
3377 |
|
3378 NS_IMETHODIMP |
|
3379 nsHTMLEditor::GetHeadContentsAsHTML(nsAString& aOutputString) |
|
3380 { |
|
3381 nsRefPtr<Selection> selection = GetSelection(); |
|
3382 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); |
|
3383 |
|
3384 // Save current selection |
|
3385 nsAutoSelectionReset selectionResetter(selection, this); |
|
3386 |
|
3387 nsresult res = SetSelectionAroundHeadChildren(selection, mDocWeak); |
|
3388 NS_ENSURE_SUCCESS(res, res); |
|
3389 |
|
3390 res = OutputToString(NS_LITERAL_STRING("text/html"), |
|
3391 nsIDocumentEncoder::OutputSelectionOnly, |
|
3392 aOutputString); |
|
3393 if (NS_SUCCEEDED(res)) |
|
3394 { |
|
3395 // Selection always includes <body></body>, |
|
3396 // so terminate there |
|
3397 nsReadingIterator<char16_t> findIter,endFindIter; |
|
3398 aOutputString.BeginReading(findIter); |
|
3399 aOutputString.EndReading(endFindIter); |
|
3400 //counting on our parser to always lower case!!! |
|
3401 if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<body"), |
|
3402 findIter, endFindIter)) |
|
3403 { |
|
3404 nsReadingIterator<char16_t> beginIter; |
|
3405 aOutputString.BeginReading(beginIter); |
|
3406 int32_t offset = Distance(beginIter, findIter);//get the distance |
|
3407 |
|
3408 nsWritingIterator<char16_t> writeIter; |
|
3409 aOutputString.BeginWriting(writeIter); |
|
3410 // Ensure the string ends in a newline |
|
3411 char16_t newline ('\n'); |
|
3412 findIter.advance(-1); |
|
3413 if (offset ==0 || (offset >0 && (*findIter) != newline)) //check for 0 |
|
3414 { |
|
3415 writeIter.advance(offset); |
|
3416 *writeIter = newline; |
|
3417 aOutputString.Truncate(offset+1); |
|
3418 } |
|
3419 } |
|
3420 } |
|
3421 return res; |
|
3422 } |
|
3423 |
|
3424 NS_IMETHODIMP |
|
3425 nsHTMLEditor::DebugUnitTests(int32_t *outNumTests, int32_t *outNumTestsFailed) |
|
3426 { |
|
3427 #ifdef DEBUG |
|
3428 NS_ENSURE_TRUE(outNumTests && outNumTestsFailed, NS_ERROR_NULL_POINTER); |
|
3429 |
|
3430 TextEditorTest *tester = new TextEditorTest(); |
|
3431 NS_ENSURE_TRUE(tester, NS_ERROR_OUT_OF_MEMORY); |
|
3432 |
|
3433 tester->Run(this, outNumTests, outNumTestsFailed); |
|
3434 delete tester; |
|
3435 return NS_OK; |
|
3436 #else |
|
3437 return NS_ERROR_NOT_IMPLEMENTED; |
|
3438 #endif |
|
3439 } |
|
3440 |
|
3441 |
|
3442 NS_IMETHODIMP |
|
3443 nsHTMLEditor::StyleSheetLoaded(nsCSSStyleSheet* aSheet, bool aWasAlternate, |
|
3444 nsresult aStatus) |
|
3445 { |
|
3446 nsresult rv = NS_OK; |
|
3447 nsAutoEditBatch batchIt(this); |
|
3448 |
|
3449 if (!mLastStyleSheetURL.IsEmpty()) |
|
3450 RemoveStyleSheet(mLastStyleSheetURL); |
|
3451 |
|
3452 nsRefPtr<AddStyleSheetTxn> txn; |
|
3453 rv = CreateTxnForAddStyleSheet(aSheet, getter_AddRefs(txn)); |
|
3454 if (!txn) rv = NS_ERROR_NULL_POINTER; |
|
3455 if (NS_SUCCEEDED(rv)) |
|
3456 { |
|
3457 rv = DoTransaction(txn); |
|
3458 if (NS_SUCCEEDED(rv)) |
|
3459 { |
|
3460 // Get the URI, then url spec from the sheet |
|
3461 nsAutoCString spec; |
|
3462 rv = aSheet->GetSheetURI()->GetSpec(spec); |
|
3463 |
|
3464 if (NS_SUCCEEDED(rv)) |
|
3465 { |
|
3466 // Save it so we can remove before applying the next one |
|
3467 mLastStyleSheetURL.AssignWithConversion(spec.get()); |
|
3468 |
|
3469 // Also save in our arrays of urls and sheets |
|
3470 AddNewStyleSheetToList(mLastStyleSheetURL, aSheet); |
|
3471 } |
|
3472 } |
|
3473 } |
|
3474 |
|
3475 return NS_OK; |
|
3476 } |
|
3477 |
|
3478 |
|
3479 /** All editor operations which alter the doc should be prefaced |
|
3480 * with a call to StartOperation, naming the action and direction */ |
|
3481 NS_IMETHODIMP |
|
3482 nsHTMLEditor::StartOperation(EditAction opID, |
|
3483 nsIEditor::EDirection aDirection) |
|
3484 { |
|
3485 // Protect the edit rules object from dying |
|
3486 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
3487 |
|
3488 nsEditor::StartOperation(opID, aDirection); // will set mAction, mDirection |
|
3489 if (mRules) return mRules->BeforeEdit(mAction, mDirection); |
|
3490 return NS_OK; |
|
3491 } |
|
3492 |
|
3493 |
|
3494 /** All editor operations which alter the doc should be followed |
|
3495 * with a call to EndOperation */ |
|
3496 NS_IMETHODIMP |
|
3497 nsHTMLEditor::EndOperation() |
|
3498 { |
|
3499 // Protect the edit rules object from dying |
|
3500 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
3501 |
|
3502 // post processing |
|
3503 nsresult res = NS_OK; |
|
3504 if (mRules) res = mRules->AfterEdit(mAction, mDirection); |
|
3505 nsEditor::EndOperation(); // will clear mAction, mDirection |
|
3506 return res; |
|
3507 } |
|
3508 |
|
3509 bool |
|
3510 nsHTMLEditor::TagCanContainTag(nsIAtom* aParentTag, nsIAtom* aChildTag) |
|
3511 { |
|
3512 MOZ_ASSERT(aParentTag && aChildTag); |
|
3513 |
|
3514 nsIParserService* parserService = nsContentUtils::GetParserService(); |
|
3515 |
|
3516 int32_t childTagEnum; |
|
3517 // XXX Should this handle #cdata-section too? |
|
3518 if (aChildTag == nsGkAtoms::textTagName) { |
|
3519 childTagEnum = eHTMLTag_text; |
|
3520 } else { |
|
3521 childTagEnum = parserService->HTMLAtomTagToId(aChildTag); |
|
3522 } |
|
3523 |
|
3524 int32_t parentTagEnum = parserService->HTMLAtomTagToId(aParentTag); |
|
3525 return nsHTMLEditUtils::CanContain(parentTagEnum, childTagEnum); |
|
3526 } |
|
3527 |
|
3528 bool |
|
3529 nsHTMLEditor::IsContainer(nsIDOMNode *aNode) |
|
3530 { |
|
3531 if (!aNode) { |
|
3532 return false; |
|
3533 } |
|
3534 |
|
3535 nsAutoString stringTag; |
|
3536 |
|
3537 nsresult rv = aNode->GetNodeName(stringTag); |
|
3538 NS_ENSURE_SUCCESS(rv, false); |
|
3539 |
|
3540 int32_t tagEnum; |
|
3541 // XXX Should this handle #cdata-section too? |
|
3542 if (stringTag.EqualsLiteral("#text")) { |
|
3543 tagEnum = eHTMLTag_text; |
|
3544 } |
|
3545 else { |
|
3546 tagEnum = nsContentUtils::GetParserService()->HTMLStringTagToId(stringTag); |
|
3547 } |
|
3548 |
|
3549 return nsHTMLEditUtils::IsContainer(tagEnum); |
|
3550 } |
|
3551 |
|
3552 |
|
3553 NS_IMETHODIMP |
|
3554 nsHTMLEditor::SelectEntireDocument(nsISelection *aSelection) |
|
3555 { |
|
3556 if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; } |
|
3557 |
|
3558 // Protect the edit rules object from dying |
|
3559 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
3560 |
|
3561 // get editor root node |
|
3562 nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(GetRoot()); |
|
3563 |
|
3564 // is doc empty? |
|
3565 bool bDocIsEmpty; |
|
3566 nsresult res = mRules->DocumentIsEmpty(&bDocIsEmpty); |
|
3567 NS_ENSURE_SUCCESS(res, res); |
|
3568 |
|
3569 if (bDocIsEmpty) |
|
3570 { |
|
3571 // if its empty dont select entire doc - that would select the bogus node |
|
3572 return aSelection->Collapse(rootElement, 0); |
|
3573 } |
|
3574 |
|
3575 return nsEditor::SelectEntireDocument(aSelection); |
|
3576 } |
|
3577 |
|
3578 NS_IMETHODIMP |
|
3579 nsHTMLEditor::SelectAll() |
|
3580 { |
|
3581 ForceCompositionEnd(); |
|
3582 |
|
3583 nsresult rv; |
|
3584 nsCOMPtr<nsISelectionController> selCon; |
|
3585 rv = GetSelectionController(getter_AddRefs(selCon)); |
|
3586 NS_ENSURE_SUCCESS(rv, rv); |
|
3587 |
|
3588 nsCOMPtr<nsISelection> selection; |
|
3589 rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, |
|
3590 getter_AddRefs(selection)); |
|
3591 NS_ENSURE_SUCCESS(rv, rv); |
|
3592 |
|
3593 nsCOMPtr<nsIDOMNode> anchorNode; |
|
3594 rv = selection->GetAnchorNode(getter_AddRefs(anchorNode)); |
|
3595 NS_ENSURE_SUCCESS(rv, rv); |
|
3596 |
|
3597 nsCOMPtr<nsIContent> anchorContent = do_QueryInterface(anchorNode, &rv); |
|
3598 NS_ENSURE_SUCCESS(rv, rv); |
|
3599 |
|
3600 // If the anchor content has independent selection, we never need to explicitly |
|
3601 // select its children. |
|
3602 if (anchorContent->HasIndependentSelection()) { |
|
3603 nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection); |
|
3604 NS_ENSURE_TRUE(selPriv, NS_ERROR_UNEXPECTED); |
|
3605 rv = selPriv->SetAncestorLimiter(nullptr); |
|
3606 NS_ENSURE_SUCCESS(rv, rv); |
|
3607 nsCOMPtr<nsIDOMNode> rootElement = do_QueryInterface(mRootElement, &rv); |
|
3608 NS_ENSURE_SUCCESS(rv, rv); |
|
3609 return selection->SelectAllChildren(rootElement); |
|
3610 } |
|
3611 |
|
3612 nsCOMPtr<nsIPresShell> ps = GetPresShell(); |
|
3613 nsIContent *rootContent = anchorContent->GetSelectionRootContent(ps); |
|
3614 NS_ENSURE_TRUE(rootContent, NS_ERROR_UNEXPECTED); |
|
3615 |
|
3616 nsCOMPtr<nsIDOMNode> rootElement = do_QueryInterface(rootContent, &rv); |
|
3617 NS_ENSURE_SUCCESS(rv, rv); |
|
3618 |
|
3619 return selection->SelectAllChildren(rootElement); |
|
3620 } |
|
3621 |
|
3622 |
|
3623 // this will NOT find aAttribute unless aAttribute has a non-null value |
|
3624 // so singleton attributes like <Table border> will not be matched! |
|
3625 bool nsHTMLEditor::IsTextPropertySetByContent(nsIContent* aContent, |
|
3626 nsIAtom* aProperty, |
|
3627 const nsAString* aAttribute, |
|
3628 const nsAString* aValue, |
|
3629 nsAString* outValue) |
|
3630 { |
|
3631 MOZ_ASSERT(aContent && aProperty); |
|
3632 MOZ_ASSERT_IF(aAttribute, aValue); |
|
3633 bool isSet; |
|
3634 IsTextPropertySetByContent(aContent->AsDOMNode(), aProperty, aAttribute, |
|
3635 aValue, isSet, outValue); |
|
3636 return isSet; |
|
3637 } |
|
3638 |
|
3639 void nsHTMLEditor::IsTextPropertySetByContent(nsIDOMNode *aNode, |
|
3640 nsIAtom *aProperty, |
|
3641 const nsAString *aAttribute, |
|
3642 const nsAString *aValue, |
|
3643 bool &aIsSet, |
|
3644 nsAString *outValue) |
|
3645 { |
|
3646 nsresult result; |
|
3647 aIsSet = false; // must be initialized to false for code below to work |
|
3648 nsAutoString propName; |
|
3649 aProperty->ToString(propName); |
|
3650 nsCOMPtr<nsIDOMNode>node = aNode; |
|
3651 |
|
3652 while (node) |
|
3653 { |
|
3654 nsCOMPtr<nsIDOMElement>element; |
|
3655 element = do_QueryInterface(node); |
|
3656 if (element) |
|
3657 { |
|
3658 nsAutoString tag, value; |
|
3659 element->GetTagName(tag); |
|
3660 if (propName.Equals(tag, nsCaseInsensitiveStringComparator())) |
|
3661 { |
|
3662 bool found = false; |
|
3663 if (aAttribute && 0!=aAttribute->Length()) |
|
3664 { |
|
3665 element->GetAttribute(*aAttribute, value); |
|
3666 if (outValue) *outValue = value; |
|
3667 if (!value.IsEmpty()) |
|
3668 { |
|
3669 if (!aValue) { |
|
3670 found = true; |
|
3671 } |
|
3672 else |
|
3673 { |
|
3674 nsString tString(*aValue); |
|
3675 if (tString.Equals(value, nsCaseInsensitiveStringComparator())) { |
|
3676 found = true; |
|
3677 } |
|
3678 else { // we found the prop with the attribute, but the value doesn't match |
|
3679 break; |
|
3680 } |
|
3681 } |
|
3682 } |
|
3683 } |
|
3684 else { |
|
3685 found = true; |
|
3686 } |
|
3687 if (found) |
|
3688 { |
|
3689 aIsSet = true; |
|
3690 break; |
|
3691 } |
|
3692 } |
|
3693 } |
|
3694 nsCOMPtr<nsIDOMNode>temp; |
|
3695 result = node->GetParentNode(getter_AddRefs(temp)); |
|
3696 if (NS_SUCCEEDED(result) && temp) { |
|
3697 node = temp; |
|
3698 } |
|
3699 else { |
|
3700 node = nullptr; |
|
3701 } |
|
3702 } |
|
3703 } |
|
3704 |
|
3705 |
|
3706 //================================================================ |
|
3707 // HTML Editor methods |
|
3708 // |
|
3709 // Note: Table Editing methods are implemented in nsTableEditor.cpp |
|
3710 // |
|
3711 |
|
3712 |
|
3713 bool |
|
3714 nsHTMLEditor::SetCaretInTableCell(nsIDOMElement* aElement) |
|
3715 { |
|
3716 nsCOMPtr<dom::Element> element = do_QueryInterface(aElement); |
|
3717 if (!element || !element->IsHTML() || |
|
3718 !nsHTMLEditUtils::IsTableElement(element) || |
|
3719 !IsDescendantOfEditorRoot(element)) { |
|
3720 return false; |
|
3721 } |
|
3722 |
|
3723 nsIContent* node = element; |
|
3724 while (node->HasChildren()) { |
|
3725 node = node->GetFirstChild(); |
|
3726 } |
|
3727 |
|
3728 // Set selection at beginning of the found node |
|
3729 nsCOMPtr<nsISelection> selection; |
|
3730 nsresult rv = GetSelection(getter_AddRefs(selection)); |
|
3731 NS_ENSURE_SUCCESS(rv, false); |
|
3732 NS_ENSURE_TRUE(selection, false); |
|
3733 |
|
3734 return NS_SUCCEEDED(selection->CollapseNative(node, 0)); |
|
3735 } |
|
3736 |
|
3737 /////////////////////////////////////////////////////////////////////////// |
|
3738 // GetEnclosingTable: find ancestor who is a table, if any |
|
3739 // |
|
3740 nsCOMPtr<nsIDOMNode> |
|
3741 nsHTMLEditor::GetEnclosingTable(nsIDOMNode *aNode) |
|
3742 { |
|
3743 NS_PRECONDITION(aNode, "null node passed to nsHTMLEditor::GetEnclosingTable"); |
|
3744 nsCOMPtr<nsIDOMNode> tbl, tmp, node = aNode; |
|
3745 |
|
3746 while (!tbl) |
|
3747 { |
|
3748 tmp = GetBlockNodeParent(node); |
|
3749 if (!tmp) break; |
|
3750 if (nsHTMLEditUtils::IsTable(tmp)) tbl = tmp; |
|
3751 node = tmp; |
|
3752 } |
|
3753 return tbl; |
|
3754 } |
|
3755 |
|
3756 |
|
3757 /* this method scans the selection for adjacent text nodes |
|
3758 * and collapses them into a single text node. |
|
3759 * "adjacent" means literally adjacent siblings of the same parent. |
|
3760 * Uses nsEditor::JoinNodes so action is undoable. |
|
3761 * Should be called within the context of a batch transaction. |
|
3762 */ |
|
3763 NS_IMETHODIMP |
|
3764 nsHTMLEditor::CollapseAdjacentTextNodes(nsIDOMRange *aInRange) |
|
3765 { |
|
3766 NS_ENSURE_TRUE(aInRange, NS_ERROR_NULL_POINTER); |
|
3767 nsAutoTxnsConserveSelection dontSpazMySelection(this); |
|
3768 nsTArray<nsCOMPtr<nsIDOMNode> > textNodes; |
|
3769 // we can't actually do anything during iteration, so store the text nodes in an array |
|
3770 // don't bother ref counting them because we know we can hold them for the |
|
3771 // lifetime of this method |
|
3772 |
|
3773 |
|
3774 // build a list of editable text nodes |
|
3775 nsresult result; |
|
3776 nsCOMPtr<nsIContentIterator> iter = |
|
3777 do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &result); |
|
3778 NS_ENSURE_SUCCESS(result, result); |
|
3779 |
|
3780 iter->Init(aInRange); |
|
3781 |
|
3782 while (!iter->IsDone()) |
|
3783 { |
|
3784 nsINode* node = iter->GetCurrentNode(); |
|
3785 if (node->NodeType() == nsIDOMNode::TEXT_NODE && |
|
3786 IsEditable(static_cast<nsIContent*>(node))) { |
|
3787 nsCOMPtr<nsIDOMNode> domNode = do_QueryInterface(node); |
|
3788 textNodes.AppendElement(domNode); |
|
3789 } |
|
3790 |
|
3791 iter->Next(); |
|
3792 } |
|
3793 |
|
3794 // now that I have a list of text nodes, collapse adjacent text nodes |
|
3795 // NOTE: assumption that JoinNodes keeps the righthand node |
|
3796 while (textNodes.Length() > 1) |
|
3797 { |
|
3798 // we assume a textNodes entry can't be nullptr |
|
3799 nsIDOMNode *leftTextNode = textNodes[0]; |
|
3800 nsIDOMNode *rightTextNode = textNodes[1]; |
|
3801 NS_ASSERTION(leftTextNode && rightTextNode,"left or rightTextNode null in CollapseAdjacentTextNodes"); |
|
3802 |
|
3803 // get the prev sibling of the right node, and see if its leftTextNode |
|
3804 nsCOMPtr<nsIDOMNode> prevSibOfRightNode; |
|
3805 result = |
|
3806 rightTextNode->GetPreviousSibling(getter_AddRefs(prevSibOfRightNode)); |
|
3807 NS_ENSURE_SUCCESS(result, result); |
|
3808 if (prevSibOfRightNode && (prevSibOfRightNode == leftTextNode)) |
|
3809 { |
|
3810 nsCOMPtr<nsIDOMNode> parent; |
|
3811 result = rightTextNode->GetParentNode(getter_AddRefs(parent)); |
|
3812 NS_ENSURE_SUCCESS(result, result); |
|
3813 NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); |
|
3814 result = JoinNodes(leftTextNode, rightTextNode, parent); |
|
3815 NS_ENSURE_SUCCESS(result, result); |
|
3816 } |
|
3817 |
|
3818 textNodes.RemoveElementAt(0); // remove the leftmost text node from the list |
|
3819 } |
|
3820 |
|
3821 return result; |
|
3822 } |
|
3823 |
|
3824 NS_IMETHODIMP |
|
3825 nsHTMLEditor::SetSelectionAtDocumentStart(nsISelection *aSelection) |
|
3826 { |
|
3827 dom::Element* rootElement = GetRoot(); |
|
3828 NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER); |
|
3829 |
|
3830 return aSelection->CollapseNative(rootElement, 0); |
|
3831 } |
|
3832 |
|
3833 |
|
3834 /////////////////////////////////////////////////////////////////////////// |
|
3835 // RemoveBlockContainer: remove inNode, reparenting its children into their |
|
3836 // the parent of inNode. In addition, INSERT ANY BR's NEEDED |
|
3837 // TO PRESERVE IDENTITY OF REMOVED BLOCK. |
|
3838 // |
|
3839 nsresult |
|
3840 nsHTMLEditor::RemoveBlockContainer(nsIDOMNode *inNode) |
|
3841 { |
|
3842 NS_ENSURE_TRUE(inNode, NS_ERROR_NULL_POINTER); |
|
3843 nsresult res; |
|
3844 nsCOMPtr<nsIDOMNode> sibling, child, unused; |
|
3845 |
|
3846 // Two possibilities: the container cold be empty of editable content. |
|
3847 // If that is the case, we need to compare what is before and after inNode |
|
3848 // to determine if we need a br. |
|
3849 // Or it could not be empty, in which case we have to compare previous |
|
3850 // sibling and first child to determine if we need a leading br, |
|
3851 // and compare following sibling and last child to determine if we need a |
|
3852 // trailing br. |
|
3853 |
|
3854 res = GetFirstEditableChild(inNode, address_of(child)); |
|
3855 NS_ENSURE_SUCCESS(res, res); |
|
3856 |
|
3857 if (child) // the case of inNode not being empty |
|
3858 { |
|
3859 // we need a br at start unless: |
|
3860 // 1) previous sibling of inNode is a block, OR |
|
3861 // 2) previous sibling of inNode is a br, OR |
|
3862 // 3) first child of inNode is a block OR |
|
3863 // 4) either is null |
|
3864 |
|
3865 res = GetPriorHTMLSibling(inNode, address_of(sibling)); |
|
3866 NS_ENSURE_SUCCESS(res, res); |
|
3867 if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling)) |
|
3868 { |
|
3869 res = GetFirstEditableChild(inNode, address_of(child)); |
|
3870 NS_ENSURE_SUCCESS(res, res); |
|
3871 if (child && !IsBlockNode(child)) |
|
3872 { |
|
3873 // insert br node |
|
3874 res = CreateBR(inNode, 0, address_of(unused)); |
|
3875 NS_ENSURE_SUCCESS(res, res); |
|
3876 } |
|
3877 } |
|
3878 |
|
3879 // we need a br at end unless: |
|
3880 // 1) following sibling of inNode is a block, OR |
|
3881 // 2) last child of inNode is a block, OR |
|
3882 // 3) last child of inNode is a block OR |
|
3883 // 4) either is null |
|
3884 |
|
3885 res = GetNextHTMLSibling(inNode, address_of(sibling)); |
|
3886 NS_ENSURE_SUCCESS(res, res); |
|
3887 if (sibling && !IsBlockNode(sibling)) |
|
3888 { |
|
3889 res = GetLastEditableChild(inNode, address_of(child)); |
|
3890 NS_ENSURE_SUCCESS(res, res); |
|
3891 if (child && !IsBlockNode(child) && !nsTextEditUtils::IsBreak(child)) |
|
3892 { |
|
3893 // insert br node |
|
3894 uint32_t len; |
|
3895 res = GetLengthOfDOMNode(inNode, len); |
|
3896 NS_ENSURE_SUCCESS(res, res); |
|
3897 res = CreateBR(inNode, (int32_t)len, address_of(unused)); |
|
3898 NS_ENSURE_SUCCESS(res, res); |
|
3899 } |
|
3900 } |
|
3901 } |
|
3902 else // the case of inNode being empty |
|
3903 { |
|
3904 // we need a br at start unless: |
|
3905 // 1) previous sibling of inNode is a block, OR |
|
3906 // 2) previous sibling of inNode is a br, OR |
|
3907 // 3) following sibling of inNode is a block, OR |
|
3908 // 4) following sibling of inNode is a br OR |
|
3909 // 5) either is null |
|
3910 res = GetPriorHTMLSibling(inNode, address_of(sibling)); |
|
3911 NS_ENSURE_SUCCESS(res, res); |
|
3912 if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling)) |
|
3913 { |
|
3914 res = GetNextHTMLSibling(inNode, address_of(sibling)); |
|
3915 NS_ENSURE_SUCCESS(res, res); |
|
3916 if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling)) |
|
3917 { |
|
3918 // insert br node |
|
3919 res = CreateBR(inNode, 0, address_of(unused)); |
|
3920 NS_ENSURE_SUCCESS(res, res); |
|
3921 } |
|
3922 } |
|
3923 } |
|
3924 |
|
3925 // now remove container |
|
3926 return RemoveContainer(inNode); |
|
3927 } |
|
3928 |
|
3929 |
|
3930 /////////////////////////////////////////////////////////////////////////// |
|
3931 // GetPriorHTMLSibling: returns the previous editable sibling, if there is |
|
3932 // one within the parent |
|
3933 // |
|
3934 nsIContent* |
|
3935 nsHTMLEditor::GetPriorHTMLSibling(nsINode* aNode) |
|
3936 { |
|
3937 MOZ_ASSERT(aNode); |
|
3938 |
|
3939 nsIContent* node = aNode->GetPreviousSibling(); |
|
3940 while (node && !IsEditable(node)) { |
|
3941 node = node->GetPreviousSibling(); |
|
3942 } |
|
3943 |
|
3944 return node; |
|
3945 } |
|
3946 |
|
3947 nsresult |
|
3948 nsHTMLEditor::GetPriorHTMLSibling(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode) |
|
3949 { |
|
3950 NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER); |
|
3951 *outNode = nullptr; |
|
3952 |
|
3953 nsCOMPtr<nsINode> node = do_QueryInterface(inNode); |
|
3954 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); |
|
3955 |
|
3956 *outNode = do_QueryInterface(GetPriorHTMLSibling(node)); |
|
3957 return NS_OK; |
|
3958 } |
|
3959 |
|
3960 |
|
3961 |
|
3962 /////////////////////////////////////////////////////////////////////////// |
|
3963 // GetPriorHTMLSibling: returns the previous editable sibling, if there is |
|
3964 // one within the parent. just like above routine but |
|
3965 // takes a parent/offset instead of a node. |
|
3966 // |
|
3967 nsIContent* |
|
3968 nsHTMLEditor::GetPriorHTMLSibling(nsINode* aParent, int32_t aOffset) |
|
3969 { |
|
3970 MOZ_ASSERT(aParent); |
|
3971 |
|
3972 nsIContent* node = aParent->GetChildAt(aOffset - 1); |
|
3973 if (!node || IsEditable(node)) { |
|
3974 return node; |
|
3975 } |
|
3976 |
|
3977 return GetPriorHTMLSibling(node); |
|
3978 } |
|
3979 |
|
3980 nsresult |
|
3981 nsHTMLEditor::GetPriorHTMLSibling(nsIDOMNode *inParent, int32_t inOffset, nsCOMPtr<nsIDOMNode> *outNode) |
|
3982 { |
|
3983 NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER); |
|
3984 *outNode = nullptr; |
|
3985 |
|
3986 nsCOMPtr<nsINode> parent = do_QueryInterface(inParent); |
|
3987 NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); |
|
3988 |
|
3989 *outNode = do_QueryInterface(GetPriorHTMLSibling(parent, inOffset)); |
|
3990 return NS_OK; |
|
3991 } |
|
3992 |
|
3993 |
|
3994 |
|
3995 /////////////////////////////////////////////////////////////////////////// |
|
3996 // GetNextHTMLSibling: returns the next editable sibling, if there is |
|
3997 // one within the parent |
|
3998 // |
|
3999 nsIContent* |
|
4000 nsHTMLEditor::GetNextHTMLSibling(nsINode* aNode) |
|
4001 { |
|
4002 MOZ_ASSERT(aNode); |
|
4003 |
|
4004 nsIContent* node = aNode->GetNextSibling(); |
|
4005 while (node && !IsEditable(node)) { |
|
4006 node = node->GetNextSibling(); |
|
4007 } |
|
4008 |
|
4009 return node; |
|
4010 } |
|
4011 |
|
4012 nsresult |
|
4013 nsHTMLEditor::GetNextHTMLSibling(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode) |
|
4014 { |
|
4015 NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER); |
|
4016 *outNode = nullptr; |
|
4017 |
|
4018 nsCOMPtr<nsINode> node = do_QueryInterface(inNode); |
|
4019 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); |
|
4020 |
|
4021 *outNode = do_QueryInterface(GetNextHTMLSibling(node)); |
|
4022 return NS_OK; |
|
4023 } |
|
4024 |
|
4025 |
|
4026 |
|
4027 /////////////////////////////////////////////////////////////////////////// |
|
4028 // GetNextHTMLSibling: returns the next editable sibling, if there is |
|
4029 // one within the parent. just like above routine but |
|
4030 // takes a parent/offset instead of a node. |
|
4031 nsIContent* |
|
4032 nsHTMLEditor::GetNextHTMLSibling(nsINode* aParent, int32_t aOffset) |
|
4033 { |
|
4034 MOZ_ASSERT(aParent); |
|
4035 |
|
4036 nsIContent* node = aParent->GetChildAt(aOffset + 1); |
|
4037 if (!node || IsEditable(node)) { |
|
4038 return node; |
|
4039 } |
|
4040 |
|
4041 return GetNextHTMLSibling(node); |
|
4042 } |
|
4043 |
|
4044 nsresult |
|
4045 nsHTMLEditor::GetNextHTMLSibling(nsIDOMNode *inParent, int32_t inOffset, nsCOMPtr<nsIDOMNode> *outNode) |
|
4046 { |
|
4047 NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER); |
|
4048 *outNode = nullptr; |
|
4049 |
|
4050 nsCOMPtr<nsINode> parent = do_QueryInterface(inParent); |
|
4051 NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); |
|
4052 |
|
4053 *outNode = do_QueryInterface(GetNextHTMLSibling(parent, inOffset)); |
|
4054 return NS_OK; |
|
4055 } |
|
4056 |
|
4057 |
|
4058 |
|
4059 /////////////////////////////////////////////////////////////////////////// |
|
4060 // GetPriorHTMLNode: returns the previous editable leaf node, if there is |
|
4061 // one within the <body> |
|
4062 // |
|
4063 nsIContent* |
|
4064 nsHTMLEditor::GetPriorHTMLNode(nsINode* aNode, bool aNoBlockCrossing) |
|
4065 { |
|
4066 MOZ_ASSERT(aNode); |
|
4067 |
|
4068 if (!GetActiveEditingHost()) { |
|
4069 return nullptr; |
|
4070 } |
|
4071 |
|
4072 return GetPriorNode(aNode, true, aNoBlockCrossing); |
|
4073 } |
|
4074 |
|
4075 nsresult |
|
4076 nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode* aNode, |
|
4077 nsCOMPtr<nsIDOMNode>* aResultNode, |
|
4078 bool aNoBlockCrossing) |
|
4079 { |
|
4080 NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); |
|
4081 |
|
4082 nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
|
4083 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); |
|
4084 |
|
4085 *aResultNode = do_QueryInterface(GetPriorHTMLNode(node, aNoBlockCrossing)); |
|
4086 return NS_OK; |
|
4087 } |
|
4088 |
|
4089 |
|
4090 /////////////////////////////////////////////////////////////////////////// |
|
4091 // GetPriorHTMLNode: same as above but takes {parent,offset} instead of node |
|
4092 // |
|
4093 nsIContent* |
|
4094 nsHTMLEditor::GetPriorHTMLNode(nsINode* aParent, int32_t aOffset, |
|
4095 bool aNoBlockCrossing) |
|
4096 { |
|
4097 MOZ_ASSERT(aParent); |
|
4098 |
|
4099 if (!GetActiveEditingHost()) { |
|
4100 return nullptr; |
|
4101 } |
|
4102 |
|
4103 return GetPriorNode(aParent, aOffset, true, aNoBlockCrossing); |
|
4104 } |
|
4105 |
|
4106 nsresult |
|
4107 nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode* aNode, int32_t aOffset, |
|
4108 nsCOMPtr<nsIDOMNode>* aResultNode, |
|
4109 bool aNoBlockCrossing) |
|
4110 { |
|
4111 NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); |
|
4112 |
|
4113 nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
|
4114 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); |
|
4115 |
|
4116 *aResultNode = do_QueryInterface(GetPriorHTMLNode(node, aOffset, |
|
4117 aNoBlockCrossing)); |
|
4118 return NS_OK; |
|
4119 } |
|
4120 |
|
4121 |
|
4122 /////////////////////////////////////////////////////////////////////////// |
|
4123 // GetNextHTMLNode: returns the next editable leaf node, if there is |
|
4124 // one within the <body> |
|
4125 // |
|
4126 nsIContent* |
|
4127 nsHTMLEditor::GetNextHTMLNode(nsINode* aNode, bool aNoBlockCrossing) |
|
4128 { |
|
4129 MOZ_ASSERT(aNode); |
|
4130 |
|
4131 nsIContent* result = GetNextNode(aNode, true, aNoBlockCrossing); |
|
4132 |
|
4133 if (result && !IsDescendantOfEditorRoot(result)) { |
|
4134 return nullptr; |
|
4135 } |
|
4136 |
|
4137 return result; |
|
4138 } |
|
4139 |
|
4140 nsresult |
|
4141 nsHTMLEditor::GetNextHTMLNode(nsIDOMNode* aNode, |
|
4142 nsCOMPtr<nsIDOMNode>* aResultNode, |
|
4143 bool aNoBlockCrossing) |
|
4144 { |
|
4145 NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); |
|
4146 |
|
4147 nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
|
4148 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); |
|
4149 |
|
4150 *aResultNode = do_QueryInterface(GetNextHTMLNode(node, aNoBlockCrossing)); |
|
4151 return NS_OK; |
|
4152 } |
|
4153 |
|
4154 |
|
4155 /////////////////////////////////////////////////////////////////////////// |
|
4156 // GetNextHTMLNode: same as above but takes {parent,offset} instead of node |
|
4157 // |
|
4158 nsIContent* |
|
4159 nsHTMLEditor::GetNextHTMLNode(nsINode* aParent, int32_t aOffset, |
|
4160 bool aNoBlockCrossing) |
|
4161 { |
|
4162 nsIContent* content = GetNextNode(aParent, aOffset, true, aNoBlockCrossing); |
|
4163 if (content && !IsDescendantOfEditorRoot(content)) { |
|
4164 return nullptr; |
|
4165 } |
|
4166 return content; |
|
4167 } |
|
4168 |
|
4169 nsresult |
|
4170 nsHTMLEditor::GetNextHTMLNode(nsIDOMNode* aNode, int32_t aOffset, |
|
4171 nsCOMPtr<nsIDOMNode>* aResultNode, |
|
4172 bool aNoBlockCrossing) |
|
4173 { |
|
4174 NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); |
|
4175 |
|
4176 nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
|
4177 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); |
|
4178 |
|
4179 *aResultNode = do_QueryInterface(GetNextHTMLNode(node, aOffset, |
|
4180 aNoBlockCrossing)); |
|
4181 return NS_OK; |
|
4182 } |
|
4183 |
|
4184 |
|
4185 nsresult |
|
4186 nsHTMLEditor::IsFirstEditableChild( nsIDOMNode *aNode, bool *aOutIsFirst) |
|
4187 { |
|
4188 // check parms |
|
4189 NS_ENSURE_TRUE(aOutIsFirst && aNode, NS_ERROR_NULL_POINTER); |
|
4190 |
|
4191 // init out parms |
|
4192 *aOutIsFirst = false; |
|
4193 |
|
4194 // find first editable child and compare it to aNode |
|
4195 nsCOMPtr<nsIDOMNode> parent, firstChild; |
|
4196 nsresult res = aNode->GetParentNode(getter_AddRefs(parent)); |
|
4197 NS_ENSURE_SUCCESS(res, res); |
|
4198 NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE); |
|
4199 res = GetFirstEditableChild(parent, address_of(firstChild)); |
|
4200 NS_ENSURE_SUCCESS(res, res); |
|
4201 |
|
4202 *aOutIsFirst = (firstChild.get() == aNode); |
|
4203 return res; |
|
4204 } |
|
4205 |
|
4206 |
|
4207 nsresult |
|
4208 nsHTMLEditor::IsLastEditableChild( nsIDOMNode *aNode, bool *aOutIsLast) |
|
4209 { |
|
4210 // check parms |
|
4211 NS_ENSURE_TRUE(aOutIsLast && aNode, NS_ERROR_NULL_POINTER); |
|
4212 |
|
4213 // init out parms |
|
4214 *aOutIsLast = false; |
|
4215 |
|
4216 // find last editable child and compare it to aNode |
|
4217 nsCOMPtr<nsIDOMNode> parent, lastChild; |
|
4218 nsresult res = aNode->GetParentNode(getter_AddRefs(parent)); |
|
4219 NS_ENSURE_SUCCESS(res, res); |
|
4220 NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE); |
|
4221 res = GetLastEditableChild(parent, address_of(lastChild)); |
|
4222 NS_ENSURE_SUCCESS(res, res); |
|
4223 |
|
4224 *aOutIsLast = (lastChild.get() == aNode); |
|
4225 return res; |
|
4226 } |
|
4227 |
|
4228 |
|
4229 nsresult |
|
4230 nsHTMLEditor::GetFirstEditableChild( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutFirstChild) |
|
4231 { |
|
4232 // check parms |
|
4233 NS_ENSURE_TRUE(aOutFirstChild && aNode, NS_ERROR_NULL_POINTER); |
|
4234 |
|
4235 // init out parms |
|
4236 *aOutFirstChild = nullptr; |
|
4237 |
|
4238 // find first editable child |
|
4239 nsCOMPtr<nsIDOMNode> child; |
|
4240 nsresult res = aNode->GetFirstChild(getter_AddRefs(child)); |
|
4241 NS_ENSURE_SUCCESS(res, res); |
|
4242 |
|
4243 while (child && !IsEditable(child)) |
|
4244 { |
|
4245 nsCOMPtr<nsIDOMNode> tmp; |
|
4246 res = child->GetNextSibling(getter_AddRefs(tmp)); |
|
4247 NS_ENSURE_SUCCESS(res, res); |
|
4248 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); |
|
4249 child = tmp; |
|
4250 } |
|
4251 |
|
4252 *aOutFirstChild = child; |
|
4253 return res; |
|
4254 } |
|
4255 |
|
4256 |
|
4257 nsresult |
|
4258 nsHTMLEditor::GetLastEditableChild( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutLastChild) |
|
4259 { |
|
4260 // check parms |
|
4261 NS_ENSURE_TRUE(aOutLastChild && aNode, NS_ERROR_NULL_POINTER); |
|
4262 |
|
4263 // init out parms |
|
4264 *aOutLastChild = aNode; |
|
4265 |
|
4266 // find last editable child |
|
4267 nsCOMPtr<nsIDOMNode> child; |
|
4268 nsresult res = aNode->GetLastChild(getter_AddRefs(child)); |
|
4269 NS_ENSURE_SUCCESS(res, res); |
|
4270 |
|
4271 while (child && !IsEditable(child)) |
|
4272 { |
|
4273 nsCOMPtr<nsIDOMNode> tmp; |
|
4274 res = child->GetPreviousSibling(getter_AddRefs(tmp)); |
|
4275 NS_ENSURE_SUCCESS(res, res); |
|
4276 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); |
|
4277 child = tmp; |
|
4278 } |
|
4279 |
|
4280 *aOutLastChild = child; |
|
4281 return res; |
|
4282 } |
|
4283 |
|
4284 nsresult |
|
4285 nsHTMLEditor::GetFirstEditableLeaf( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutFirstLeaf) |
|
4286 { |
|
4287 // check parms |
|
4288 NS_ENSURE_TRUE(aOutFirstLeaf && aNode, NS_ERROR_NULL_POINTER); |
|
4289 |
|
4290 // init out parms |
|
4291 *aOutFirstLeaf = aNode; |
|
4292 |
|
4293 // find leftmost leaf |
|
4294 nsCOMPtr<nsIDOMNode> child; |
|
4295 nsresult res = NS_OK; |
|
4296 child = GetLeftmostChild(aNode); |
|
4297 while (child && (!IsEditable(child) || !nsEditorUtils::IsLeafNode(child))) |
|
4298 { |
|
4299 nsCOMPtr<nsIDOMNode> tmp; |
|
4300 res = GetNextHTMLNode(child, address_of(tmp)); |
|
4301 NS_ENSURE_SUCCESS(res, res); |
|
4302 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); |
|
4303 |
|
4304 // only accept nodes that are descendants of aNode |
|
4305 if (nsEditorUtils::IsDescendantOf(tmp, aNode)) |
|
4306 child = tmp; |
|
4307 else |
|
4308 { |
|
4309 child = nullptr; // this will abort the loop |
|
4310 } |
|
4311 } |
|
4312 |
|
4313 *aOutFirstLeaf = child; |
|
4314 return res; |
|
4315 } |
|
4316 |
|
4317 |
|
4318 nsresult |
|
4319 nsHTMLEditor::GetLastEditableLeaf(nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutLastLeaf) |
|
4320 { |
|
4321 // check parms |
|
4322 NS_ENSURE_TRUE(aOutLastLeaf && aNode, NS_ERROR_NULL_POINTER); |
|
4323 |
|
4324 // init out parms |
|
4325 *aOutLastLeaf = nullptr; |
|
4326 |
|
4327 // find rightmost leaf |
|
4328 nsCOMPtr<nsIDOMNode> child = GetRightmostChild(aNode, false); |
|
4329 nsresult res = NS_OK; |
|
4330 while (child && (!IsEditable(child) || !nsEditorUtils::IsLeafNode(child))) |
|
4331 { |
|
4332 nsCOMPtr<nsIDOMNode> tmp; |
|
4333 res = GetPriorHTMLNode(child, address_of(tmp)); |
|
4334 NS_ENSURE_SUCCESS(res, res); |
|
4335 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); |
|
4336 |
|
4337 // only accept nodes that are descendants of aNode |
|
4338 if (nsEditorUtils::IsDescendantOf(tmp, aNode)) |
|
4339 child = tmp; |
|
4340 else |
|
4341 { |
|
4342 child = nullptr; |
|
4343 } |
|
4344 } |
|
4345 |
|
4346 *aOutLastLeaf = child; |
|
4347 return res; |
|
4348 } |
|
4349 |
|
4350 |
|
4351 /////////////////////////////////////////////////////////////////////////// |
|
4352 // IsVisTextNode: figure out if textnode aTextNode has any visible content. |
|
4353 // |
|
4354 nsresult |
|
4355 nsHTMLEditor::IsVisTextNode(nsIContent* aNode, |
|
4356 bool* outIsEmptyNode, |
|
4357 bool aSafeToAskFrames) |
|
4358 { |
|
4359 MOZ_ASSERT(aNode); |
|
4360 MOZ_ASSERT(aNode->NodeType() == nsIDOMNode::TEXT_NODE); |
|
4361 MOZ_ASSERT(outIsEmptyNode); |
|
4362 |
|
4363 *outIsEmptyNode = true; |
|
4364 |
|
4365 uint32_t length = aNode->TextLength(); |
|
4366 if (aSafeToAskFrames) |
|
4367 { |
|
4368 nsCOMPtr<nsISelectionController> selCon; |
|
4369 nsresult res = GetSelectionController(getter_AddRefs(selCon)); |
|
4370 NS_ENSURE_SUCCESS(res, res); |
|
4371 NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE); |
|
4372 bool isVisible = false; |
|
4373 // ask the selection controller for information about whether any |
|
4374 // of the data in the node is really rendered. This is really |
|
4375 // something that frames know about, but we aren't supposed to talk to frames. |
|
4376 // So we put a call in the selection controller interface, since it's already |
|
4377 // in bed with frames anyway. (this is a fix for bug 22227, and a |
|
4378 // partial fix for bug 46209) |
|
4379 res = selCon->CheckVisibilityContent(aNode, 0, length, &isVisible); |
|
4380 NS_ENSURE_SUCCESS(res, res); |
|
4381 if (isVisible) |
|
4382 { |
|
4383 *outIsEmptyNode = false; |
|
4384 } |
|
4385 } |
|
4386 else if (length) |
|
4387 { |
|
4388 if (aNode->TextIsOnlyWhitespace()) |
|
4389 { |
|
4390 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode); |
|
4391 nsWSRunObject wsRunObj(this, node, 0); |
|
4392 nsCOMPtr<nsIDOMNode> visNode; |
|
4393 int32_t outVisOffset=0; |
|
4394 WSType visType; |
|
4395 wsRunObj.NextVisibleNode(node, 0, address_of(visNode), |
|
4396 &outVisOffset, &visType); |
|
4397 if (visType == WSType::normalWS || visType == WSType::text) { |
|
4398 *outIsEmptyNode = (node != visNode); |
|
4399 } |
|
4400 } |
|
4401 else |
|
4402 { |
|
4403 *outIsEmptyNode = false; |
|
4404 } |
|
4405 } |
|
4406 return NS_OK; |
|
4407 } |
|
4408 |
|
4409 |
|
4410 /////////////////////////////////////////////////////////////////////////// |
|
4411 // IsEmptyNode: figure out if aNode is an empty node. |
|
4412 // A block can have children and still be considered empty, |
|
4413 // if the children are empty or non-editable. |
|
4414 // |
|
4415 nsresult |
|
4416 nsHTMLEditor::IsEmptyNode( nsIDOMNode *aNode, |
|
4417 bool *outIsEmptyNode, |
|
4418 bool aSingleBRDoesntCount, |
|
4419 bool aListOrCellNotEmpty, |
|
4420 bool aSafeToAskFrames) |
|
4421 { |
|
4422 nsCOMPtr<nsINode> node = do_QueryInterface(aNode); |
|
4423 return IsEmptyNode(node, outIsEmptyNode, aSingleBRDoesntCount, |
|
4424 aListOrCellNotEmpty, aSafeToAskFrames); |
|
4425 } |
|
4426 |
|
4427 nsresult |
|
4428 nsHTMLEditor::IsEmptyNode(nsINode* aNode, |
|
4429 bool* outIsEmptyNode, |
|
4430 bool aSingleBRDoesntCount, |
|
4431 bool aListOrCellNotEmpty, |
|
4432 bool aSafeToAskFrames) |
|
4433 { |
|
4434 NS_ENSURE_TRUE(aNode && outIsEmptyNode, NS_ERROR_NULL_POINTER); |
|
4435 *outIsEmptyNode = true; |
|
4436 bool seenBR = false; |
|
4437 return IsEmptyNodeImpl(aNode, outIsEmptyNode, aSingleBRDoesntCount, |
|
4438 aListOrCellNotEmpty, aSafeToAskFrames, &seenBR); |
|
4439 } |
|
4440 |
|
4441 /////////////////////////////////////////////////////////////////////////// |
|
4442 // IsEmptyNodeImpl: workhorse for IsEmptyNode. |
|
4443 // |
|
4444 nsresult |
|
4445 nsHTMLEditor::IsEmptyNodeImpl(nsINode* aNode, |
|
4446 bool *outIsEmptyNode, |
|
4447 bool aSingleBRDoesntCount, |
|
4448 bool aListOrCellNotEmpty, |
|
4449 bool aSafeToAskFrames, |
|
4450 bool *aSeenBR) |
|
4451 { |
|
4452 NS_ENSURE_TRUE(aNode && outIsEmptyNode && aSeenBR, NS_ERROR_NULL_POINTER); |
|
4453 |
|
4454 if (aNode->NodeType() == nsIDOMNode::TEXT_NODE) { |
|
4455 return IsVisTextNode(static_cast<nsIContent*>(aNode), outIsEmptyNode, aSafeToAskFrames); |
|
4456 } |
|
4457 |
|
4458 // if it's not a text node (handled above) and it's not a container, |
|
4459 // then we don't call it empty (it's an <hr>, or <br>, etc). |
|
4460 // Also, if it's an anchor then don't treat it as empty - even though |
|
4461 // anchors are containers, named anchors are "empty" but we don't |
|
4462 // want to treat them as such. Also, don't call ListItems or table |
|
4463 // cells empty if caller desires. Form Widgets not empty. |
|
4464 if (!IsContainer(aNode->AsDOMNode()) || |
|
4465 (nsHTMLEditUtils::IsNamedAnchor(aNode) || |
|
4466 nsHTMLEditUtils::IsFormWidget(aNode) || |
|
4467 (aListOrCellNotEmpty && |
|
4468 (nsHTMLEditUtils::IsListItem(aNode) || |
|
4469 nsHTMLEditUtils::IsTableCell(aNode))))) { |
|
4470 *outIsEmptyNode = false; |
|
4471 return NS_OK; |
|
4472 } |
|
4473 |
|
4474 // need this for later |
|
4475 bool isListItemOrCell = nsHTMLEditUtils::IsListItem(aNode) || |
|
4476 nsHTMLEditUtils::IsTableCell(aNode); |
|
4477 |
|
4478 // loop over children of node. if no children, or all children are either |
|
4479 // empty text nodes or non-editable, then node qualifies as empty |
|
4480 for (nsCOMPtr<nsIContent> child = aNode->GetFirstChild(); |
|
4481 child; |
|
4482 child = child->GetNextSibling()) { |
|
4483 // Is the child editable and non-empty? if so, return false |
|
4484 if (nsEditor::IsEditable(child)) { |
|
4485 if (child->NodeType() == nsIDOMNode::TEXT_NODE) { |
|
4486 nsresult rv = IsVisTextNode(child, outIsEmptyNode, aSafeToAskFrames); |
|
4487 NS_ENSURE_SUCCESS(rv, rv); |
|
4488 // break out if we find we aren't emtpy |
|
4489 if (!*outIsEmptyNode) { |
|
4490 return NS_OK; |
|
4491 } |
|
4492 } else { |
|
4493 // An editable, non-text node. We need to check its content. |
|
4494 // Is it the node we are iterating over? |
|
4495 if (child == aNode) { |
|
4496 break; |
|
4497 } |
|
4498 |
|
4499 if (aSingleBRDoesntCount && !*aSeenBR && child->IsHTML(nsGkAtoms::br)) { |
|
4500 // the first br in a block doesn't count if the caller so indicated |
|
4501 *aSeenBR = true; |
|
4502 } else { |
|
4503 // is it an empty node of some sort? |
|
4504 // note: list items or table cells are not considered empty |
|
4505 // if they contain other lists or tables |
|
4506 if (child->IsElement()) { |
|
4507 if (isListItemOrCell) { |
|
4508 if (nsHTMLEditUtils::IsList(child) || |
|
4509 child->IsHTML(nsGkAtoms::table)) { |
|
4510 // break out if we find we aren't empty |
|
4511 *outIsEmptyNode = false; |
|
4512 return NS_OK; |
|
4513 } |
|
4514 } else if (nsHTMLEditUtils::IsFormWidget(child)) { |
|
4515 // is it a form widget? |
|
4516 // break out if we find we aren't empty |
|
4517 *outIsEmptyNode = false; |
|
4518 return NS_OK; |
|
4519 } |
|
4520 } |
|
4521 |
|
4522 bool isEmptyNode = true; |
|
4523 nsresult rv = IsEmptyNodeImpl(child, &isEmptyNode, |
|
4524 aSingleBRDoesntCount, |
|
4525 aListOrCellNotEmpty, aSafeToAskFrames, |
|
4526 aSeenBR); |
|
4527 NS_ENSURE_SUCCESS(rv, rv); |
|
4528 if (!isEmptyNode) { |
|
4529 // otherwise it ain't empty |
|
4530 *outIsEmptyNode = false; |
|
4531 return NS_OK; |
|
4532 } |
|
4533 } |
|
4534 } |
|
4535 } |
|
4536 } |
|
4537 |
|
4538 return NS_OK; |
|
4539 } |
|
4540 |
|
4541 // add to aElement the CSS inline styles corresponding to the HTML attribute |
|
4542 // aAttribute with its value aValue |
|
4543 nsresult |
|
4544 nsHTMLEditor::SetAttributeOrEquivalent(nsIDOMElement * aElement, |
|
4545 const nsAString & aAttribute, |
|
4546 const nsAString & aValue, |
|
4547 bool aSuppressTransaction) |
|
4548 { |
|
4549 nsAutoScriptBlocker scriptBlocker; |
|
4550 |
|
4551 nsresult res = NS_OK; |
|
4552 if (IsCSSEnabled() && mHTMLCSSUtils) { |
|
4553 int32_t count; |
|
4554 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(aElement, nullptr, &aAttribute, &aValue, &count, |
|
4555 aSuppressTransaction); |
|
4556 NS_ENSURE_SUCCESS(res, res); |
|
4557 if (count) { |
|
4558 // we found an equivalence ; let's remove the HTML attribute itself if it is set |
|
4559 nsAutoString existingValue; |
|
4560 bool wasSet = false; |
|
4561 res = GetAttributeValue(aElement, aAttribute, existingValue, &wasSet); |
|
4562 NS_ENSURE_SUCCESS(res, res); |
|
4563 if (wasSet) { |
|
4564 if (aSuppressTransaction) |
|
4565 res = aElement->RemoveAttribute(aAttribute); |
|
4566 else |
|
4567 res = RemoveAttribute(aElement, aAttribute); |
|
4568 } |
|
4569 } |
|
4570 else { |
|
4571 // count is an integer that represents the number of CSS declarations applied to the |
|
4572 // element. If it is zero, we found no equivalence in this implementation for the |
|
4573 // attribute |
|
4574 if (aAttribute.EqualsLiteral("style")) { |
|
4575 // if it is the style attribute, just add the new value to the existing style |
|
4576 // attribute's value |
|
4577 nsAutoString existingValue; |
|
4578 bool wasSet = false; |
|
4579 res = GetAttributeValue(aElement, NS_LITERAL_STRING("style"), existingValue, &wasSet); |
|
4580 NS_ENSURE_SUCCESS(res, res); |
|
4581 existingValue.AppendLiteral(" "); |
|
4582 existingValue.Append(aValue); |
|
4583 if (aSuppressTransaction) |
|
4584 res = aElement->SetAttribute(aAttribute, existingValue); |
|
4585 else |
|
4586 res = SetAttribute(aElement, aAttribute, existingValue); |
|
4587 } |
|
4588 else { |
|
4589 // we have no CSS equivalence for this attribute and it is not the style |
|
4590 // attribute; let's set it the good'n'old HTML way |
|
4591 if (aSuppressTransaction) |
|
4592 res = aElement->SetAttribute(aAttribute, aValue); |
|
4593 else |
|
4594 res = SetAttribute(aElement, aAttribute, aValue); |
|
4595 } |
|
4596 } |
|
4597 } |
|
4598 else { |
|
4599 // we are not in an HTML+CSS editor; let's set the attribute the HTML way |
|
4600 if (aSuppressTransaction) |
|
4601 res = aElement->SetAttribute(aAttribute, aValue); |
|
4602 else |
|
4603 res = SetAttribute(aElement, aAttribute, aValue); |
|
4604 } |
|
4605 return res; |
|
4606 } |
|
4607 |
|
4608 nsresult |
|
4609 nsHTMLEditor::RemoveAttributeOrEquivalent(nsIDOMElement* aElement, |
|
4610 const nsAString& aAttribute, |
|
4611 bool aSuppressTransaction) |
|
4612 { |
|
4613 nsCOMPtr<dom::Element> element = do_QueryInterface(aElement); |
|
4614 NS_ENSURE_TRUE(element, NS_OK); |
|
4615 |
|
4616 nsCOMPtr<nsIAtom> attribute = do_GetAtom(aAttribute); |
|
4617 MOZ_ASSERT(attribute); |
|
4618 |
|
4619 nsresult res = NS_OK; |
|
4620 if (IsCSSEnabled() && mHTMLCSSUtils) { |
|
4621 res = mHTMLCSSUtils->RemoveCSSEquivalentToHTMLStyle( |
|
4622 element, nullptr, &aAttribute, nullptr, aSuppressTransaction); |
|
4623 NS_ENSURE_SUCCESS(res, res); |
|
4624 } |
|
4625 |
|
4626 if (element->HasAttr(kNameSpaceID_None, attribute)) { |
|
4627 if (aSuppressTransaction) { |
|
4628 res = element->UnsetAttr(kNameSpaceID_None, attribute, |
|
4629 /* aNotify = */ true); |
|
4630 } else { |
|
4631 res = RemoveAttribute(aElement, aAttribute); |
|
4632 } |
|
4633 } |
|
4634 return res; |
|
4635 } |
|
4636 |
|
4637 nsresult |
|
4638 nsHTMLEditor::SetIsCSSEnabled(bool aIsCSSPrefChecked) |
|
4639 { |
|
4640 if (!mHTMLCSSUtils) { |
|
4641 return NS_ERROR_NOT_INITIALIZED; |
|
4642 } |
|
4643 |
|
4644 mHTMLCSSUtils->SetCSSEnabled(aIsCSSPrefChecked); |
|
4645 |
|
4646 // Disable the eEditorNoCSSMask flag if we're enabling StyleWithCSS. |
|
4647 uint32_t flags = mFlags; |
|
4648 if (aIsCSSPrefChecked) { |
|
4649 // Turn off NoCSS as we're enabling CSS |
|
4650 flags &= ~eEditorNoCSSMask; |
|
4651 } else { |
|
4652 // Turn on NoCSS, as we're disabling CSS. |
|
4653 flags |= eEditorNoCSSMask; |
|
4654 } |
|
4655 |
|
4656 return SetFlags(flags); |
|
4657 } |
|
4658 |
|
4659 // Set the block background color |
|
4660 NS_IMETHODIMP |
|
4661 nsHTMLEditor::SetCSSBackgroundColor(const nsAString& aColor) |
|
4662 { |
|
4663 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } |
|
4664 ForceCompositionEnd(); |
|
4665 |
|
4666 // Protect the edit rules object from dying |
|
4667 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules); |
|
4668 |
|
4669 nsRefPtr<Selection> selection = GetSelection(); |
|
4670 |
|
4671 bool isCollapsed = selection->Collapsed(); |
|
4672 |
|
4673 nsAutoEditBatch batchIt(this); |
|
4674 nsAutoRules beginRulesSniffing(this, EditAction::insertElement, nsIEditor::eNext); |
|
4675 nsAutoSelectionReset selectionResetter(selection, this); |
|
4676 nsAutoTxnsConserveSelection dontSpazMySelection(this); |
|
4677 |
|
4678 bool cancel, handled; |
|
4679 nsTextRulesInfo ruleInfo(EditAction::setTextProperty); |
|
4680 nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); |
|
4681 NS_ENSURE_SUCCESS(res, res); |
|
4682 if (!cancel && !handled) |
|
4683 { |
|
4684 // loop thru the ranges in the selection |
|
4685 nsAutoString bgcolor; bgcolor.AssignLiteral("bgcolor"); |
|
4686 uint32_t rangeCount = selection->GetRangeCount(); |
|
4687 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { |
|
4688 nsCOMPtr<nsIDOMNode> cachedBlockParent = nullptr; |
|
4689 nsRefPtr<nsRange> range = selection->GetRangeAt(rangeIdx); |
|
4690 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); |
|
4691 |
|
4692 // check for easy case: both range endpoints in same text node |
|
4693 nsCOMPtr<nsIDOMNode> startNode, endNode; |
|
4694 int32_t startOffset, endOffset; |
|
4695 res = range->GetStartContainer(getter_AddRefs(startNode)); |
|
4696 NS_ENSURE_SUCCESS(res, res); |
|
4697 res = range->GetEndContainer(getter_AddRefs(endNode)); |
|
4698 NS_ENSURE_SUCCESS(res, res); |
|
4699 res = range->GetStartOffset(&startOffset); |
|
4700 NS_ENSURE_SUCCESS(res, res); |
|
4701 res = range->GetEndOffset(&endOffset); |
|
4702 NS_ENSURE_SUCCESS(res, res); |
|
4703 if ((startNode == endNode) && IsTextNode(startNode)) |
|
4704 { |
|
4705 // let's find the block container of the text node |
|
4706 nsCOMPtr<nsIDOMNode> blockParent; |
|
4707 blockParent = GetBlockNodeParent(startNode); |
|
4708 // and apply the background color to that block container |
|
4709 if (cachedBlockParent != blockParent) |
|
4710 { |
|
4711 cachedBlockParent = blockParent; |
|
4712 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent); |
|
4713 int32_t count; |
|
4714 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nullptr, &bgcolor, &aColor, &count, false); |
|
4715 NS_ENSURE_SUCCESS(res, res); |
|
4716 } |
|
4717 } |
|
4718 else if ((startNode == endNode) && nsTextEditUtils::IsBody(startNode) && isCollapsed) |
|
4719 { |
|
4720 // we have no block in the document, let's apply the background to the body |
|
4721 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(startNode); |
|
4722 int32_t count; |
|
4723 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nullptr, &bgcolor, &aColor, &count, false); |
|
4724 NS_ENSURE_SUCCESS(res, res); |
|
4725 } |
|
4726 else if ((startNode == endNode) && (((endOffset-startOffset) == 1) || (!startOffset && !endOffset))) |
|
4727 { |
|
4728 // a unique node is selected, let's also apply the background color |
|
4729 // to the containing block, possibly the node itself |
|
4730 nsCOMPtr<nsIDOMNode> selectedNode = GetChildAt(startNode, startOffset); |
|
4731 bool isBlock =false; |
|
4732 res = NodeIsBlockStatic(selectedNode, &isBlock); |
|
4733 NS_ENSURE_SUCCESS(res, res); |
|
4734 nsCOMPtr<nsIDOMNode> blockParent = selectedNode; |
|
4735 if (!isBlock) { |
|
4736 blockParent = GetBlockNodeParent(selectedNode); |
|
4737 } |
|
4738 if (cachedBlockParent != blockParent) |
|
4739 { |
|
4740 cachedBlockParent = blockParent; |
|
4741 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent); |
|
4742 int32_t count; |
|
4743 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nullptr, &bgcolor, &aColor, &count, false); |
|
4744 NS_ENSURE_SUCCESS(res, res); |
|
4745 } |
|
4746 } |
|
4747 else |
|
4748 { |
|
4749 // not the easy case. range not contained in single text node. |
|
4750 // there are up to three phases here. There are all the nodes |
|
4751 // reported by the subtree iterator to be processed. And there |
|
4752 // are potentially a starting textnode and an ending textnode |
|
4753 // which are only partially contained by the range. |
|
4754 |
|
4755 // lets handle the nodes reported by the iterator. These nodes |
|
4756 // are entirely contained in the selection range. We build up |
|
4757 // a list of them (since doing operations on the document during |
|
4758 // iteration would perturb the iterator). |
|
4759 |
|
4760 nsCOMPtr<nsIContentIterator> iter = |
|
4761 do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &res); |
|
4762 NS_ENSURE_SUCCESS(res, res); |
|
4763 NS_ENSURE_TRUE(iter, NS_ERROR_FAILURE); |
|
4764 |
|
4765 nsCOMArray<nsIDOMNode> arrayOfNodes; |
|
4766 nsCOMPtr<nsIDOMNode> node; |
|
4767 |
|
4768 // iterate range and build up array |
|
4769 res = iter->Init(range); |
|
4770 // init returns an error if no nodes in range. |
|
4771 // this can easily happen with the subtree |
|
4772 // iterator if the selection doesn't contain |
|
4773 // any *whole* nodes. |
|
4774 if (NS_SUCCEEDED(res)) |
|
4775 { |
|
4776 while (!iter->IsDone()) |
|
4777 { |
|
4778 node = do_QueryInterface(iter->GetCurrentNode()); |
|
4779 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); |
|
4780 |
|
4781 if (IsEditable(node)) |
|
4782 { |
|
4783 arrayOfNodes.AppendObject(node); |
|
4784 } |
|
4785 |
|
4786 iter->Next(); |
|
4787 } |
|
4788 } |
|
4789 // first check the start parent of the range to see if it needs to |
|
4790 // be separately handled (it does if it's a text node, due to how the |
|
4791 // subtree iterator works - it will not have reported it). |
|
4792 if (IsTextNode(startNode) && IsEditable(startNode)) |
|
4793 { |
|
4794 nsCOMPtr<nsIDOMNode> blockParent; |
|
4795 blockParent = GetBlockNodeParent(startNode); |
|
4796 if (cachedBlockParent != blockParent) |
|
4797 { |
|
4798 cachedBlockParent = blockParent; |
|
4799 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent); |
|
4800 int32_t count; |
|
4801 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nullptr, &bgcolor, &aColor, &count, false); |
|
4802 NS_ENSURE_SUCCESS(res, res); |
|
4803 } |
|
4804 } |
|
4805 |
|
4806 // then loop through the list, set the property on each node |
|
4807 int32_t listCount = arrayOfNodes.Count(); |
|
4808 int32_t j; |
|
4809 for (j = 0; j < listCount; j++) |
|
4810 { |
|
4811 node = arrayOfNodes[j]; |
|
4812 // do we have a block here ? |
|
4813 bool isBlock =false; |
|
4814 res = NodeIsBlockStatic(node, &isBlock); |
|
4815 NS_ENSURE_SUCCESS(res, res); |
|
4816 nsCOMPtr<nsIDOMNode> blockParent = node; |
|
4817 if (!isBlock) { |
|
4818 // no we don't, let's find the block ancestor |
|
4819 blockParent = GetBlockNodeParent(node); |
|
4820 } |
|
4821 if (cachedBlockParent != blockParent) |
|
4822 { |
|
4823 cachedBlockParent = blockParent; |
|
4824 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent); |
|
4825 int32_t count; |
|
4826 // and set the property on it |
|
4827 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nullptr, &bgcolor, &aColor, &count, false); |
|
4828 NS_ENSURE_SUCCESS(res, res); |
|
4829 } |
|
4830 } |
|
4831 arrayOfNodes.Clear(); |
|
4832 |
|
4833 // last check the end parent of the range to see if it needs to |
|
4834 // be separately handled (it does if it's a text node, due to how the |
|
4835 // subtree iterator works - it will not have reported it). |
|
4836 if (IsTextNode(endNode) && IsEditable(endNode)) |
|
4837 { |
|
4838 nsCOMPtr<nsIDOMNode> blockParent; |
|
4839 blockParent = GetBlockNodeParent(endNode); |
|
4840 if (cachedBlockParent != blockParent) |
|
4841 { |
|
4842 cachedBlockParent = blockParent; |
|
4843 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent); |
|
4844 int32_t count; |
|
4845 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nullptr, &bgcolor, &aColor, &count, false); |
|
4846 NS_ENSURE_SUCCESS(res, res); |
|
4847 } |
|
4848 } |
|
4849 } |
|
4850 } |
|
4851 } |
|
4852 if (!cancel) |
|
4853 { |
|
4854 // post-process |
|
4855 res = mRules->DidDoAction(selection, &ruleInfo, res); |
|
4856 } |
|
4857 return res; |
|
4858 } |
|
4859 |
|
4860 NS_IMETHODIMP |
|
4861 nsHTMLEditor::SetBackgroundColor(const nsAString& aColor) |
|
4862 { |
|
4863 nsresult res; |
|
4864 if (IsCSSEnabled()) { |
|
4865 // if we are in CSS mode, we have to apply the background color to the |
|
4866 // containing block (or the body if we have no block-level element in |
|
4867 // the document) |
|
4868 res = SetCSSBackgroundColor(aColor); |
|
4869 } |
|
4870 else { |
|
4871 // but in HTML mode, we can only set the document's background color |
|
4872 res = SetHTMLBackgroundColor(aColor); |
|
4873 } |
|
4874 return res; |
|
4875 } |
|
4876 |
|
4877 /////////////////////////////////////////////////////////////////////////// |
|
4878 // NodesSameType: do these nodes have the same tag? |
|
4879 // |
|
4880 /* virtual */ |
|
4881 bool |
|
4882 nsHTMLEditor::AreNodesSameType(nsIContent* aNode1, nsIContent* aNode2) |
|
4883 { |
|
4884 MOZ_ASSERT(aNode1); |
|
4885 MOZ_ASSERT(aNode2); |
|
4886 |
|
4887 if (aNode1->Tag() != aNode2->Tag()) { |
|
4888 return false; |
|
4889 } |
|
4890 |
|
4891 if (!IsCSSEnabled() || !aNode1->IsHTML(nsGkAtoms::span)) { |
|
4892 return true; |
|
4893 } |
|
4894 |
|
4895 // If CSS is enabled, we are stricter about span nodes. |
|
4896 return mHTMLCSSUtils->ElementsSameStyle(aNode1->AsDOMNode(), |
|
4897 aNode2->AsDOMNode()); |
|
4898 } |
|
4899 |
|
4900 NS_IMETHODIMP |
|
4901 nsHTMLEditor::CopyLastEditableChildStyles(nsIDOMNode * aPreviousBlock, nsIDOMNode * aNewBlock, |
|
4902 nsIDOMNode **aOutBrNode) |
|
4903 { |
|
4904 *aOutBrNode = nullptr; |
|
4905 nsCOMPtr<nsIDOMNode> child, tmp; |
|
4906 nsresult res; |
|
4907 // first, clear out aNewBlock. Contract is that we want only the styles from previousBlock. |
|
4908 res = aNewBlock->GetFirstChild(getter_AddRefs(child)); |
|
4909 while (NS_SUCCEEDED(res) && child) |
|
4910 { |
|
4911 res = DeleteNode(child); |
|
4912 NS_ENSURE_SUCCESS(res, res); |
|
4913 res = aNewBlock->GetFirstChild(getter_AddRefs(child)); |
|
4914 } |
|
4915 // now find and clone the styles |
|
4916 child = aPreviousBlock; |
|
4917 tmp = aPreviousBlock; |
|
4918 while (tmp) { |
|
4919 child = tmp; |
|
4920 res = GetLastEditableChild(child, address_of(tmp)); |
|
4921 NS_ENSURE_SUCCESS(res, res); |
|
4922 } |
|
4923 while (child && nsTextEditUtils::IsBreak(child)) { |
|
4924 nsCOMPtr<nsIDOMNode> priorNode; |
|
4925 res = GetPriorHTMLNode(child, address_of(priorNode)); |
|
4926 NS_ENSURE_SUCCESS(res, res); |
|
4927 child = priorNode; |
|
4928 } |
|
4929 nsCOMPtr<nsIDOMNode> newStyles = nullptr, deepestStyle = nullptr; |
|
4930 while (child && (child != aPreviousBlock)) { |
|
4931 if (nsHTMLEditUtils::IsInlineStyle(child) || |
|
4932 nsEditor::NodeIsType(child, nsEditProperty::span)) { |
|
4933 nsAutoString domTagName; |
|
4934 child->GetNodeName(domTagName); |
|
4935 ToLowerCase(domTagName); |
|
4936 if (newStyles) { |
|
4937 nsCOMPtr<nsIDOMNode> newContainer; |
|
4938 res = InsertContainerAbove(newStyles, address_of(newContainer), domTagName); |
|
4939 NS_ENSURE_SUCCESS(res, res); |
|
4940 newStyles = newContainer; |
|
4941 } |
|
4942 else { |
|
4943 res = CreateNode(domTagName, aNewBlock, 0, getter_AddRefs(newStyles)); |
|
4944 NS_ENSURE_SUCCESS(res, res); |
|
4945 deepestStyle = newStyles; |
|
4946 } |
|
4947 res = CloneAttributes(newStyles, child); |
|
4948 NS_ENSURE_SUCCESS(res, res); |
|
4949 } |
|
4950 nsCOMPtr<nsIDOMNode> tmp; |
|
4951 res = child->GetParentNode(getter_AddRefs(tmp)); |
|
4952 NS_ENSURE_SUCCESS(res, res); |
|
4953 child = tmp; |
|
4954 } |
|
4955 if (deepestStyle) { |
|
4956 nsCOMPtr<nsIDOMNode> outBRNode; |
|
4957 res = CreateBR(deepestStyle, 0, address_of(outBRNode)); |
|
4958 NS_ENSURE_SUCCESS(res, res); |
|
4959 // Getters must addref |
|
4960 outBRNode.forget(aOutBrNode); |
|
4961 } |
|
4962 return NS_OK; |
|
4963 } |
|
4964 |
|
4965 nsresult |
|
4966 nsHTMLEditor::GetElementOrigin(nsIDOMElement * aElement, int32_t & aX, int32_t & aY) |
|
4967 { |
|
4968 aX = 0; |
|
4969 aY = 0; |
|
4970 |
|
4971 NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED); |
|
4972 nsCOMPtr<nsIPresShell> ps = GetPresShell(); |
|
4973 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); |
|
4974 |
|
4975 nsCOMPtr<nsIContent> content = do_QueryInterface(aElement); |
|
4976 nsIFrame *frame = content->GetPrimaryFrame(); |
|
4977 NS_ENSURE_TRUE(frame, NS_OK); |
|
4978 |
|
4979 nsIFrame *container = ps->GetAbsoluteContainingBlock(frame); |
|
4980 NS_ENSURE_TRUE(container, NS_OK); |
|
4981 nsPoint off = frame->GetOffsetTo(container); |
|
4982 aX = nsPresContext::AppUnitsToIntCSSPixels(off.x); |
|
4983 aY = nsPresContext::AppUnitsToIntCSSPixels(off.y); |
|
4984 |
|
4985 return NS_OK; |
|
4986 } |
|
4987 |
|
4988 nsresult |
|
4989 nsHTMLEditor::EndUpdateViewBatch() |
|
4990 { |
|
4991 nsresult res = nsEditor::EndUpdateViewBatch(); |
|
4992 NS_ENSURE_SUCCESS(res, res); |
|
4993 |
|
4994 // We may need to show resizing handles or update existing ones after |
|
4995 // all transactions are done. This way of doing is preferred to DOM |
|
4996 // mutation events listeners because all the changes the user can apply |
|
4997 // to a document may result in multiple events, some of them quite hard |
|
4998 // to listen too (in particular when an ancestor of the selection is |
|
4999 // changed but the selection itself is not changed). |
|
5000 if (mUpdateCount == 0) { |
|
5001 nsCOMPtr<nsISelection> selection; |
|
5002 res = GetSelection(getter_AddRefs(selection)); |
|
5003 NS_ENSURE_SUCCESS(res, res); |
|
5004 NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED); |
|
5005 res = CheckSelectionStateForAnonymousButtons(selection); |
|
5006 } |
|
5007 return res; |
|
5008 } |
|
5009 |
|
5010 NS_IMETHODIMP |
|
5011 nsHTMLEditor::GetSelectionContainer(nsIDOMElement ** aReturn) |
|
5012 { |
|
5013 nsCOMPtr<nsISelection>selection; |
|
5014 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
5015 // if we don't get the selection, just skip this |
|
5016 if (NS_FAILED(res) || !selection) return res; |
|
5017 |
|
5018 nsCOMPtr<nsIDOMNode> focusNode; |
|
5019 |
|
5020 if (selection->Collapsed()) { |
|
5021 res = selection->GetFocusNode(getter_AddRefs(focusNode)); |
|
5022 NS_ENSURE_SUCCESS(res, res); |
|
5023 } else { |
|
5024 |
|
5025 int32_t rangeCount; |
|
5026 res = selection->GetRangeCount(&rangeCount); |
|
5027 NS_ENSURE_SUCCESS(res, res); |
|
5028 |
|
5029 if (rangeCount == 1) { |
|
5030 |
|
5031 nsCOMPtr<nsIDOMRange> range; |
|
5032 res = selection->GetRangeAt(0, getter_AddRefs(range)); |
|
5033 NS_ENSURE_SUCCESS(res, res); |
|
5034 NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER); |
|
5035 |
|
5036 nsCOMPtr<nsIDOMNode> startContainer, endContainer; |
|
5037 res = range->GetStartContainer(getter_AddRefs(startContainer)); |
|
5038 NS_ENSURE_SUCCESS(res, res); |
|
5039 res = range->GetEndContainer(getter_AddRefs(endContainer)); |
|
5040 NS_ENSURE_SUCCESS(res, res); |
|
5041 int32_t startOffset, endOffset; |
|
5042 res = range->GetStartOffset(&startOffset); |
|
5043 NS_ENSURE_SUCCESS(res, res); |
|
5044 res = range->GetEndOffset(&endOffset); |
|
5045 NS_ENSURE_SUCCESS(res, res); |
|
5046 |
|
5047 nsCOMPtr<nsIDOMElement> focusElement; |
|
5048 if (startContainer == endContainer && startOffset + 1 == endOffset) { |
|
5049 res = GetSelectedElement(EmptyString(), getter_AddRefs(focusElement)); |
|
5050 NS_ENSURE_SUCCESS(res, res); |
|
5051 if (focusElement) |
|
5052 focusNode = do_QueryInterface(focusElement); |
|
5053 } |
|
5054 if (!focusNode) { |
|
5055 res = range->GetCommonAncestorContainer(getter_AddRefs(focusNode)); |
|
5056 NS_ENSURE_SUCCESS(res, res); |
|
5057 } |
|
5058 } |
|
5059 else { |
|
5060 int32_t i; |
|
5061 nsCOMPtr<nsIDOMRange> range; |
|
5062 for (i = 0; i < rangeCount; i++) |
|
5063 { |
|
5064 res = selection->GetRangeAt(i, getter_AddRefs(range)); |
|
5065 NS_ENSURE_SUCCESS(res, res); |
|
5066 nsCOMPtr<nsIDOMNode> startContainer; |
|
5067 res = range->GetStartContainer(getter_AddRefs(startContainer)); |
|
5068 if (NS_FAILED(res)) continue; |
|
5069 if (!focusNode) |
|
5070 focusNode = startContainer; |
|
5071 else if (focusNode != startContainer) { |
|
5072 res = startContainer->GetParentNode(getter_AddRefs(focusNode)); |
|
5073 NS_ENSURE_SUCCESS(res, res); |
|
5074 break; |
|
5075 } |
|
5076 } |
|
5077 } |
|
5078 } |
|
5079 |
|
5080 if (focusNode) { |
|
5081 uint16_t nodeType; |
|
5082 focusNode->GetNodeType(&nodeType); |
|
5083 if (nsIDOMNode::TEXT_NODE == nodeType) { |
|
5084 nsCOMPtr<nsIDOMNode> parent; |
|
5085 res = focusNode->GetParentNode(getter_AddRefs(parent)); |
|
5086 NS_ENSURE_SUCCESS(res, res); |
|
5087 focusNode = parent; |
|
5088 } |
|
5089 } |
|
5090 |
|
5091 nsCOMPtr<nsIDOMElement> focusElement = do_QueryInterface(focusNode); |
|
5092 focusElement.forget(aReturn); |
|
5093 return NS_OK; |
|
5094 } |
|
5095 |
|
5096 NS_IMETHODIMP |
|
5097 nsHTMLEditor::IsAnonymousElement(nsIDOMElement * aElement, bool * aReturn) |
|
5098 { |
|
5099 NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); |
|
5100 nsCOMPtr<nsIContent> content = do_QueryInterface(aElement); |
|
5101 *aReturn = content->IsRootOfNativeAnonymousSubtree(); |
|
5102 return NS_OK; |
|
5103 } |
|
5104 |
|
5105 nsresult |
|
5106 nsHTMLEditor::SetReturnInParagraphCreatesNewParagraph(bool aCreatesNewParagraph) |
|
5107 { |
|
5108 mCRInParagraphCreatesParagraph = aCreatesNewParagraph; |
|
5109 return NS_OK; |
|
5110 } |
|
5111 |
|
5112 bool |
|
5113 nsHTMLEditor::GetReturnInParagraphCreatesNewParagraph() |
|
5114 { |
|
5115 return mCRInParagraphCreatesParagraph; |
|
5116 } |
|
5117 |
|
5118 nsresult |
|
5119 nsHTMLEditor::GetReturnInParagraphCreatesNewParagraph(bool *aCreatesNewParagraph) |
|
5120 { |
|
5121 *aCreatesNewParagraph = mCRInParagraphCreatesParagraph; |
|
5122 return NS_OK; |
|
5123 } |
|
5124 |
|
5125 already_AddRefed<nsIContent> |
|
5126 nsHTMLEditor::GetFocusedContent() |
|
5127 { |
|
5128 NS_ENSURE_TRUE(mDocWeak, nullptr); |
|
5129 |
|
5130 nsFocusManager* fm = nsFocusManager::GetFocusManager(); |
|
5131 NS_ENSURE_TRUE(fm, nullptr); |
|
5132 |
|
5133 nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedContent(); |
|
5134 |
|
5135 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
|
5136 bool inDesignMode = doc->HasFlag(NODE_IS_EDITABLE); |
|
5137 if (!focusedContent) { |
|
5138 // in designMode, nobody gets focus in most cases. |
|
5139 if (inDesignMode && OurWindowHasFocus()) { |
|
5140 nsCOMPtr<nsIContent> docRoot = doc->GetRootElement(); |
|
5141 return docRoot.forget(); |
|
5142 } |
|
5143 return nullptr; |
|
5144 } |
|
5145 |
|
5146 if (inDesignMode) { |
|
5147 return OurWindowHasFocus() && |
|
5148 nsContentUtils::ContentIsDescendantOf(focusedContent, doc) ? |
|
5149 focusedContent.forget() : nullptr; |
|
5150 } |
|
5151 |
|
5152 // We're HTML editor for contenteditable |
|
5153 |
|
5154 // If the focused content isn't editable, or it has independent selection, |
|
5155 // we don't have focus. |
|
5156 if (!focusedContent->HasFlag(NODE_IS_EDITABLE) || |
|
5157 focusedContent->HasIndependentSelection()) { |
|
5158 return nullptr; |
|
5159 } |
|
5160 // If our window is focused, we're focused. |
|
5161 return OurWindowHasFocus() ? focusedContent.forget() : nullptr; |
|
5162 } |
|
5163 |
|
5164 already_AddRefed<nsIContent> |
|
5165 nsHTMLEditor::GetFocusedContentForIME() |
|
5166 { |
|
5167 nsCOMPtr<nsIContent> focusedContent = GetFocusedContent(); |
|
5168 if (!focusedContent) { |
|
5169 return nullptr; |
|
5170 } |
|
5171 |
|
5172 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
|
5173 NS_ENSURE_TRUE(doc, nullptr); |
|
5174 return doc->HasFlag(NODE_IS_EDITABLE) ? nullptr : focusedContent.forget(); |
|
5175 } |
|
5176 |
|
5177 bool |
|
5178 nsHTMLEditor::IsActiveInDOMWindow() |
|
5179 { |
|
5180 NS_ENSURE_TRUE(mDocWeak, false); |
|
5181 |
|
5182 nsFocusManager* fm = nsFocusManager::GetFocusManager(); |
|
5183 NS_ENSURE_TRUE(fm, false); |
|
5184 |
|
5185 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
|
5186 bool inDesignMode = doc->HasFlag(NODE_IS_EDITABLE); |
|
5187 |
|
5188 // If we're in designMode, we're always active in the DOM window. |
|
5189 if (inDesignMode) { |
|
5190 return true; |
|
5191 } |
|
5192 |
|
5193 nsPIDOMWindow* ourWindow = doc->GetWindow(); |
|
5194 nsCOMPtr<nsPIDOMWindow> win; |
|
5195 nsIContent* content = |
|
5196 nsFocusManager::GetFocusedDescendant(ourWindow, false, |
|
5197 getter_AddRefs(win)); |
|
5198 if (!content) { |
|
5199 return false; |
|
5200 } |
|
5201 |
|
5202 // We're HTML editor for contenteditable |
|
5203 |
|
5204 // If the active content isn't editable, or it has independent selection, |
|
5205 // we're not active). |
|
5206 if (!content->HasFlag(NODE_IS_EDITABLE) || |
|
5207 content->HasIndependentSelection()) { |
|
5208 return false; |
|
5209 } |
|
5210 return true; |
|
5211 } |
|
5212 |
|
5213 dom::Element* |
|
5214 nsHTMLEditor::GetActiveEditingHost() |
|
5215 { |
|
5216 NS_ENSURE_TRUE(mDocWeak, nullptr); |
|
5217 |
|
5218 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
|
5219 NS_ENSURE_TRUE(doc, nullptr); |
|
5220 if (doc->HasFlag(NODE_IS_EDITABLE)) { |
|
5221 return doc->GetBodyElement(); |
|
5222 } |
|
5223 |
|
5224 // We're HTML editor for contenteditable |
|
5225 nsCOMPtr<nsISelection> selection; |
|
5226 nsresult rv = GetSelection(getter_AddRefs(selection)); |
|
5227 NS_ENSURE_SUCCESS(rv, nullptr); |
|
5228 nsCOMPtr<nsIDOMNode> focusNode; |
|
5229 rv = selection->GetFocusNode(getter_AddRefs(focusNode)); |
|
5230 NS_ENSURE_SUCCESS(rv, nullptr); |
|
5231 nsCOMPtr<nsIContent> content = do_QueryInterface(focusNode); |
|
5232 if (!content) { |
|
5233 return nullptr; |
|
5234 } |
|
5235 |
|
5236 // If the active content isn't editable, or it has independent selection, |
|
5237 // we're not active. |
|
5238 if (!content->HasFlag(NODE_IS_EDITABLE) || |
|
5239 content->HasIndependentSelection()) { |
|
5240 return nullptr; |
|
5241 } |
|
5242 return content->GetEditingHost(); |
|
5243 } |
|
5244 |
|
5245 already_AddRefed<mozilla::dom::EventTarget> |
|
5246 nsHTMLEditor::GetDOMEventTarget() |
|
5247 { |
|
5248 // Don't use getDocument here, because we have no way of knowing |
|
5249 // whether Init() was ever called. So we need to get the document |
|
5250 // ourselves, if it exists. |
|
5251 NS_PRECONDITION(mDocWeak, "This editor has not been initialized yet"); |
|
5252 nsCOMPtr<mozilla::dom::EventTarget> target = do_QueryReferent(mDocWeak); |
|
5253 return target.forget(); |
|
5254 } |
|
5255 |
|
5256 bool |
|
5257 nsHTMLEditor::ShouldReplaceRootElement() |
|
5258 { |
|
5259 if (!mRootElement) { |
|
5260 // If we don't know what is our root element, we should find our root. |
|
5261 return true; |
|
5262 } |
|
5263 |
|
5264 // If we temporary set document root element to mRootElement, but there is |
|
5265 // body element now, we should replace the root element by the body element. |
|
5266 nsCOMPtr<nsIDOMHTMLElement> docBody; |
|
5267 GetBodyElement(getter_AddRefs(docBody)); |
|
5268 return !SameCOMIdentity(docBody, mRootElement); |
|
5269 } |
|
5270 |
|
5271 void |
|
5272 nsHTMLEditor::ResetRootElementAndEventTarget() |
|
5273 { |
|
5274 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); |
|
5275 |
|
5276 // Need to remove the event listeners first because BeginningOfDocument |
|
5277 // could set a new root (and event target is set by InstallEventListeners()) |
|
5278 // and we won't be able to remove them from the old event target then. |
|
5279 RemoveEventListeners(); |
|
5280 mRootElement = nullptr; |
|
5281 nsresult rv = InstallEventListeners(); |
|
5282 if (NS_FAILED(rv)) { |
|
5283 return; |
|
5284 } |
|
5285 |
|
5286 // We must have mRootElement now. |
|
5287 nsCOMPtr<nsIDOMElement> root; |
|
5288 rv = GetRootElement(getter_AddRefs(root)); |
|
5289 if (NS_FAILED(rv) || !mRootElement) { |
|
5290 return; |
|
5291 } |
|
5292 |
|
5293 rv = BeginningOfDocument(); |
|
5294 if (NS_FAILED(rv)) { |
|
5295 return; |
|
5296 } |
|
5297 |
|
5298 // When this editor has focus, we need to reset the selection limiter to |
|
5299 // new root. Otherwise, that is going to be done when this gets focus. |
|
5300 nsCOMPtr<nsINode> node = GetFocusedNode(); |
|
5301 nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(node); |
|
5302 if (target) { |
|
5303 InitializeSelection(target); |
|
5304 } |
|
5305 |
|
5306 SyncRealTimeSpell(); |
|
5307 } |
|
5308 |
|
5309 nsresult |
|
5310 nsHTMLEditor::GetBodyElement(nsIDOMHTMLElement** aBody) |
|
5311 { |
|
5312 NS_PRECONDITION(mDocWeak, "bad state, null mDocWeak"); |
|
5313 nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryReferent(mDocWeak); |
|
5314 if (!htmlDoc) { |
|
5315 return NS_ERROR_NOT_INITIALIZED; |
|
5316 } |
|
5317 return htmlDoc->GetBody(aBody); |
|
5318 } |
|
5319 |
|
5320 already_AddRefed<nsINode> |
|
5321 nsHTMLEditor::GetFocusedNode() |
|
5322 { |
|
5323 nsCOMPtr<nsIContent> focusedContent = GetFocusedContent(); |
|
5324 if (!focusedContent) { |
|
5325 return nullptr; |
|
5326 } |
|
5327 |
|
5328 nsIFocusManager* fm = nsFocusManager::GetFocusManager(); |
|
5329 NS_ASSERTION(fm, "Focus manager is null"); |
|
5330 nsCOMPtr<nsIDOMElement> focusedElement; |
|
5331 fm->GetFocusedElement(getter_AddRefs(focusedElement)); |
|
5332 if (focusedElement) { |
|
5333 nsCOMPtr<nsINode> node = do_QueryInterface(focusedElement); |
|
5334 return node.forget(); |
|
5335 } |
|
5336 |
|
5337 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
|
5338 return doc.forget(); |
|
5339 } |
|
5340 |
|
5341 bool |
|
5342 nsHTMLEditor::OurWindowHasFocus() |
|
5343 { |
|
5344 NS_ENSURE_TRUE(mDocWeak, false); |
|
5345 nsIFocusManager* fm = nsFocusManager::GetFocusManager(); |
|
5346 NS_ENSURE_TRUE(fm, false); |
|
5347 nsCOMPtr<nsIDOMWindow> focusedWindow; |
|
5348 fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); |
|
5349 if (!focusedWindow) { |
|
5350 return false; |
|
5351 } |
|
5352 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak); |
|
5353 nsCOMPtr<nsIDOMWindow> ourWindow = do_QueryInterface(doc->GetWindow()); |
|
5354 return ourWindow == focusedWindow; |
|
5355 } |
|
5356 |
|
5357 bool |
|
5358 nsHTMLEditor::IsAcceptableInputEvent(nsIDOMEvent* aEvent) |
|
5359 { |
|
5360 if (!nsEditor::IsAcceptableInputEvent(aEvent)) { |
|
5361 return false; |
|
5362 } |
|
5363 |
|
5364 NS_ENSURE_TRUE(mDocWeak, false); |
|
5365 |
|
5366 nsCOMPtr<nsIDOMEventTarget> target; |
|
5367 aEvent->GetTarget(getter_AddRefs(target)); |
|
5368 NS_ENSURE_TRUE(target, false); |
|
5369 |
|
5370 nsCOMPtr<nsIDocument> document = do_QueryReferent(mDocWeak); |
|
5371 if (document->HasFlag(NODE_IS_EDITABLE)) { |
|
5372 // If this editor is in designMode and the event target is the document, |
|
5373 // the event is for this editor. |
|
5374 nsCOMPtr<nsIDocument> targetDocument = do_QueryInterface(target); |
|
5375 if (targetDocument) { |
|
5376 return targetDocument == document; |
|
5377 } |
|
5378 // Otherwise, check whether the event target is in this document or not. |
|
5379 nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target); |
|
5380 NS_ENSURE_TRUE(targetContent, false); |
|
5381 return document == targetContent->GetCurrentDoc(); |
|
5382 } |
|
5383 |
|
5384 // This HTML editor is for contenteditable. We need to check the validity of |
|
5385 // the target. |
|
5386 nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target); |
|
5387 NS_ENSURE_TRUE(targetContent, false); |
|
5388 |
|
5389 // If the event is a mouse event, we need to check if the target content is |
|
5390 // the focused editing host or its descendant. |
|
5391 nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aEvent); |
|
5392 if (mouseEvent) { |
|
5393 nsIContent* editingHost = GetActiveEditingHost(); |
|
5394 // If there is no active editing host, we cannot handle the mouse event |
|
5395 // correctly. |
|
5396 if (!editingHost) { |
|
5397 return false; |
|
5398 } |
|
5399 // If clicked on non-editable root element but the body element is the |
|
5400 // active editing host, we should assume that the click event is targetted. |
|
5401 if (targetContent == document->GetRootElement() && |
|
5402 !targetContent->HasFlag(NODE_IS_EDITABLE) && |
|
5403 editingHost == document->GetBodyElement()) { |
|
5404 targetContent = editingHost; |
|
5405 } |
|
5406 // If the target element is neither the active editing host nor a descendant |
|
5407 // of it, we may not be able to handle the event. |
|
5408 if (!nsContentUtils::ContentIsDescendantOf(targetContent, editingHost)) { |
|
5409 return false; |
|
5410 } |
|
5411 // If the clicked element has an independent selection, we shouldn't |
|
5412 // handle this click event. |
|
5413 if (targetContent->HasIndependentSelection()) { |
|
5414 return false; |
|
5415 } |
|
5416 // If the target content is editable, we should handle this event. |
|
5417 return targetContent->HasFlag(NODE_IS_EDITABLE); |
|
5418 } |
|
5419 |
|
5420 // If the target of the other events which target focused element isn't |
|
5421 // editable or has an independent selection, this editor shouldn't handle the |
|
5422 // event. |
|
5423 if (!targetContent->HasFlag(NODE_IS_EDITABLE) || |
|
5424 targetContent->HasIndependentSelection()) { |
|
5425 return false; |
|
5426 } |
|
5427 |
|
5428 // Finally, check whether we're actually focused or not. When we're not |
|
5429 // focused, we should ignore the dispatched event by script (or something) |
|
5430 // because content editable element needs selection in itself for editing. |
|
5431 // However, when we're not focused, it's not guaranteed. |
|
5432 return IsActiveInDOMWindow(); |
|
5433 } |
|
5434 |
|
5435 NS_IMETHODIMP |
|
5436 nsHTMLEditor::GetPreferredIMEState(IMEState *aState) |
|
5437 { |
|
5438 // HTML editor don't prefer the CSS ime-mode because IE didn't do so too. |
|
5439 aState->mOpen = IMEState::DONT_CHANGE_OPEN_STATE; |
|
5440 if (IsReadonly() || IsDisabled()) { |
|
5441 aState->mEnabled = IMEState::DISABLED; |
|
5442 } else { |
|
5443 aState->mEnabled = IMEState::ENABLED; |
|
5444 } |
|
5445 return NS_OK; |
|
5446 } |
|
5447 |
|
5448 already_AddRefed<nsIContent> |
|
5449 nsHTMLEditor::GetInputEventTargetContent() |
|
5450 { |
|
5451 nsCOMPtr<nsIContent> target = GetActiveEditingHost(); |
|
5452 return target.forget(); |
|
5453 } |
|
5454 |
|
5455 bool |
|
5456 nsHTMLEditor::IsEditable(nsIContent* aNode) { |
|
5457 if (!nsPlaintextEditor::IsEditable(aNode)) { |
|
5458 return false; |
|
5459 } |
|
5460 if (aNode->IsElement()) { |
|
5461 // If we're dealing with an element, then ask it whether it's editable. |
|
5462 return aNode->IsEditable(); |
|
5463 } |
|
5464 // We might be dealing with a text node for example, which we always consider |
|
5465 // to be editable. |
|
5466 return true; |
|
5467 } |
|
5468 |
|
5469 // virtual MOZ_OVERRIDE |
|
5470 dom::Element* |
|
5471 nsHTMLEditor::GetEditorRoot() |
|
5472 { |
|
5473 return GetActiveEditingHost(); |
|
5474 } |