editor/libeditor/html/nsHTMLEditor.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial