michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/EventStates.h" michael@0: #include "mozilla/TextEvents.h" michael@0: michael@0: #include "nsCRT.h" michael@0: michael@0: #include "nsUnicharUtils.h" michael@0: michael@0: #include "nsHTMLEditor.h" michael@0: #include "nsHTMLEditRules.h" michael@0: #include "nsTextEditUtils.h" michael@0: #include "nsHTMLEditUtils.h" michael@0: michael@0: #include "nsHTMLEditorEventListener.h" michael@0: #include "TypeInState.h" michael@0: michael@0: #include "nsHTMLURIRefObject.h" michael@0: michael@0: #include "nsIDOMText.h" michael@0: #include "nsIDOMMozNamedAttrMap.h" michael@0: #include "nsIDOMNodeList.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMAttr.h" michael@0: #include "nsIDocumentInlines.h" michael@0: #include "nsIDOMEventTarget.h" michael@0: #include "nsIDOMKeyEvent.h" michael@0: #include "nsIDOMMouseEvent.h" michael@0: #include "nsIDOMHTMLAnchorElement.h" michael@0: #include "nsISelectionController.h" michael@0: #include "nsIDOMHTMLDocument.h" michael@0: #include "nsILinkHandler.h" michael@0: #include "nsIInlineSpellChecker.h" michael@0: michael@0: #include "mozilla/css/Loader.h" michael@0: #include "nsCSSStyleSheet.h" michael@0: #include "nsIDOMStyleSheet.h" michael@0: michael@0: #include "nsIContent.h" michael@0: #include "nsIContentIterator.h" michael@0: #include "nsIDOMRange.h" michael@0: #include "nsISupportsArray.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIDocumentEncoder.h" michael@0: #include "nsIDOMDocumentFragment.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsPresContext.h" michael@0: #include "SetDocTitleTxn.h" michael@0: #include "nsFocusManager.h" michael@0: #include "nsPIDOMWindow.h" michael@0: michael@0: // netwerk michael@0: #include "nsIURI.h" michael@0: #include "nsNetUtil.h" michael@0: michael@0: // Transactionas michael@0: #include "nsStyleSheetTxns.h" michael@0: michael@0: // Misc michael@0: #include "TextEditorTest.h" michael@0: #include "nsEditorUtils.h" michael@0: #include "nsWSRunObject.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIWidget.h" michael@0: michael@0: #include "nsIFrame.h" michael@0: #include "nsIParserService.h" michael@0: #include "mozilla/dom/Selection.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/dom/EventTarget.h" michael@0: #include "mozilla/dom/HTMLBodyElement.h" michael@0: #include "nsTextFragment.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::widget; michael@0: michael@0: // Some utilities to handle annoying overloading of "A" tag for link and named anchor michael@0: static char hrefText[] = "href"; michael@0: static char anchorTxt[] = "anchor"; michael@0: static char namedanchorText[] = "namedanchor"; michael@0: michael@0: #define IsLinkTag(s) (s.EqualsIgnoreCase(hrefText)) michael@0: #define IsNamedAnchorTag(s) (s.EqualsIgnoreCase(anchorTxt) || s.EqualsIgnoreCase(namedanchorText)) michael@0: michael@0: nsHTMLEditor::nsHTMLEditor() michael@0: : nsPlaintextEditor() michael@0: , mCRInParagraphCreatesParagraph(false) michael@0: , mSelectedCellIndex(0) michael@0: , mIsObjectResizingEnabled(true) michael@0: , mIsResizing(false) michael@0: , mIsAbsolutelyPositioningEnabled(true) michael@0: , mResizedObjectIsAbsolutelyPositioned(false) michael@0: , mGrabberClicked(false) michael@0: , mIsMoving(false) michael@0: , mSnapToGridEnabled(false) michael@0: , mIsInlineTableEditingEnabled(true) michael@0: , mInfoXIncrement(20) michael@0: , mInfoYIncrement(20) michael@0: , mGridSize(0) michael@0: { michael@0: } michael@0: michael@0: nsHTMLEditor::~nsHTMLEditor() michael@0: { michael@0: // remove the rules as an action listener. Else we get a bad michael@0: // ownership loop later on. it's ok if the rules aren't a listener; michael@0: // we ignore the error. michael@0: nsCOMPtr mListener = do_QueryInterface(mRules); michael@0: RemoveEditActionListener(mListener); michael@0: michael@0: //the autopointers will clear themselves up. michael@0: //but we need to also remove the listeners or we have a leak michael@0: nsCOMPtrselection; michael@0: nsresult result = GetSelection(getter_AddRefs(selection)); michael@0: // if we don't get the selection, just skip this michael@0: if (NS_SUCCEEDED(result) && selection) michael@0: { michael@0: nsCOMPtr selPriv(do_QueryInterface(selection)); michael@0: nsCOMPtrlistener; michael@0: listener = do_QueryInterface(mTypeInState); michael@0: if (listener) michael@0: { michael@0: selPriv->RemoveSelectionListener(listener); michael@0: } michael@0: listener = do_QueryInterface(mSelectionListenerP); michael@0: if (listener) michael@0: { michael@0: selPriv->RemoveSelectionListener(listener); michael@0: } michael@0: } michael@0: michael@0: mTypeInState = nullptr; michael@0: mSelectionListenerP = nullptr; michael@0: michael@0: // free any default style propItems michael@0: RemoveAllDefaultProperties(); michael@0: michael@0: if (mLinkHandler && mDocWeak) michael@0: { michael@0: nsCOMPtr ps = GetPresShell(); michael@0: michael@0: if (ps && ps->GetPresContext()) michael@0: { michael@0: ps->GetPresContext()->SetLinkHandler(mLinkHandler); michael@0: } michael@0: } michael@0: michael@0: RemoveEventListeners(); michael@0: } michael@0: michael@0: void michael@0: nsHTMLEditor::HideAnonymousEditingUIs() michael@0: { michael@0: if (mAbsolutelyPositionedObject) michael@0: HideGrabber(); michael@0: if (mInlineEditedCell) michael@0: HideInlineTableEditingUI(); michael@0: if (mResizedObject) michael@0: HideResizers(); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsHTMLEditor) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHTMLEditor, nsPlaintextEditor) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mTypeInState) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mStyleSheets) michael@0: michael@0: tmp->HideAnonymousEditingUIs(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHTMLEditor, nsPlaintextEditor) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTypeInState) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheets) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopLeftHandle) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopHandle) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopRightHandle) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLeftHandle) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRightHandle) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomLeftHandle) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomHandle) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomRightHandle) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActivatedHandle) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizingShadow) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizingInfo) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizedObject) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMouseMotionListenerP) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionListenerP) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizeEventListenerP) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(objectResizeEventListeners) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbsolutelyPositionedObject) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGrabber) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPositioningShadow) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineEditedCell) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddColumnBeforeButton) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRemoveColumnButton) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddColumnAfterButton) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddRowBeforeButton) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRemoveRowButton) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddRowAfterButton) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(nsHTMLEditor, nsEditor) michael@0: NS_IMPL_RELEASE_INHERITED(nsHTMLEditor, nsEditor) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsHTMLEditor) michael@0: NS_INTERFACE_MAP_ENTRY(nsIHTMLEditor) michael@0: NS_INTERFACE_MAP_ENTRY(nsIHTMLObjectResizer) michael@0: NS_INTERFACE_MAP_ENTRY(nsIHTMLAbsPosEditor) michael@0: NS_INTERFACE_MAP_ENTRY(nsIHTMLInlineTableEditor) michael@0: NS_INTERFACE_MAP_ENTRY(nsITableEditor) michael@0: NS_INTERFACE_MAP_ENTRY(nsIEditorStyleSheets) michael@0: NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) michael@0: NS_INTERFACE_MAP_END_INHERITING(nsPlaintextEditor) michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::Init(nsIDOMDocument *aDoc, michael@0: nsIContent *aRoot, michael@0: nsISelectionController *aSelCon, michael@0: uint32_t aFlags, michael@0: const nsAString& aInitialValue) michael@0: { michael@0: NS_PRECONDITION(aDoc && !aSelCon, "bad arg"); michael@0: NS_ENSURE_TRUE(aDoc, NS_ERROR_NULL_POINTER); michael@0: MOZ_ASSERT(aInitialValue.IsEmpty(), "Non-empty initial values not supported"); michael@0: michael@0: nsresult result = NS_OK, rulesRes = NS_OK; michael@0: michael@0: if (1) michael@0: { michael@0: // block to scope nsAutoEditInitRulesTrigger michael@0: nsAutoEditInitRulesTrigger rulesTrigger(static_cast(this), rulesRes); michael@0: michael@0: // Init the plaintext editor michael@0: result = nsPlaintextEditor::Init(aDoc, aRoot, nullptr, aFlags, aInitialValue); michael@0: if (NS_FAILED(result)) { return result; } michael@0: michael@0: // Init mutation observer michael@0: nsCOMPtr document = do_QueryInterface(aDoc); michael@0: document->AddMutationObserverUnlessExists(this); michael@0: michael@0: // disable Composer-only features michael@0: if (IsMailEditor()) michael@0: { michael@0: SetAbsolutePositioningEnabled(false); michael@0: SetSnapToGridEnabled(false); michael@0: } michael@0: michael@0: // Init the HTML-CSS utils michael@0: mHTMLCSSUtils = new nsHTMLCSSUtils(this); michael@0: michael@0: // disable links michael@0: nsCOMPtr presShell = GetPresShell(); michael@0: NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); michael@0: nsPresContext *context = presShell->GetPresContext(); michael@0: NS_ENSURE_TRUE(context, NS_ERROR_NULL_POINTER); michael@0: if (!IsPlaintextEditor() && !IsInteractionAllowed()) { michael@0: mLinkHandler = context->GetLinkHandler(); michael@0: michael@0: context->SetLinkHandler(nullptr); michael@0: } michael@0: michael@0: // init the type-in state michael@0: mTypeInState = new TypeInState(); michael@0: michael@0: // init the selection listener for image resizing michael@0: mSelectionListenerP = new ResizerSelectionListener(this); michael@0: michael@0: if (!IsInteractionAllowed()) { michael@0: // ignore any errors from this in case the file is missing michael@0: AddOverrideStyleSheet(NS_LITERAL_STRING("resource://gre/res/EditorOverride.css")); michael@0: } michael@0: michael@0: nsCOMPtrselection; michael@0: result = GetSelection(getter_AddRefs(selection)); michael@0: if (NS_FAILED(result)) { return result; } michael@0: if (selection) michael@0: { michael@0: nsCOMPtr selPriv(do_QueryInterface(selection)); michael@0: nsCOMPtrlistener; michael@0: listener = do_QueryInterface(mTypeInState); michael@0: if (listener) { michael@0: selPriv->AddSelectionListener(listener); michael@0: } michael@0: listener = do_QueryInterface(mSelectionListenerP); michael@0: if (listener) { michael@0: selPriv->AddSelectionListener(listener); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(rulesRes, rulesRes); michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::PreDestroy(bool aDestroyingFrames) michael@0: { michael@0: if (mDidPreDestroy) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr document = do_QueryReferent(mDocWeak); michael@0: if (document) { michael@0: document->RemoveMutationObserver(this); michael@0: } michael@0: michael@0: while (mStyleSheetURLs.Length()) michael@0: { michael@0: RemoveOverrideStyleSheet(mStyleSheetURLs[0]); michael@0: } michael@0: michael@0: // Clean up after our anonymous content -- we don't want these nodes to michael@0: // stay around (which they would, since the frames have an owning reference). michael@0: HideAnonymousEditingUIs(); michael@0: michael@0: return nsPlaintextEditor::PreDestroy(aDestroyingFrames); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetRootElement(nsIDOMElement **aRootElement) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aRootElement); michael@0: michael@0: if (mRootElement) { michael@0: return nsEditor::GetRootElement(aRootElement); michael@0: } michael@0: michael@0: *aRootElement = nullptr; michael@0: michael@0: // Use the HTML documents body element as the editor root if we didn't michael@0: // get a root element during initialization. michael@0: michael@0: nsCOMPtr rootElement; michael@0: nsCOMPtr bodyElement; michael@0: nsresult rv = GetBodyElement(getter_AddRefs(bodyElement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (bodyElement) { michael@0: rootElement = bodyElement; michael@0: } else { michael@0: // If there is no HTML body element, michael@0: // we should use the document root element instead. michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: rv = doc->GetDocumentElement(getter_AddRefs(rootElement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // Document can have no elements michael@0: if (!rootElement) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } michael@0: michael@0: mRootElement = do_QueryInterface(rootElement); michael@0: rootElement.forget(aRootElement); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsHTMLEditor::FindSelectionRoot(nsINode *aNode) michael@0: { michael@0: NS_PRECONDITION(aNode->IsNodeOfType(nsINode::eDOCUMENT) || michael@0: aNode->IsNodeOfType(nsINode::eCONTENT), michael@0: "aNode must be content or document node"); michael@0: michael@0: nsCOMPtr doc = aNode->GetCurrentDoc(); michael@0: if (!doc) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr content; michael@0: if (doc->HasFlag(NODE_IS_EDITABLE) || !aNode->IsContent()) { michael@0: content = doc->GetRootElement(); michael@0: return content.forget(); michael@0: } michael@0: content = aNode->AsContent(); michael@0: michael@0: // XXX If we have readonly flag, shouldn't return the element which has michael@0: // contenteditable="true"? However, such case isn't there without chrome michael@0: // permission script. michael@0: if (IsReadonly()) { michael@0: // We still want to allow selection in a readonly editor. michael@0: content = do_QueryInterface(GetRoot()); michael@0: return content.forget(); michael@0: } michael@0: michael@0: if (!content->HasFlag(NODE_IS_EDITABLE)) { michael@0: // If the content is in read-write state but is not editable itself, michael@0: // return it as the selection root. michael@0: if (content->IsElement() && michael@0: content->AsElement()->State().HasState(NS_EVENT_STATE_MOZ_READWRITE)) { michael@0: return content.forget(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: // For non-readonly editors we want to find the root of the editable subtree michael@0: // containing aContent. michael@0: content = content->GetEditingHost(); michael@0: return content.forget(); michael@0: } michael@0: michael@0: /* virtual */ michael@0: void michael@0: nsHTMLEditor::CreateEventListeners() michael@0: { michael@0: // Don't create the handler twice michael@0: if (!mEventListener) { michael@0: mEventListener = new nsHTMLEditorEventListener(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::InstallEventListeners() michael@0: { michael@0: NS_ENSURE_TRUE(mDocWeak && mEventListener, michael@0: NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // NOTE: nsHTMLEditor doesn't need to initialize mEventTarget here because michael@0: // the target must be document node and it must be referenced as weak pointer. michael@0: michael@0: nsHTMLEditorEventListener* listener = michael@0: reinterpret_cast(mEventListener.get()); michael@0: return listener->Connect(this); michael@0: } michael@0: michael@0: void michael@0: nsHTMLEditor::RemoveEventListeners() michael@0: { michael@0: if (!mDocWeak) michael@0: { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr target = GetDOMEventTarget(); michael@0: michael@0: if (target) michael@0: { michael@0: // Both mMouseMotionListenerP and mResizeEventListenerP can be michael@0: // registerd with other targets than the DOM event receiver that michael@0: // we can reach from here. But nonetheless, unregister the event michael@0: // listeners with the DOM event reveiver (if it's registerd with michael@0: // other targets, it'll get unregisterd once the target goes michael@0: // away). michael@0: michael@0: if (mMouseMotionListenerP) michael@0: { michael@0: // mMouseMotionListenerP might be registerd either as bubbling or michael@0: // capturing, unregister by both. michael@0: target->RemoveEventListener(NS_LITERAL_STRING("mousemove"), michael@0: mMouseMotionListenerP, false); michael@0: target->RemoveEventListener(NS_LITERAL_STRING("mousemove"), michael@0: mMouseMotionListenerP, true); michael@0: } michael@0: michael@0: if (mResizeEventListenerP) michael@0: { michael@0: target->RemoveEventListener(NS_LITERAL_STRING("resize"), michael@0: mResizeEventListenerP, false); michael@0: } michael@0: } michael@0: michael@0: mMouseMotionListenerP = nullptr; michael@0: mResizeEventListenerP = nullptr; michael@0: michael@0: nsPlaintextEditor::RemoveEventListeners(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::SetFlags(uint32_t aFlags) michael@0: { michael@0: nsresult rv = nsPlaintextEditor::SetFlags(aFlags); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Sets mCSSAware to correspond to aFlags. This toggles whether CSS is michael@0: // used to style elements in the editor. Note that the editor is only CSS michael@0: // aware by default in Composer and in the mail editor. michael@0: mCSSAware = !NoCSS() && !IsMailEditor(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::InitRules() michael@0: { michael@0: if (!mRules) { michael@0: // instantiate the rules for the html editor michael@0: mRules = new nsHTMLEditRules(); michael@0: } michael@0: return mRules->Init(static_cast(this)); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::BeginningOfDocument() michael@0: { michael@0: if (!mDocWeak) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: // get the selection michael@0: nsCOMPtr selection; michael@0: nsresult res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // Get the root element. michael@0: nsCOMPtr rootElement = do_QueryInterface(GetRoot()); michael@0: if (!rootElement) { michael@0: NS_WARNING("GetRoot() returned a null pointer (mRootElement is null)"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // find first editable thingy michael@0: bool done = false; michael@0: nsCOMPtr curNode(rootElement), selNode; michael@0: int32_t curOffset = 0, selOffset; michael@0: while (!done) michael@0: { michael@0: nsWSRunObject wsObj(this, curNode, curOffset); michael@0: nsCOMPtr visNode; michael@0: int32_t visOffset=0; michael@0: WSType visType; michael@0: wsObj.NextVisibleNode(curNode, curOffset, address_of(visNode), &visOffset, &visType); michael@0: if (visType == WSType::normalWS || visType == WSType::text) { michael@0: selNode = visNode; michael@0: selOffset = visOffset; michael@0: done = true; michael@0: } else if (visType == WSType::br || visType == WSType::special) { michael@0: selNode = GetNodeLocation(visNode, &selOffset); michael@0: done = true; michael@0: } else if (visType == WSType::otherBlock) { michael@0: // By definition of nsWSRunObject, a block element terminates michael@0: // a whitespace run. That is, although we are calling a method michael@0: // that is named "NextVisibleNode", the node returned michael@0: // might not be visible/editable! michael@0: // If the given block does not contain any visible/editable items, michael@0: // we want to skip it and continue our search. michael@0: michael@0: if (!IsContainer(visNode)) michael@0: { michael@0: // However, we were given a block that is not a container. michael@0: // Since the block can not contain anything that's visible, michael@0: // such a block only makes sense if it is visible by itself, michael@0: // like a
michael@0: // We want to place the caret in front of that block. michael@0: michael@0: selNode = GetNodeLocation(visNode, &selOffset); michael@0: done = true; michael@0: } michael@0: else michael@0: { michael@0: bool isEmptyBlock; michael@0: if (NS_SUCCEEDED(IsEmptyNode(visNode, &isEmptyBlock)) && michael@0: isEmptyBlock) michael@0: { michael@0: // skip the empty block michael@0: curNode = GetNodeLocation(visNode, &curOffset); michael@0: ++curOffset; michael@0: } michael@0: else michael@0: { michael@0: curNode = visNode; michael@0: curOffset = 0; michael@0: } michael@0: // keep looping michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // else we found nothing useful michael@0: selNode = curNode; michael@0: selOffset = curOffset; michael@0: done = true; michael@0: } michael@0: } michael@0: return selection->Collapse(selNode, selOffset); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent) michael@0: { michael@0: // NOTE: When you change this method, you should also change: michael@0: // * editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html michael@0: michael@0: if (IsReadonly() || IsDisabled()) { michael@0: // When we're not editable, the events are handled on nsEditor, so, we can michael@0: // bypass nsPlaintextEditor. michael@0: return nsEditor::HandleKeyPressEvent(aKeyEvent); michael@0: } michael@0: michael@0: WidgetKeyboardEvent* nativeKeyEvent = michael@0: aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); michael@0: NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED); michael@0: NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS, michael@0: "HandleKeyPressEvent gets non-keypress event"); michael@0: michael@0: switch (nativeKeyEvent->keyCode) { michael@0: case nsIDOMKeyEvent::DOM_VK_META: michael@0: case nsIDOMKeyEvent::DOM_VK_WIN: michael@0: case nsIDOMKeyEvent::DOM_VK_SHIFT: michael@0: case nsIDOMKeyEvent::DOM_VK_CONTROL: michael@0: case nsIDOMKeyEvent::DOM_VK_ALT: michael@0: case nsIDOMKeyEvent::DOM_VK_BACK_SPACE: michael@0: case nsIDOMKeyEvent::DOM_VK_DELETE: michael@0: // These keys are handled on nsEditor, so, we can bypass michael@0: // nsPlaintextEditor. michael@0: return nsEditor::HandleKeyPressEvent(aKeyEvent); michael@0: case nsIDOMKeyEvent::DOM_VK_TAB: { michael@0: if (IsPlaintextEditor()) { michael@0: // If this works as plain text editor, e.g., mail editor for plain michael@0: // text, should be handled on nsPlaintextEditor. michael@0: return nsPlaintextEditor::HandleKeyPressEvent(aKeyEvent); michael@0: } michael@0: michael@0: if (IsTabbable()) { michael@0: return NS_OK; // let it be used for focus switching michael@0: } michael@0: michael@0: if (nativeKeyEvent->IsControl() || nativeKeyEvent->IsAlt() || michael@0: nativeKeyEvent->IsMeta() || nativeKeyEvent->IsOS()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr selection; michael@0: nsresult rv = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: int32_t offset; michael@0: nsCOMPtr node, blockParent; michael@0: rv = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); michael@0: michael@0: bool isBlock = false; michael@0: NodeIsBlock(node, &isBlock); michael@0: if (isBlock) { michael@0: blockParent = node; michael@0: } else { michael@0: blockParent = GetBlockNodeParent(node); michael@0: } michael@0: michael@0: if (!blockParent) { michael@0: break; michael@0: } michael@0: michael@0: bool handled = false; michael@0: if (nsHTMLEditUtils::IsTableElement(blockParent)) { michael@0: rv = TabInTable(nativeKeyEvent->IsShift(), &handled); michael@0: if (handled) { michael@0: ScrollSelectionIntoView(false); michael@0: } michael@0: } else if (nsHTMLEditUtils::IsListItem(blockParent)) { michael@0: rv = Indent(nativeKeyEvent->IsShift() ? michael@0: NS_LITERAL_STRING("outdent") : michael@0: NS_LITERAL_STRING("indent")); michael@0: handled = true; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (handled) { michael@0: return aKeyEvent->PreventDefault(); // consumed michael@0: } michael@0: if (nativeKeyEvent->IsShift()) { michael@0: return NS_OK; // don't type text for shift tabs michael@0: } michael@0: aKeyEvent->PreventDefault(); michael@0: return TypedText(NS_LITERAL_STRING("\t"), eTypedText); michael@0: } michael@0: case nsIDOMKeyEvent::DOM_VK_RETURN: michael@0: if (nativeKeyEvent->IsControl() || nativeKeyEvent->IsAlt() || michael@0: nativeKeyEvent->IsMeta() || nativeKeyEvent->IsOS()) { michael@0: return NS_OK; michael@0: } michael@0: aKeyEvent->PreventDefault(); // consumed michael@0: if (nativeKeyEvent->IsShift() && !IsPlaintextEditor()) { michael@0: // only inserts a br node michael@0: return TypedText(EmptyString(), eTypedBR); michael@0: } michael@0: // uses rules to figure out what to insert michael@0: return TypedText(EmptyString(), eTypedBreak); michael@0: } michael@0: michael@0: // NOTE: On some keyboard layout, some characters are inputted with Control michael@0: // key or Alt key, but at that time, widget sets FALSE to these keys. michael@0: if (nativeKeyEvent->charCode == 0 || nativeKeyEvent->IsControl() || michael@0: nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta() || michael@0: nativeKeyEvent->IsOS()) { michael@0: // we don't PreventDefault() here or keybindings like control-x won't work michael@0: return NS_OK; michael@0: } michael@0: aKeyEvent->PreventDefault(); michael@0: nsAutoString str(nativeKeyEvent->charCode); michael@0: return TypedText(str, eTypedText); michael@0: } michael@0: michael@0: static void michael@0: AssertParserServiceIsCorrect(nsIAtom* aTag, bool aIsBlock) michael@0: { michael@0: #ifdef DEBUG michael@0: // Check this against what we would have said with the old code: michael@0: if (aTag==nsEditProperty::p || michael@0: aTag==nsEditProperty::div || michael@0: aTag==nsEditProperty::blockquote || michael@0: aTag==nsEditProperty::h1 || michael@0: aTag==nsEditProperty::h2 || michael@0: aTag==nsEditProperty::h3 || michael@0: aTag==nsEditProperty::h4 || michael@0: aTag==nsEditProperty::h5 || michael@0: aTag==nsEditProperty::h6 || michael@0: aTag==nsEditProperty::ul || michael@0: aTag==nsEditProperty::ol || michael@0: aTag==nsEditProperty::dl || michael@0: aTag==nsEditProperty::noscript || michael@0: aTag==nsEditProperty::form || michael@0: aTag==nsEditProperty::hr || michael@0: aTag==nsEditProperty::table || michael@0: aTag==nsEditProperty::fieldset || michael@0: aTag==nsEditProperty::address || michael@0: aTag==nsEditProperty::col || michael@0: aTag==nsEditProperty::colgroup || michael@0: aTag==nsEditProperty::li || michael@0: aTag==nsEditProperty::dt || michael@0: aTag==nsEditProperty::dd || michael@0: aTag==nsEditProperty::legend ) michael@0: { michael@0: if (!aIsBlock) { michael@0: nsAutoString assertmsg (NS_LITERAL_STRING("Parser and editor disagree on blockness: ")); michael@0: michael@0: nsAutoString tagName; michael@0: aTag->ToString(tagName); michael@0: assertmsg.Append(tagName); michael@0: char* assertstr = ToNewCString(assertmsg); michael@0: NS_ASSERTION(aIsBlock, assertstr); michael@0: NS_Free(assertstr); michael@0: } michael@0: } michael@0: #endif // DEBUG michael@0: } michael@0: michael@0: /** michael@0: * Returns true if the id represents an element of block type. michael@0: * Can be used to determine if a new paragraph should be started. michael@0: */ michael@0: bool michael@0: nsHTMLEditor::NodeIsBlockStatic(const dom::Element* aElement) michael@0: { michael@0: MOZ_ASSERT(aElement); michael@0: michael@0: nsIAtom* tagAtom = aElement->Tag(); michael@0: MOZ_ASSERT(tagAtom); michael@0: michael@0: // Nodes we know we want to treat as block michael@0: // even though the parser says they're not: michael@0: if (tagAtom==nsEditProperty::body || michael@0: tagAtom==nsEditProperty::head || michael@0: tagAtom==nsEditProperty::tbody || michael@0: tagAtom==nsEditProperty::thead || michael@0: tagAtom==nsEditProperty::tfoot || michael@0: tagAtom==nsEditProperty::tr || michael@0: tagAtom==nsEditProperty::th || michael@0: tagAtom==nsEditProperty::td || michael@0: tagAtom==nsEditProperty::li || michael@0: tagAtom==nsEditProperty::dt || michael@0: tagAtom==nsEditProperty::dd || michael@0: tagAtom==nsEditProperty::pre) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: bool isBlock; michael@0: #ifdef DEBUG michael@0: // XXX we can't use DebugOnly here because VC++ is stupid (bug 802884) michael@0: nsresult rv = michael@0: #endif michael@0: nsContentUtils::GetParserService()-> michael@0: IsBlock(nsContentUtils::GetParserService()->HTMLAtomTagToId(tagAtom), michael@0: isBlock); michael@0: MOZ_ASSERT(rv == NS_OK); michael@0: michael@0: AssertParserServiceIsCorrect(tagAtom, isBlock); michael@0: michael@0: return isBlock; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::NodeIsBlockStatic(nsIDOMNode *aNode, bool *aIsBlock) michael@0: { michael@0: if (!aNode || !aIsBlock) { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: nsCOMPtr element = do_QueryInterface(aNode); michael@0: *aIsBlock = element && NodeIsBlockStatic(element); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::NodeIsBlock(nsIDOMNode *aNode, bool *aIsBlock) michael@0: { michael@0: return NodeIsBlockStatic(aNode, aIsBlock); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::IsBlockNode(nsINode *aNode) michael@0: { michael@0: return aNode && aNode->IsElement() && NodeIsBlockStatic(aNode->AsElement()); michael@0: } michael@0: michael@0: // Non-static version for the nsIEditor interface and JavaScript michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::SetDocumentTitle(const nsAString &aTitle) michael@0: { michael@0: nsRefPtr txn = new SetDocTitleTxn(); michael@0: NS_ENSURE_TRUE(txn, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: nsresult result = txn->Init(this, &aTitle); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: //Don't let Rules System change the selection michael@0: nsAutoTxnsConserveSelection dontChangeSelection(this); michael@0: return nsEditor::DoTransaction(txn); michael@0: } michael@0: michael@0: /* ------------ Block methods moved from nsEditor -------------- */ michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetBlockNodeParent: returns enclosing block level ancestor, if any michael@0: // michael@0: already_AddRefed michael@0: nsHTMLEditor::GetBlockNodeParent(nsIDOMNode *aNode) michael@0: { michael@0: if (!aNode) michael@0: { michael@0: NS_NOTREACHED("null node passed to GetBlockNodeParent()"); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr p; michael@0: if (NS_FAILED(aNode->GetParentNode(getter_AddRefs(p)))) // no parent, ran off top of tree michael@0: return nullptr; michael@0: michael@0: nsCOMPtr tmp; michael@0: while (p) michael@0: { michael@0: bool isBlock; michael@0: if (NS_FAILED(NodeIsBlockStatic(p, &isBlock)) || isBlock) michael@0: break; michael@0: if (NS_FAILED(p->GetParentNode(getter_AddRefs(tmp))) || !tmp) // no parent, ran off top of tree michael@0: break; michael@0: michael@0: p = tmp; michael@0: } michael@0: return p.forget(); michael@0: } michael@0: michael@0: static const char16_t nbsp = 160; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // IsNextCharInNodeWhitespace: checks the adjacent content in the same node to michael@0: // see if following selection is whitespace or nbsp michael@0: void michael@0: nsHTMLEditor::IsNextCharInNodeWhitespace(nsIContent* aContent, michael@0: int32_t aOffset, michael@0: bool* outIsSpace, michael@0: bool* outIsNBSP, michael@0: nsIContent** outNode, michael@0: int32_t* outOffset) michael@0: { michael@0: MOZ_ASSERT(aContent && outIsSpace && outIsNBSP); michael@0: MOZ_ASSERT((outNode && outOffset) || (!outNode && !outOffset)); michael@0: *outIsSpace = false; michael@0: *outIsNBSP = false; michael@0: if (outNode && outOffset) { michael@0: *outNode = nullptr; michael@0: *outOffset = -1; michael@0: } michael@0: michael@0: if (aContent->IsNodeOfType(nsINode::eTEXT) && michael@0: (uint32_t)aOffset < aContent->Length()) { michael@0: char16_t ch = aContent->GetText()->CharAt(aOffset); michael@0: *outIsSpace = nsCRT::IsAsciiSpace(ch); michael@0: *outIsNBSP = (ch == nbsp); michael@0: if (outNode && outOffset) { michael@0: NS_IF_ADDREF(*outNode = aContent); michael@0: // yes, this is _past_ the character michael@0: *outOffset = aOffset + 1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // IsPrevCharInNodeWhitespace: checks the adjacent content in the same node to michael@0: // see if following selection is whitespace michael@0: void michael@0: nsHTMLEditor::IsPrevCharInNodeWhitespace(nsIContent* aContent, michael@0: int32_t aOffset, michael@0: bool* outIsSpace, michael@0: bool* outIsNBSP, michael@0: nsIContent** outNode, michael@0: int32_t* outOffset) michael@0: { michael@0: MOZ_ASSERT(aContent && outIsSpace && outIsNBSP); michael@0: MOZ_ASSERT((outNode && outOffset) || (!outNode && !outOffset)); michael@0: *outIsSpace = false; michael@0: *outIsNBSP = false; michael@0: if (outNode && outOffset) { michael@0: *outNode = nullptr; michael@0: *outOffset = -1; michael@0: } michael@0: michael@0: if (aContent->IsNodeOfType(nsINode::eTEXT) && aOffset > 0) { michael@0: char16_t ch = aContent->GetText()->CharAt(aOffset - 1); michael@0: *outIsSpace = nsCRT::IsAsciiSpace(ch); michael@0: *outIsNBSP = (ch == nbsp); michael@0: if (outNode && outOffset) { michael@0: NS_IF_ADDREF(*outNode = aContent); michael@0: *outOffset = aOffset - 1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: michael@0: /* ------------ End Block methods -------------- */ michael@0: michael@0: michael@0: bool nsHTMLEditor::IsVisBreak(nsIDOMNode *aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, false); michael@0: if (!nsTextEditUtils::IsBreak(aNode)) michael@0: return false; michael@0: // check if there is a later node in block after br michael@0: nsCOMPtr priorNode, nextNode; michael@0: GetPriorHTMLNode(aNode, address_of(priorNode), true); michael@0: GetNextHTMLNode(aNode, address_of(nextNode), true); michael@0: // if we are next to another break, we are visible michael@0: if (priorNode && nsTextEditUtils::IsBreak(priorNode)) michael@0: return true; michael@0: if (nextNode && nsTextEditUtils::IsBreak(nextNode)) michael@0: return true; michael@0: michael@0: // if we are right before block boundary, then br not visible michael@0: NS_ENSURE_TRUE(nextNode, false); // this break is trailer in block, it's not visible michael@0: if (IsBlockNode(nextNode)) michael@0: return false; // break is right before a block, it's not visible michael@0: michael@0: // sigh. We have to use expensive whitespace calculation code to michael@0: // determine what is going on michael@0: nsCOMPtr selNode, tmp; michael@0: int32_t selOffset; michael@0: selNode = GetNodeLocation(aNode, &selOffset); michael@0: selOffset++; // lets look after the break michael@0: nsWSRunObject wsObj(this, selNode, selOffset); michael@0: nsCOMPtr visNode; michael@0: int32_t visOffset=0; michael@0: WSType visType; michael@0: wsObj.NextVisibleNode(selNode, selOffset, address_of(visNode), &visOffset, &visType); michael@0: if (visType & WSType::block) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::BreakIsVisible(nsIDOMNode *aNode, bool *aIsVisible) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aNode && aIsVisible); michael@0: michael@0: *aIsVisible = IsVisBreak(aNode); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetIsDocumentEditable(bool *aIsDocumentEditable) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aIsDocumentEditable); michael@0: michael@0: nsCOMPtr doc = GetDOMDocument(); michael@0: *aIsDocumentEditable = doc && IsModifiable(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool nsHTMLEditor::IsModifiable() michael@0: { michael@0: return !IsReadonly(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::UpdateBaseURL() michael@0: { michael@0: nsCOMPtr domDoc = GetDOMDocument(); michael@0: NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE); michael@0: michael@0: // Look for an HTML tag michael@0: nsCOMPtr nodeList; michael@0: nsresult rv = domDoc->GetElementsByTagName(NS_LITERAL_STRING("base"), getter_AddRefs(nodeList)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr baseNode; michael@0: if (nodeList) michael@0: { michael@0: uint32_t count; michael@0: nodeList->GetLength(&count); michael@0: if (count >= 1) michael@0: { michael@0: rv = nodeList->Item(0, getter_AddRefs(baseNode)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: // If no base tag, then set baseURL to the document's URL michael@0: // This is very important, else relative URLs for links and images are wrong michael@0: if (!baseNode) michael@0: { michael@0: nsCOMPtr doc = do_QueryInterface(domDoc); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); michael@0: michael@0: return doc->SetBaseURI(doc->GetDocumentURI()); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* This routine is needed to provide a bottleneck for typing for logging michael@0: purposes. Can't use HandleKeyPress() (above) for that since it takes michael@0: a nsIDOMKeyEvent* parameter. So instead we pass enough info through michael@0: to TypedText() to determine what action to take, but without passing michael@0: an event. michael@0: */ michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::TypedText(const nsAString& aString, ETypingAction aAction) michael@0: { michael@0: nsAutoPlaceHolderBatch batch(this, nsGkAtoms::TypingTxnName); michael@0: michael@0: if (aAction == eTypedBR) { michael@0: // only inserts a br node michael@0: nsCOMPtr brNode; michael@0: return InsertBR(address_of(brNode)); michael@0: } michael@0: michael@0: return nsPlaintextEditor::TypedText(aString, aAction); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::TabInTable(bool inIsShift, bool *outHandled) michael@0: { michael@0: NS_ENSURE_TRUE(outHandled, NS_ERROR_NULL_POINTER); michael@0: *outHandled = false; michael@0: michael@0: // Find enclosing table cell from the selection (cell may be the selected element) michael@0: nsCOMPtr cellElement; michael@0: // can't use |NS_LITERAL_STRING| here until |GetElementOrParentByTagName| is fixed to accept readables michael@0: nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr, getter_AddRefs(cellElement)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // Do nothing -- we didn't find a table cell michael@0: NS_ENSURE_TRUE(cellElement, NS_OK); michael@0: michael@0: // find enclosing table michael@0: nsCOMPtr tbl = GetEnclosingTable(cellElement); michael@0: NS_ENSURE_TRUE(tbl, res); michael@0: michael@0: // advance to next cell michael@0: // first create an iterator over the table michael@0: nsCOMPtr iter = michael@0: do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr cTbl = do_QueryInterface(tbl); michael@0: nsCOMPtr cBlock = do_QueryInterface(cellElement); michael@0: res = iter->Init(cTbl); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // position iter at block michael@0: res = iter->PositionAt(cBlock); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr node; michael@0: do michael@0: { michael@0: if (inIsShift) michael@0: iter->Prev(); michael@0: else michael@0: iter->Next(); michael@0: michael@0: node = do_QueryInterface(iter->GetCurrentNode()); michael@0: michael@0: if (node && nsHTMLEditUtils::IsTableCell(node) && michael@0: GetEnclosingTable(node) == tbl) michael@0: { michael@0: res = CollapseSelectionToDeepestNonTableFirstChild(nullptr, node); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: *outHandled = true; michael@0: return NS_OK; michael@0: } michael@0: } while (!iter->IsDone()); michael@0: michael@0: if (!(*outHandled) && !inIsShift) michael@0: { michael@0: // if we havent handled it yet then we must have run off the end of michael@0: // the table. Insert a new row. michael@0: res = InsertTableRow(1, true); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: *outHandled = true; michael@0: // put selection in right place michael@0: // Use table code to get selection and index to new row... michael@0: nsCOMPtrselection; michael@0: nsCOMPtr tblElement; michael@0: nsCOMPtr cell; michael@0: int32_t row; michael@0: res = GetCellContext(getter_AddRefs(selection), michael@0: getter_AddRefs(tblElement), michael@0: getter_AddRefs(cell), michael@0: nullptr, nullptr, michael@0: &row, nullptr); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // ...so that we can ask for first cell in that row... michael@0: res = GetCellAt(tblElement, row, 0, getter_AddRefs(cell)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // ...and then set selection there. michael@0: // (Note that normally you should use CollapseSelectionToDeepestNonTableFirstChild(), michael@0: // but we know cell is an empty new cell, so this works fine) michael@0: node = do_QueryInterface(cell); michael@0: if (node) selection->Collapse(node,0); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::CreateBR(nsIDOMNode *aNode, int32_t aOffset, nsCOMPtr *outBRNode, EDirection aSelect) michael@0: { michael@0: nsCOMPtr parent = aNode; michael@0: int32_t offset = aOffset; michael@0: return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::CollapseSelectionToDeepestNonTableFirstChild(nsISelection *aSelection, nsIDOMNode *aNode) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: nsresult res; michael@0: michael@0: nsCOMPtr selection; michael@0: if (aSelection) michael@0: { michael@0: selection = aSelection; michael@0: } else { michael@0: res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); michael@0: } michael@0: nsCOMPtr node = aNode; michael@0: nsCOMPtr child; michael@0: michael@0: do { michael@0: node->GetFirstChild(getter_AddRefs(child)); michael@0: michael@0: if (child) michael@0: { michael@0: // Stop if we find a table michael@0: // don't want to go into nested tables michael@0: if (nsHTMLEditUtils::IsTable(child)) break; michael@0: // hey, it'g gotta be a container too! michael@0: if (!IsContainer(child)) break; michael@0: node = child; michael@0: } michael@0: } michael@0: while (child); michael@0: michael@0: selection->Collapse(node,0); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // This is mostly like InsertHTMLWithCharsetAndContext, michael@0: // but we can't use that because it is selection-based and michael@0: // the rules code won't let us edit under the node michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::ReplaceHeadContentsWithHTML(const nsAString& aSourceToInsert) michael@0: { michael@0: nsAutoRules beginRulesSniffing(this, EditAction::ignore, nsIEditor::eNone); // don't do any post processing, rules get confused michael@0: nsCOMPtr selection; michael@0: nsresult res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: ForceCompositionEnd(); michael@0: michael@0: // Do not use nsAutoRules -- rules code won't let us insert in michael@0: // Use the head node as a parent and delete/insert directly michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsCOMPtrnodeList; michael@0: res = doc->GetElementsByTagName(NS_LITERAL_STRING("head"), getter_AddRefs(nodeList)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(nodeList, NS_ERROR_NULL_POINTER); michael@0: michael@0: uint32_t count; michael@0: nodeList->GetLength(&count); michael@0: if (count < 1) return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr headNode; michael@0: res = nodeList->Item(0, getter_AddRefs(headNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(headNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: // First, make sure there are no return chars in the source. michael@0: // Bad things happen if you insert returns (instead of dom newlines, \n) michael@0: // into an editor document. michael@0: nsAutoString inputString (aSourceToInsert); // hope this does copy-on-write michael@0: michael@0: // Windows linebreaks: Map CRLF to LF: michael@0: inputString.ReplaceSubstring(MOZ_UTF16("\r\n"), michael@0: MOZ_UTF16("\n")); michael@0: michael@0: // Mac linebreaks: Map any remaining CR to LF: michael@0: inputString.ReplaceSubstring(MOZ_UTF16("\r"), michael@0: MOZ_UTF16("\n")); michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: michael@0: res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Get the first range in the selection, for context: michael@0: nsCOMPtr range; michael@0: res = selection->GetRangeAt(0, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr docfrag; michael@0: res = range->CreateContextualFragment(inputString, michael@0: getter_AddRefs(docfrag)); michael@0: michael@0: //XXXX BUG 50965: This is not returning the text between ... michael@0: // Special code is needed in JS to handle title anyway, so it really doesn't matter! michael@0: michael@0: if (NS_FAILED(res)) michael@0: { michael@0: #ifdef DEBUG michael@0: printf("Couldn't create contextual fragment: error was %X\n", michael@0: static_cast(res)); michael@0: #endif michael@0: return res; michael@0: } michael@0: NS_ENSURE_TRUE(docfrag, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr child; michael@0: michael@0: // First delete all children in head michael@0: do { michael@0: res = headNode->GetFirstChild(getter_AddRefs(child)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (child) michael@0: { michael@0: res = DeleteNode(child); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } while (child); michael@0: michael@0: // Now insert the new nodes michael@0: int32_t offsetOfNewNode = 0; michael@0: nsCOMPtr fragmentAsNode (do_QueryInterface(docfrag)); michael@0: michael@0: // Loop over the contents of the fragment and move into the document michael@0: do { michael@0: res = fragmentAsNode->GetFirstChild(getter_AddRefs(child)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (child) michael@0: { michael@0: res = InsertNode(child, headNode, offsetOfNewNode++); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } while (child); michael@0: michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::RebuildDocumentFromSource(const nsAString& aSourceString) michael@0: { michael@0: ForceCompositionEnd(); michael@0: michael@0: nsCOMPtrselection; michael@0: nsresult res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr bodyElement = do_QueryInterface(GetRoot()); michael@0: NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Find where the tag starts. michael@0: nsReadingIterator beginbody; michael@0: nsReadingIterator endbody; michael@0: aSourceString.BeginReading(beginbody); michael@0: aSourceString.EndReading(endbody); michael@0: bool foundbody = CaseInsensitiveFindInReadable(NS_LITERAL_STRING(" beginhead; michael@0: nsReadingIterator endhead; michael@0: aSourceString.BeginReading(beginhead); michael@0: aSourceString.EndReading(endhead); michael@0: bool foundhead = CaseInsensitiveFindInReadable(NS_LITERAL_STRING(" beginbody.get()) michael@0: foundhead = false; michael@0: michael@0: nsReadingIterator beginclosehead; michael@0: nsReadingIterator endclosehead; michael@0: aSourceString.BeginReading(beginclosehead); michael@0: aSourceString.EndReading(endclosehead); michael@0: michael@0: // Find the index after "" michael@0: bool foundclosehead = CaseInsensitiveFindInReadable( michael@0: NS_LITERAL_STRING(""), beginclosehead, endclosehead); michael@0: // a valid close head appears after a found head michael@0: if (foundhead && beginhead.get() > beginclosehead.get()) michael@0: foundclosehead = false; michael@0: // a valid close head appears before a found body michael@0: if (foundbody && beginclosehead.get() > beginbody.get()) michael@0: foundclosehead = false; michael@0: michael@0: // Time to change the document michael@0: nsAutoEditBatch beginBatching(this); michael@0: michael@0: nsReadingIterator endtotal; michael@0: aSourceString.EndReading(endtotal); michael@0: michael@0: if (foundhead) { michael@0: if (foundclosehead) michael@0: res = ReplaceHeadContentsWithHTML(Substring(beginhead, beginclosehead)); michael@0: else if (foundbody) michael@0: res = ReplaceHeadContentsWithHTML(Substring(beginhead, beginbody)); michael@0: else michael@0: // XXX Without recourse to some parser/content sink/docshell hackery michael@0: // we don't really know where the head ends and the body begins michael@0: // so we assume that there is no body michael@0: res = ReplaceHeadContentsWithHTML(Substring(beginhead, endtotal)); michael@0: } else { michael@0: nsReadingIterator begintotal; michael@0: aSourceString.BeginReading(begintotal); michael@0: NS_NAMED_LITERAL_STRING(head, ""); michael@0: if (foundclosehead) michael@0: res = ReplaceHeadContentsWithHTML(head + Substring(begintotal, beginclosehead)); michael@0: else if (foundbody) michael@0: res = ReplaceHeadContentsWithHTML(head + Substring(begintotal, beginbody)); michael@0: else michael@0: // XXX Without recourse to some parser/content sink/docshell hackery michael@0: // we don't really know where the head ends and the body begins michael@0: // so we assume that there is no head michael@0: res = ReplaceHeadContentsWithHTML(head); michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: res = SelectAll(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (!foundbody) { michael@0: NS_NAMED_LITERAL_STRING(body, ""); michael@0: // XXX Without recourse to some parser/content sink/docshell hackery michael@0: // we don't really know where the head ends and the body begins michael@0: if (foundclosehead) // assume body starts after the head ends michael@0: res = LoadHTML(body + Substring(endclosehead, endtotal)); michael@0: else if (foundhead) // assume there is no body michael@0: res = LoadHTML(body); michael@0: else // assume there is no head, the entire source is body michael@0: res = LoadHTML(body + aSourceString); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr divElement; michael@0: res = CreateElementWithDefaults(NS_LITERAL_STRING("div"), getter_AddRefs(divElement)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: res = CloneAttributes(bodyElement, divElement); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: return BeginningOfDocument(); michael@0: } michael@0: michael@0: res = LoadHTML(Substring(beginbody, endtotal)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // Now we must copy attributes user might have edited on the tag michael@0: // because InsertHTML (actually, CreateContextualFragment()) michael@0: // will never return a body node in the DOM fragment michael@0: michael@0: // We already know where " beginclosebody = beginbody; michael@0: nsReadingIterator endclosebody; michael@0: aSourceString.EndReading(endclosebody); michael@0: if (!FindInReadable(NS_LITERAL_STRING(">"),beginclosebody,endclosebody)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Truncate at the end of the body tag michael@0: // Kludge of the year: fool the parser by replacing "body" with "div" so we get a node michael@0: nsAutoString bodyTag; michael@0: bodyTag.AssignLiteral("
range; michael@0: res = selection->GetRangeAt(0, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr docfrag; michael@0: res = range->CreateContextualFragment(bodyTag, getter_AddRefs(docfrag)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr fragmentAsNode (do_QueryInterface(docfrag)); michael@0: NS_ENSURE_TRUE(fragmentAsNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr child; michael@0: res = fragmentAsNode->GetFirstChild(getter_AddRefs(child)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(child, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Copy all attributes from the div child to current body element michael@0: res = CloneAttributes(bodyElement, child); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // place selection at first editable content michael@0: return BeginningOfDocument(); michael@0: } michael@0: michael@0: void michael@0: nsHTMLEditor::NormalizeEOLInsertPosition(nsIDOMNode *firstNodeToInsert, michael@0: nsCOMPtr *insertParentNode, michael@0: int32_t *insertOffset) michael@0: { michael@0: /* michael@0: This function will either correct the position passed in, michael@0: or leave the position unchanged. michael@0: michael@0: When the (first) item to insert is a block level element, michael@0: and our insertion position is after the last visible item in a line, michael@0: i.e. the insertion position is just before a visible line break
, michael@0: we want to skip to the position just after the line break (see bug 68767) michael@0: michael@0: However, our logic to detect whether we should skip or not michael@0: needs to be more clever. michael@0: We must not skip when the caret appears to be positioned at the beginning michael@0: of a block, in that case skipping the
would not insert the
michael@0: at the caret position, but after the current empty line. michael@0: michael@0: So we have several cases to test: michael@0: michael@0: 1) We only ever want to skip, if the next visible thing after the current position is a break michael@0: michael@0: 2) We do not want to skip if there is no previous visible thing at all michael@0: That is detected if the call to PriorVisibleNode gives us an offset of zero. michael@0: Because PriorVisibleNode always positions after the prior node, we would michael@0: see an offset > 0, if there were a prior node. michael@0: michael@0: 3) We do not want to skip, if both the next and the previous visible things are breaks. michael@0: michael@0: 4) We do not want to skip if the previous visible thing is in a different block michael@0: than the insertion position. michael@0: */ michael@0: michael@0: if (!IsBlockNode(firstNodeToInsert)) michael@0: return; michael@0: michael@0: nsWSRunObject wsObj(this, *insertParentNode, *insertOffset); michael@0: nsCOMPtr nextVisNode; michael@0: nsCOMPtr prevVisNode; michael@0: int32_t nextVisOffset=0; michael@0: WSType nextVisType; michael@0: int32_t prevVisOffset=0; michael@0: WSType prevVisType; michael@0: michael@0: wsObj.NextVisibleNode(*insertParentNode, *insertOffset, address_of(nextVisNode), &nextVisOffset, &nextVisType); michael@0: if (!nextVisNode) michael@0: return; michael@0: michael@0: if (!(nextVisType & WSType::br)) { michael@0: return; michael@0: } michael@0: michael@0: wsObj.PriorVisibleNode(*insertParentNode, *insertOffset, address_of(prevVisNode), &prevVisOffset, &prevVisType); michael@0: if (!prevVisNode) michael@0: return; michael@0: michael@0: if (prevVisType & WSType::br) { michael@0: return; michael@0: } michael@0: michael@0: if (prevVisType & WSType::thisBlock) { michael@0: return; michael@0: } michael@0: michael@0: int32_t brOffset=0; michael@0: nsCOMPtr brNode = GetNodeLocation(nextVisNode, &brOffset); michael@0: michael@0: *insertParentNode = brNode; michael@0: *insertOffset = brOffset + 1; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::InsertElementAtSelection(nsIDOMElement* aElement, bool aDeleteSelection) michael@0: { michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: nsresult res = NS_ERROR_NOT_INITIALIZED; michael@0: michael@0: NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr node = do_QueryInterface(aElement); michael@0: michael@0: ForceCompositionEnd(); michael@0: nsAutoEditBatch beginBatching(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::insertElement, nsIEditor::eNext); michael@0: michael@0: nsRefPtr selection = GetSelection(); michael@0: if (!selection) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // hand off to the rules system, see if it has anything to say about this michael@0: bool cancel, handled; michael@0: nsTextRulesInfo ruleInfo(EditAction::insertElement); michael@0: ruleInfo.insertElement = aElement; michael@0: res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: if (cancel || (NS_FAILED(res))) return res; michael@0: michael@0: if (!handled) michael@0: { michael@0: if (aDeleteSelection) michael@0: { michael@0: if (!IsBlockNode(aElement)) { michael@0: // E.g., inserting an image. In this case we don't need to delete any michael@0: // inline wrappers before we do the insertion. Otherwise we let michael@0: // DeleteSelectionAndPrepareToCreateNode do the deletion for us, which michael@0: // calls DeleteSelection with aStripWrappers = eStrip. michael@0: res = DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: nsresult result = DeleteSelectionAndPrepareToCreateNode(); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: michael@0: // If deleting, selection will be collapsed. michael@0: // so if not, we collapse it michael@0: if (!aDeleteSelection) michael@0: { michael@0: // Named Anchor is a special case, michael@0: // We collapse to insert element BEFORE the selection michael@0: // For all other tags, we insert AFTER the selection michael@0: if (nsHTMLEditUtils::IsNamedAnchor(node)) michael@0: { michael@0: selection->CollapseToStart(); michael@0: } else { michael@0: selection->CollapseToEnd(); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr parentSelectedNode; michael@0: int32_t offsetForInsert; michael@0: res = selection->GetAnchorNode(getter_AddRefs(parentSelectedNode)); michael@0: // XXX: ERROR_HANDLING bad XPCOM usage michael@0: if (NS_SUCCEEDED(res) && NS_SUCCEEDED(selection->GetAnchorOffset(&offsetForInsert)) && parentSelectedNode) michael@0: { michael@0: #ifdef DEBUG_cmanske michael@0: { michael@0: nsAutoString name; michael@0: parentSelectedNode->GetNodeName(name); michael@0: printf("InsertElement: Anchor node of selection: "); michael@0: wprintf(name.get()); michael@0: printf(" Offset: %d\n", offsetForInsert); michael@0: } michael@0: #endif michael@0: michael@0: // Adjust position based on the node we are going to insert. michael@0: NormalizeEOLInsertPosition(node, address_of(parentSelectedNode), &offsetForInsert); michael@0: michael@0: res = InsertNodeAtPoint(node, address_of(parentSelectedNode), &offsetForInsert, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // Set caret after element, but check for special case michael@0: // of inserting table-related elements: set in first cell instead michael@0: if (!SetCaretInTableCell(aElement)) michael@0: { michael@0: res = SetCaretAfterElement(aElement); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: // check for inserting a whole table at the end of a block. If so insert a br after it. michael@0: if (nsHTMLEditUtils::IsTable(node)) michael@0: { michael@0: bool isLast; michael@0: res = IsLastEditableChild(node, &isLast); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (isLast) michael@0: { michael@0: nsCOMPtr brNode; michael@0: res = CreateBR(parentSelectedNode, offsetForInsert+1, address_of(brNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: selection->Collapse(parentSelectedNode, offsetForInsert+1); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: res = mRules->DidDoAction(selection, &ruleInfo, res); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /* michael@0: InsertNodeAtPoint: attempts to insert aNode into the document, at a point specified by michael@0: {*ioParent,*ioOffset}. Checks with strict dtd to see if containment is allowed. If not michael@0: allowed, will attempt to find a parent in the parent hierarchy of *ioParent that will michael@0: accept aNode as a child. If such a parent is found, will split the document tree from michael@0: {*ioParent,*ioOffset} up to parent, and then insert aNode. ioParent & ioOffset are then michael@0: adjusted to point to the actual location that aNode was inserted at. aNoEmptyNodes michael@0: specifies if the splitting process is allowed to reslt in empty nodes. michael@0: nsIDOMNode *aNode node to insert michael@0: nsCOMPtr *ioParent insertion parent michael@0: int32_t *ioOffset insertion offset michael@0: bool aNoEmptyNodes splitting can result in empty nodes? michael@0: */ michael@0: nsresult michael@0: nsHTMLEditor::InsertNodeAtPoint(nsIDOMNode *aNode, michael@0: nsCOMPtr *ioParent, michael@0: int32_t *ioOffset, michael@0: bool aNoEmptyNodes) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: NS_ENSURE_TRUE(ioParent, NS_ERROR_NULL_POINTER); michael@0: NS_ENSURE_TRUE(*ioParent, NS_ERROR_NULL_POINTER); michael@0: NS_ENSURE_TRUE(ioOffset, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult res = NS_OK; michael@0: nsCOMPtr parent = *ioParent; michael@0: nsCOMPtr topChild = *ioParent; michael@0: nsCOMPtr tmp; michael@0: int32_t offsetOfInsert = *ioOffset; michael@0: michael@0: // Search up the parent chain to find a suitable container michael@0: while (!CanContain(parent, aNode)) { michael@0: // If the current parent is a root (body or table element) michael@0: // then go no further - we can't insert michael@0: if (nsTextEditUtils::IsBody(parent) || nsHTMLEditUtils::IsTableElement(parent)) michael@0: return NS_ERROR_FAILURE; michael@0: // Get the next parent michael@0: parent->GetParentNode(getter_AddRefs(tmp)); michael@0: NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); michael@0: topChild = parent; michael@0: parent = tmp; michael@0: } michael@0: if (parent != topChild) michael@0: { michael@0: // we need to split some levels above the original selection parent michael@0: res = SplitNodeDeep(topChild, *ioParent, *ioOffset, &offsetOfInsert, aNoEmptyNodes); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: *ioParent = parent; michael@0: *ioOffset = offsetOfInsert; michael@0: } michael@0: // Now we can insert the new node michael@0: res = InsertNode(aNode, parent, offsetOfInsert); michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::SelectElement(nsIDOMElement* aElement) michael@0: { michael@0: nsresult res = NS_ERROR_NULL_POINTER; michael@0: michael@0: // Must be sure that element is contained in the document body michael@0: if (IsDescendantOfEditorRoot(aElement)) { michael@0: nsCOMPtr selection; michael@0: res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtrparent; michael@0: res = aElement->GetParentNode(getter_AddRefs(parent)); michael@0: if (NS_SUCCEEDED(res) && parent) michael@0: { michael@0: int32_t offsetInParent = GetChildOffset(aElement, parent); michael@0: michael@0: // Collapse selection to just before desired element, michael@0: res = selection->Collapse(parent, offsetInParent); michael@0: if (NS_SUCCEEDED(res)) { michael@0: // then extend it to just after michael@0: res = selection->Extend(parent, offsetInParent + 1); michael@0: } michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::SetCaretAfterElement(nsIDOMElement* aElement) michael@0: { michael@0: nsresult res = NS_ERROR_NULL_POINTER; michael@0: michael@0: // Be sure the element is contained in the document body michael@0: if (aElement && IsDescendantOfEditorRoot(aElement)) { michael@0: nsCOMPtr selection; michael@0: res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtrparent; michael@0: res = aElement->GetParentNode(getter_AddRefs(parent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); michael@0: int32_t offsetInParent = GetChildOffset(aElement, parent); michael@0: // Collapse selection to just after desired element, michael@0: res = selection->Collapse(parent, offsetInParent + 1); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::SetParagraphFormat(const nsAString& aParagraphFormat) michael@0: { michael@0: nsAutoString tag; tag.Assign(aParagraphFormat); michael@0: ToLowerCase(tag); michael@0: if (tag.EqualsLiteral("dd") || tag.EqualsLiteral("dt")) michael@0: return MakeDefinitionItem(tag); michael@0: else michael@0: return InsertBasicBlock(tag); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetParagraphState(bool *aMixed, nsAString &outFormat) michael@0: { michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER); michael@0: nsRefPtr htmlRules = static_cast(mRules.get()); michael@0: michael@0: return htmlRules->GetParagraphState(aMixed, outFormat); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetBackgroundColorState(bool *aMixed, nsAString &aOutColor) michael@0: { michael@0: nsresult res; michael@0: if (IsCSSEnabled()) { michael@0: // if we are in CSS mode, we have to check if the containing block defines michael@0: // a background color michael@0: res = GetCSSBackgroundColorState(aMixed, aOutColor, true); michael@0: } michael@0: else { michael@0: // in HTML mode, we look only at page's background michael@0: res = GetHTMLBackgroundColorState(aMixed, aOutColor); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetHighlightColorState(bool *aMixed, nsAString &aOutColor) michael@0: { michael@0: nsresult res = NS_OK; michael@0: *aMixed = false; michael@0: aOutColor.AssignLiteral("transparent"); michael@0: if (IsCSSEnabled()) { michael@0: // in CSS mode, text background can be added by the Text Highlight button michael@0: // we need to query the background of the selection without looking for michael@0: // the block container of the ranges in the selection michael@0: res = GetCSSBackgroundColorState(aMixed, aOutColor, false); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetCSSBackgroundColorState(bool *aMixed, nsAString &aOutColor, bool aBlockLevel) michael@0: { michael@0: NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER); michael@0: *aMixed = false; michael@0: // the default background color is transparent michael@0: aOutColor.AssignLiteral("transparent"); michael@0: michael@0: // get selection michael@0: nsCOMPtrselection; michael@0: nsresult res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // get selection location michael@0: nsCOMPtr parent; michael@0: int32_t offset; michael@0: res = GetStartNodeAndOffset(selection, getter_AddRefs(parent), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); michael@0: michael@0: // is the selection collapsed? michael@0: nsCOMPtr nodeToExamine; michael@0: if (selection->Collapsed() || IsTextNode(parent)) { michael@0: // we want to look at the parent and ancestors michael@0: nodeToExamine = parent; michael@0: } else { michael@0: // otherwise we want to look at the first editable node after michael@0: // {parent,offset} and its ancestors for divs with alignment on them michael@0: nodeToExamine = GetChildAt(parent, offset); michael@0: //GetNextNode(parent, offset, true, address_of(nodeToExamine)); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER); michael@0: michael@0: // is the node to examine a block ? michael@0: bool isBlock; michael@0: res = NodeIsBlockStatic(nodeToExamine, &isBlock); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr tmp; michael@0: michael@0: if (aBlockLevel) { michael@0: // we are querying the block background (and not the text background), let's michael@0: // climb to the block container michael@0: nsCOMPtr blockParent = nodeToExamine; michael@0: if (!isBlock) { michael@0: blockParent = GetBlockNodeParent(nodeToExamine); michael@0: NS_ENSURE_TRUE(blockParent, NS_OK); michael@0: } michael@0: michael@0: // Make sure to not walk off onto the Document node michael@0: nsCOMPtr element; michael@0: do { michael@0: // retrieve the computed style of background-color for blockParent michael@0: mHTMLCSSUtils->GetComputedProperty(blockParent, michael@0: nsEditProperty::cssBackgroundColor, michael@0: aOutColor); michael@0: tmp.swap(blockParent); michael@0: res = tmp->GetParentNode(getter_AddRefs(blockParent)); michael@0: element = do_QueryInterface(blockParent); michael@0: // look at parent if the queried color is transparent and if the node to michael@0: // examine is not the root of the document michael@0: } while (aOutColor.EqualsLiteral("transparent") && element); michael@0: if (aOutColor.EqualsLiteral("transparent")) { michael@0: // we have hit the root of the document and the color is still transparent ! michael@0: // Grumble... Let's look at the default background color because that's the michael@0: // color we are looking for michael@0: mHTMLCSSUtils->GetDefaultBackgroundColor(aOutColor); michael@0: } michael@0: } michael@0: else { michael@0: // no, we are querying the text background for the Text Highlight button michael@0: if (IsTextNode(nodeToExamine)) { michael@0: // if the node of interest is a text node, let's climb a level michael@0: res = nodeToExamine->GetParentNode(getter_AddRefs(parent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nodeToExamine = parent; michael@0: } michael@0: do { michael@0: // is the node to examine a block ? michael@0: res = NodeIsBlockStatic(nodeToExamine, &isBlock); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (isBlock) { michael@0: // yes it is a block; in that case, the text background color is transparent michael@0: aOutColor.AssignLiteral("transparent"); michael@0: break; michael@0: } michael@0: else { michael@0: // no, it's not; let's retrieve the computed style of background-color for the michael@0: // node to examine michael@0: mHTMLCSSUtils->GetComputedProperty(nodeToExamine, nsEditProperty::cssBackgroundColor, michael@0: aOutColor); michael@0: if (!aOutColor.EqualsLiteral("transparent")) { michael@0: break; michael@0: } michael@0: } michael@0: tmp.swap(nodeToExamine); michael@0: res = tmp->GetParentNode(getter_AddRefs(nodeToExamine)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } while ( aOutColor.EqualsLiteral("transparent") && nodeToExamine ); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetHTMLBackgroundColorState(bool *aMixed, nsAString &aOutColor) michael@0: { michael@0: //TODO: We don't handle "mixed" correctly! michael@0: NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER); michael@0: *aMixed = false; michael@0: aOutColor.Truncate(); michael@0: michael@0: nsCOMPtr domElement; michael@0: int32_t selectedCount; michael@0: nsAutoString tagName; michael@0: nsresult res = GetSelectedOrParentTableElement(tagName, michael@0: &selectedCount, michael@0: getter_AddRefs(domElement)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr element = do_QueryInterface(domElement); michael@0: michael@0: while (element) { michael@0: // We are in a cell or selected table michael@0: element->GetAttr(kNameSpaceID_None, nsGkAtoms::bgcolor, aOutColor); michael@0: michael@0: // Done if we have a color explicitly set michael@0: if (!aOutColor.IsEmpty()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Once we hit the body, we're done michael@0: if (element->IsHTML(nsGkAtoms::body)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // No color is set, but we need to report visible color inherited michael@0: // from nested cells/tables, so search up parent chain michael@0: element = element->GetParentElement(); michael@0: } michael@0: michael@0: // If no table or cell found, get page body michael@0: dom::Element* bodyElement = GetRoot(); michael@0: NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: bodyElement->GetAttr(kNameSpaceID_None, nsGkAtoms::bgcolor, aOutColor); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetListState(bool *aMixed, bool *aOL, bool *aUL, bool *aDL) michael@0: { michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER); michael@0: nsRefPtr htmlRules = static_cast(mRules.get()); michael@0: michael@0: return htmlRules->GetListState(aMixed, aOL, aUL, aDL); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetListItemState(bool *aMixed, bool *aLI, bool *aDT, bool *aDD) michael@0: { michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: NS_ENSURE_TRUE(aMixed && aLI && aDT && aDD, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsRefPtr htmlRules = static_cast(mRules.get()); michael@0: michael@0: return htmlRules->GetListItemState(aMixed, aLI, aDT, aDD); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetAlignment(bool *aMixed, nsIHTMLEditor::EAlignment *aAlign) michael@0: { michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: NS_ENSURE_TRUE(aMixed && aAlign, NS_ERROR_NULL_POINTER); michael@0: nsRefPtr htmlRules = static_cast(mRules.get()); michael@0: michael@0: return htmlRules->GetAlignment(aMixed, aAlign); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetIndentState(bool *aCanIndent, bool *aCanOutdent) michael@0: { michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsRefPtr htmlRules = static_cast(mRules.get()); michael@0: michael@0: return htmlRules->GetIndentState(aCanIndent, aCanOutdent); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::MakeOrChangeList(const nsAString& aListType, bool entireList, const nsAString& aBulletType) michael@0: { michael@0: nsresult res; michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: bool cancel, handled; michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::makeList, nsIEditor::eNext); michael@0: michael@0: // pre-process michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsTextRulesInfo ruleInfo(EditAction::makeList); michael@0: ruleInfo.blockType = &aListType; michael@0: ruleInfo.entireList = entireList; michael@0: ruleInfo.bulletType = &aBulletType; michael@0: res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: if (cancel || (NS_FAILED(res))) return res; michael@0: michael@0: if (!handled) michael@0: { michael@0: // Find out if the selection is collapsed: michael@0: bool isCollapsed = selection->Collapsed(); michael@0: michael@0: nsCOMPtr node; michael@0: int32_t offset; michael@0: res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); michael@0: if (!node) res = NS_ERROR_FAILURE; michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (isCollapsed) michael@0: { michael@0: // have to find a place to put the list michael@0: nsCOMPtr parent = node; michael@0: nsCOMPtr topChild = node; michael@0: nsCOMPtr tmp; michael@0: michael@0: nsCOMPtr listAtom = do_GetAtom(aListType); michael@0: while (!CanContainTag(parent, listAtom)) { michael@0: parent->GetParentNode(getter_AddRefs(tmp)); michael@0: NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); michael@0: topChild = parent; michael@0: parent = tmp; michael@0: } michael@0: michael@0: if (parent != node) michael@0: { michael@0: // we need to split up to the child of parent michael@0: res = SplitNodeDeep(topChild, node, offset, &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // make a list michael@0: nsCOMPtr newList; michael@0: res = CreateNode(aListType, parent, offset, getter_AddRefs(newList)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // make a list item michael@0: nsCOMPtr newItem; michael@0: res = CreateNode(NS_LITERAL_STRING("li"), newList, 0, getter_AddRefs(newItem)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = selection->Collapse(newItem,0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: res = mRules->DidDoAction(selection, &ruleInfo, res); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::RemoveList(const nsAString& aListType) michael@0: { michael@0: nsresult res; michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: bool cancel, handled; michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::removeList, nsIEditor::eNext); michael@0: michael@0: // pre-process michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsTextRulesInfo ruleInfo(EditAction::removeList); michael@0: if (aListType.LowerCaseEqualsLiteral("ol")) michael@0: ruleInfo.bOrdered = true; michael@0: else ruleInfo.bOrdered = false; michael@0: res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: if (cancel || (NS_FAILED(res))) return res; michael@0: michael@0: // no default behavior for this yet. what would it mean? michael@0: michael@0: res = mRules->DidDoAction(selection, &ruleInfo, res); michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::MakeDefinitionItem(const nsAString& aItemType) michael@0: { michael@0: nsresult res; michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: bool cancel, handled; michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::makeDefListItem, nsIEditor::eNext); michael@0: michael@0: // pre-process michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: nsTextRulesInfo ruleInfo(EditAction::makeDefListItem); michael@0: ruleInfo.blockType = &aItemType; michael@0: res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: if (cancel || (NS_FAILED(res))) return res; michael@0: michael@0: if (!handled) michael@0: { michael@0: // todo: no default for now. we count on rules to handle it. michael@0: } michael@0: michael@0: res = mRules->DidDoAction(selection, &ruleInfo, res); michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::InsertBasicBlock(const nsAString& aBlockType) michael@0: { michael@0: nsresult res; michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: bool cancel, handled; michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::makeBasicBlock, nsIEditor::eNext); michael@0: michael@0: // pre-process michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: nsTextRulesInfo ruleInfo(EditAction::makeBasicBlock); michael@0: ruleInfo.blockType = &aBlockType; michael@0: res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: if (cancel || (NS_FAILED(res))) return res; michael@0: michael@0: if (!handled) michael@0: { michael@0: // Find out if the selection is collapsed: michael@0: bool isCollapsed = selection->Collapsed(); michael@0: michael@0: nsCOMPtr node; michael@0: int32_t offset; michael@0: res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); michael@0: if (!node) res = NS_ERROR_FAILURE; michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (isCollapsed) michael@0: { michael@0: // have to find a place to put the block michael@0: nsCOMPtr parent = node; michael@0: nsCOMPtr topChild = node; michael@0: nsCOMPtr tmp; michael@0: michael@0: nsCOMPtr blockAtom = do_GetAtom(aBlockType); michael@0: while (!CanContainTag(parent, blockAtom)) { michael@0: parent->GetParentNode(getter_AddRefs(tmp)); michael@0: NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); michael@0: topChild = parent; michael@0: parent = tmp; michael@0: } michael@0: michael@0: if (parent != node) michael@0: { michael@0: // we need to split up to the child of parent michael@0: res = SplitNodeDeep(topChild, node, offset, &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // make a block michael@0: nsCOMPtr newBlock; michael@0: res = CreateNode(aBlockType, parent, offset, getter_AddRefs(newBlock)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // reposition selection to inside the block michael@0: res = selection->Collapse(newBlock,0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: res = mRules->DidDoAction(selection, &ruleInfo, res); michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::Indent(const nsAString& aIndent) michael@0: { michael@0: nsresult res; michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: bool cancel, handled; michael@0: EditAction opID = EditAction::indent; michael@0: if (aIndent.LowerCaseEqualsLiteral("outdent")) michael@0: { michael@0: opID = EditAction::outdent; michael@0: } michael@0: nsAutoEditBatch beginBatching(this); michael@0: nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext); michael@0: michael@0: // pre-process michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsTextRulesInfo ruleInfo(opID); michael@0: res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: if (cancel || (NS_FAILED(res))) return res; michael@0: michael@0: if (!handled) michael@0: { michael@0: // Do default - insert a blockquote node if selection collapsed michael@0: nsCOMPtr node; michael@0: int32_t offset; michael@0: bool isCollapsed = selection->Collapsed(); michael@0: michael@0: res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); michael@0: if (!node) res = NS_ERROR_FAILURE; michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (aIndent.EqualsLiteral("indent")) michael@0: { michael@0: if (isCollapsed) michael@0: { michael@0: // have to find a place to put the blockquote michael@0: nsCOMPtr parent = node; michael@0: nsCOMPtr topChild = node; michael@0: nsCOMPtr tmp; michael@0: while (!CanContainTag(parent, nsGkAtoms::blockquote)) { michael@0: parent->GetParentNode(getter_AddRefs(tmp)); michael@0: NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); michael@0: topChild = parent; michael@0: parent = tmp; michael@0: } michael@0: michael@0: if (parent != node) michael@0: { michael@0: // we need to split up to the child of parent michael@0: res = SplitNodeDeep(topChild, node, offset, &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: // make a blockquote michael@0: nsCOMPtr newBQ; michael@0: res = CreateNode(NS_LITERAL_STRING("blockquote"), parent, offset, michael@0: getter_AddRefs(newBQ)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // put a space in it so layout will draw the list item michael@0: res = selection->Collapse(newBQ,0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = InsertText(NS_LITERAL_STRING(" ")); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // reposition selection to before the space character michael@0: res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = selection->Collapse(node,0); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: res = mRules->DidDoAction(selection, &ruleInfo, res); michael@0: return res; michael@0: } michael@0: michael@0: //TODO: IMPLEMENT ALIGNMENT! michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::Align(const nsAString& aAlignType) michael@0: { michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::align, nsIEditor::eNext); michael@0: michael@0: nsCOMPtr node; michael@0: bool cancel, handled; michael@0: michael@0: // Find out if the selection is collapsed: michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: nsTextRulesInfo ruleInfo(EditAction::align); michael@0: ruleInfo.alignType = &aAlignType; michael@0: nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: if (cancel || NS_FAILED(res)) michael@0: return res; michael@0: michael@0: res = mRules->DidDoAction(selection, &ruleInfo, res); michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetElementOrParentByTagName(const nsAString& aTagName, nsIDOMNode *aNode, nsIDOMElement** aReturn) michael@0: { michael@0: NS_ENSURE_TRUE(!aTagName.IsEmpty(), NS_ERROR_NULL_POINTER); michael@0: NS_ENSURE_TRUE(aReturn, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr current = do_QueryInterface(aNode); michael@0: if (!current) { michael@0: // If no node supplied, get it from anchor node of current selection michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr anchorNode = selection->GetAnchorNode(); michael@0: NS_ENSURE_TRUE(anchorNode, NS_ERROR_FAILURE); michael@0: michael@0: // Try to get the actual selected node michael@0: if (anchorNode->HasChildNodes() && anchorNode->IsContent()) { michael@0: uint32_t offset = selection->AnchorOffset(); michael@0: current = anchorNode->GetChildAt(offset); michael@0: } michael@0: // anchor node is probably a text node - just use that michael@0: if (!current) { michael@0: current = anchorNode; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr currentNode = current->AsDOMNode(); michael@0: michael@0: nsAutoString TagName(aTagName); michael@0: ToLowerCase(TagName); michael@0: bool getLink = IsLinkTag(TagName); michael@0: bool getNamedAnchor = IsNamedAnchorTag(TagName); michael@0: if ( getLink || getNamedAnchor) michael@0: { michael@0: TagName.AssignLiteral("a"); michael@0: } michael@0: bool findTableCell = TagName.EqualsLiteral("td"); michael@0: bool findList = TagName.EqualsLiteral("list"); michael@0: michael@0: // default is null - no element found michael@0: *aReturn = nullptr; michael@0: michael@0: nsCOMPtr parent; michael@0: bool bNodeFound = false; michael@0: michael@0: while (true) michael@0: { michael@0: nsAutoString currentTagName; michael@0: // Test if we have a link (an anchor with href set) michael@0: if ( (getLink && nsHTMLEditUtils::IsLink(currentNode)) || michael@0: (getNamedAnchor && nsHTMLEditUtils::IsNamedAnchor(currentNode)) ) michael@0: { michael@0: bNodeFound = true; michael@0: break; michael@0: } else { michael@0: if (findList) michael@0: { michael@0: // Match "ol", "ul", or "dl" for lists michael@0: if (nsHTMLEditUtils::IsList(currentNode)) michael@0: goto NODE_FOUND; michael@0: michael@0: } else if (findTableCell) michael@0: { michael@0: // Table cells are another special case: michael@0: // Match either "td" or "th" for them michael@0: if (nsHTMLEditUtils::IsTableCell(currentNode)) michael@0: goto NODE_FOUND; michael@0: michael@0: } else { michael@0: currentNode->GetNodeName(currentTagName); michael@0: if (currentTagName.Equals(TagName, nsCaseInsensitiveStringComparator())) michael@0: { michael@0: NODE_FOUND: michael@0: bNodeFound = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: // Search up the parent chain michael@0: // We should never fail because of root test below, but lets be safe michael@0: // XXX: ERROR_HANDLING error return code lost michael@0: if (NS_FAILED(currentNode->GetParentNode(getter_AddRefs(parent))) || !parent) michael@0: break; michael@0: michael@0: // Stop searching if parent is a body tag michael@0: nsAutoString parentTagName; michael@0: parent->GetNodeName(parentTagName); michael@0: // Note: Originally used IsRoot to stop at table cells, michael@0: // but that's too messy when you are trying to find the parent table michael@0: if(parentTagName.LowerCaseEqualsLiteral("body")) michael@0: break; michael@0: michael@0: currentNode = parent; michael@0: } michael@0: michael@0: if (!bNodeFound) { michael@0: return NS_EDITOR_ELEMENT_NOT_FOUND; michael@0: } michael@0: michael@0: nsCOMPtr currentElement = do_QueryInterface(currentNode); michael@0: currentElement.forget(aReturn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetSelectedElement(const nsAString& aTagName, nsIDOMElement** aReturn) michael@0: { michael@0: NS_ENSURE_TRUE(aReturn , NS_ERROR_NULL_POINTER); michael@0: michael@0: // default is null - no element found michael@0: *aReturn = nullptr; michael@0: michael@0: // First look for a single element in selection michael@0: nsCOMPtrselection; michael@0: nsresult res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: Selection* sel = static_cast(selection.get()); michael@0: michael@0: bool bNodeFound = false; michael@0: bool isCollapsed = selection->Collapsed(); michael@0: michael@0: nsAutoString domTagName; michael@0: nsAutoString TagName(aTagName); michael@0: ToLowerCase(TagName); michael@0: // Empty string indicates we should match any element tag michael@0: bool anyTag = (TagName.IsEmpty()); michael@0: bool isLinkTag = IsLinkTag(TagName); michael@0: bool isNamedAnchorTag = IsNamedAnchorTag(TagName); michael@0: michael@0: nsCOMPtr selectedElement; michael@0: nsCOMPtr range; michael@0: res = selection->GetRangeAt(0, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr startParent; michael@0: int32_t startOffset, endOffset; michael@0: res = range->GetStartContainer(getter_AddRefs(startParent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetStartOffset(&startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr endParent; michael@0: res = range->GetEndContainer(getter_AddRefs(endParent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndOffset(&endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // Optimization for a single selected element michael@0: if (startParent && startParent == endParent && (endOffset-startOffset) == 1) michael@0: { michael@0: nsCOMPtr selectedNode = GetChildAt(startParent, startOffset); michael@0: NS_ENSURE_SUCCESS(res, NS_OK); michael@0: if (selectedNode) michael@0: { michael@0: selectedNode->GetNodeName(domTagName); michael@0: ToLowerCase(domTagName); michael@0: michael@0: // Test for appropriate node type requested michael@0: if (anyTag || (TagName == domTagName) || michael@0: (isLinkTag && nsHTMLEditUtils::IsLink(selectedNode)) || michael@0: (isNamedAnchorTag && nsHTMLEditUtils::IsNamedAnchor(selectedNode))) michael@0: { michael@0: bNodeFound = true; michael@0: selectedElement = do_QueryInterface(selectedNode); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!bNodeFound) michael@0: { michael@0: if (isLinkTag) michael@0: { michael@0: // Link tag is a special case - we return the anchor node michael@0: // found for any selection that is totally within a link, michael@0: // included a collapsed selection (just a caret in a link) michael@0: nsCOMPtr anchorNode; michael@0: res = selection->GetAnchorNode(getter_AddRefs(anchorNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: int32_t anchorOffset = -1; michael@0: if (anchorNode) michael@0: selection->GetAnchorOffset(&anchorOffset); michael@0: michael@0: nsCOMPtr focusNode; michael@0: res = selection->GetFocusNode(getter_AddRefs(focusNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: int32_t focusOffset = -1; michael@0: if (focusNode) michael@0: selection->GetFocusOffset(&focusOffset); michael@0: michael@0: // Link node must be the same for both ends of selection michael@0: if (NS_SUCCEEDED(res) && anchorNode) michael@0: { michael@0: nsCOMPtr parentLinkOfAnchor; michael@0: res = GetElementOrParentByTagName(NS_LITERAL_STRING("href"), anchorNode, getter_AddRefs(parentLinkOfAnchor)); michael@0: // XXX: ERROR_HANDLING can parentLinkOfAnchor be null? michael@0: if (NS_SUCCEEDED(res) && parentLinkOfAnchor) michael@0: { michael@0: if (isCollapsed) michael@0: { michael@0: // We have just a caret in the link michael@0: bNodeFound = true; michael@0: } else if(focusNode) michael@0: { // Link node must be the same for both ends of selection michael@0: nsCOMPtr parentLinkOfFocus; michael@0: res = GetElementOrParentByTagName(NS_LITERAL_STRING("href"), focusNode, getter_AddRefs(parentLinkOfFocus)); michael@0: if (NS_SUCCEEDED(res) && parentLinkOfFocus == parentLinkOfAnchor) michael@0: bNodeFound = true; michael@0: } michael@0: michael@0: // We found a link node parent michael@0: if (bNodeFound) { michael@0: // GetElementOrParentByTagName addref'd this, so we don't need to do it here michael@0: *aReturn = parentLinkOfAnchor; michael@0: NS_IF_ADDREF(*aReturn); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: else if (anchorOffset >= 0) // Check if link node is the only thing selected michael@0: { michael@0: nsCOMPtr anchorChild; michael@0: anchorChild = GetChildAt(anchorNode,anchorOffset); michael@0: if (anchorChild && nsHTMLEditUtils::IsLink(anchorChild) && michael@0: (anchorNode == focusNode) && focusOffset == (anchorOffset+1)) michael@0: { michael@0: selectedElement = do_QueryInterface(anchorChild); michael@0: bNodeFound = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!isCollapsed) // Don't bother to examine selection if it is collapsed michael@0: { michael@0: nsRefPtr currange = sel->GetRangeAt(0); michael@0: if (currange) { michael@0: nsCOMPtr iter = michael@0: do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: iter->Init(currange); michael@0: // loop through the content iterator for each content node michael@0: while (!iter->IsDone()) michael@0: { michael@0: // Query interface to cast nsIContent to nsIDOMNode michael@0: // then get tagType to compare to aTagName michael@0: // Clone node of each desired type and append it to the aDomFrag michael@0: selectedElement = do_QueryInterface(iter->GetCurrentNode()); michael@0: if (selectedElement) michael@0: { michael@0: // If we already found a node, then we have another element, michael@0: // thus there's not just one element selected michael@0: if (bNodeFound) michael@0: { michael@0: bNodeFound = false; michael@0: break; michael@0: } michael@0: michael@0: selectedElement->GetNodeName(domTagName); michael@0: ToLowerCase(domTagName); michael@0: michael@0: if (anyTag) michael@0: { michael@0: // Get name of first selected element michael@0: selectedElement->GetTagName(TagName); michael@0: ToLowerCase(TagName); michael@0: anyTag = false; michael@0: } michael@0: michael@0: // The "A" tag is a pain, michael@0: // used for both link(href is set) and "Named Anchor" michael@0: nsCOMPtr selectedNode = do_QueryInterface(selectedElement); michael@0: if ( (isLinkTag && nsHTMLEditUtils::IsLink(selectedNode)) || michael@0: (isNamedAnchorTag && nsHTMLEditUtils::IsNamedAnchor(selectedNode)) ) michael@0: { michael@0: bNodeFound = true; michael@0: } else if (TagName == domTagName) { // All other tag names are handled here michael@0: bNodeFound = true; michael@0: } michael@0: if (!bNodeFound) michael@0: { michael@0: // Check if node we have is really part of the selection??? michael@0: break; michael@0: } michael@0: } michael@0: iter->Next(); michael@0: } michael@0: } else { michael@0: // Should never get here? michael@0: isCollapsed = true; michael@0: NS_WARNING("isCollapsed was FALSE, but no elements found in selection\n"); michael@0: } michael@0: } michael@0: } michael@0: if (bNodeFound) michael@0: { michael@0: michael@0: *aReturn = selectedElement; michael@0: if (selectedElement) michael@0: { michael@0: // Getters must addref michael@0: NS_ADDREF(*aReturn); michael@0: } michael@0: } michael@0: else res = NS_EDITOR_ELEMENT_NOT_FOUND; michael@0: michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::CreateElementWithDefaults(const nsAString& aTagName, nsIDOMElement** aReturn) michael@0: { michael@0: nsresult res=NS_ERROR_NOT_INITIALIZED; michael@0: if (aReturn) michael@0: *aReturn = nullptr; michael@0: michael@0: // NS_ENSURE_TRUE(aTagName && aReturn, NS_ERROR_NULL_POINTER); michael@0: NS_ENSURE_TRUE(!aTagName.IsEmpty() && aReturn, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsAutoString TagName(aTagName); michael@0: ToLowerCase(TagName); michael@0: nsAutoString realTagName; michael@0: michael@0: if (IsLinkTag(TagName) || IsNamedAnchorTag(TagName)) michael@0: { michael@0: realTagName.AssignLiteral("a"); michael@0: } else { michael@0: realTagName = TagName; michael@0: } michael@0: //We don't use editor's CreateElement because we don't want to michael@0: // go through the transaction system michael@0: michael@0: nsCOMPtrnewElement; michael@0: nsCOMPtr newContent; michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: //new call to use instead to get proper HTML element, bug# 39919 michael@0: res = CreateHTMLContent(realTagName, getter_AddRefs(newContent)); michael@0: newElement = do_QueryInterface(newContent); michael@0: if (NS_FAILED(res) || !newElement) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Mark the new element dirty, so it will be formatted michael@0: newElement->SetAttribute(NS_LITERAL_STRING("_moz_dirty"), EmptyString()); michael@0: michael@0: // Set default values for new elements michael@0: if (TagName.EqualsLiteral("table")) { michael@0: res = newElement->SetAttribute(NS_LITERAL_STRING("cellpadding"),NS_LITERAL_STRING("2")); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = newElement->SetAttribute(NS_LITERAL_STRING("cellspacing"),NS_LITERAL_STRING("2")); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = newElement->SetAttribute(NS_LITERAL_STRING("border"),NS_LITERAL_STRING("1")); michael@0: } else if (TagName.EqualsLiteral("td")) michael@0: { michael@0: res = SetAttributeOrEquivalent(newElement, NS_LITERAL_STRING("valign"), michael@0: NS_LITERAL_STRING("top"), true); michael@0: } michael@0: // ADD OTHER TAGS HERE michael@0: michael@0: if (NS_SUCCEEDED(res)) michael@0: { michael@0: *aReturn = newElement; michael@0: // Getters must addref michael@0: NS_ADDREF(*aReturn); michael@0: } michael@0: michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::InsertLinkAroundSelection(nsIDOMElement* aAnchorElement) michael@0: { michael@0: NS_ENSURE_TRUE(aAnchorElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: // We must have a real selection michael@0: nsCOMPtr selection; michael@0: nsresult res = GetSelection(getter_AddRefs(selection)); michael@0: if (!selection) michael@0: { michael@0: res = NS_ERROR_NULL_POINTER; michael@0: } michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (selection->Collapsed()) { michael@0: NS_WARNING("InsertLinkAroundSelection called but there is no selection!!!"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Be sure we were given an anchor element michael@0: nsCOMPtr anchor = do_QueryInterface(aAnchorElement); michael@0: if (!anchor) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoString href; michael@0: res = anchor->GetHref(href); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (href.IsEmpty()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsAutoEditBatch beginBatching(this); michael@0: michael@0: // Set all attributes found on the supplied anchor element michael@0: nsCOMPtr attrMap; michael@0: aAnchorElement->GetAttributes(getter_AddRefs(attrMap)); michael@0: NS_ENSURE_TRUE(attrMap, NS_ERROR_FAILURE); michael@0: michael@0: uint32_t count; michael@0: attrMap->GetLength(&count); michael@0: nsAutoString name, value; michael@0: michael@0: for (uint32_t i = 0; i < count; ++i) { michael@0: nsCOMPtr attribute; michael@0: res = attrMap->Item(i, getter_AddRefs(attribute)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (attribute) { michael@0: // We must clear the string buffers michael@0: // because GetName, GetValue appends to previous string! michael@0: name.Truncate(); michael@0: value.Truncate(); michael@0: michael@0: res = attribute->GetName(name); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: res = attribute->GetValue(value); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: res = SetInlineProperty(nsEditProperty::a, name, value); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::SetHTMLBackgroundColor(const nsAString& aColor) michael@0: { michael@0: NS_PRECONDITION(mDocWeak, "Missing Editor DOM Document"); michael@0: michael@0: // Find a selected or enclosing table element to set background on michael@0: nsCOMPtr element; michael@0: int32_t selectedCount; michael@0: nsAutoString tagName; michael@0: nsresult res = GetSelectedOrParentTableElement(tagName, &selectedCount, michael@0: getter_AddRefs(element)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: bool setColor = !aColor.IsEmpty(); michael@0: michael@0: NS_NAMED_LITERAL_STRING(bgcolor, "bgcolor"); michael@0: if (element) michael@0: { michael@0: if (selectedCount > 0) michael@0: { michael@0: // Traverse all selected cells michael@0: nsCOMPtr cell; michael@0: res = GetFirstSelectedCell(nullptr, getter_AddRefs(cell)); michael@0: if (NS_SUCCEEDED(res) && cell) michael@0: { michael@0: while(cell) michael@0: { michael@0: if (setColor) michael@0: res = SetAttribute(cell, bgcolor, aColor); michael@0: else michael@0: res = RemoveAttribute(cell, bgcolor); michael@0: if (NS_FAILED(res)) break; michael@0: michael@0: GetNextSelectedCell(nullptr, getter_AddRefs(cell)); michael@0: }; michael@0: return res; michael@0: } michael@0: } michael@0: // If we failed to find a cell, fall through to use originally-found element michael@0: } else { michael@0: // No table element -- set the background color on the body tag michael@0: element = do_QueryInterface(GetRoot()); michael@0: NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER); michael@0: } michael@0: // Use the editor method that goes through the transaction system michael@0: if (setColor) michael@0: res = SetAttribute(element, bgcolor, aColor); michael@0: else michael@0: res = RemoveAttribute(element, bgcolor); michael@0: michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::SetBodyAttribute(const nsAString& aAttribute, const nsAString& aValue) michael@0: { michael@0: // TODO: Check selection for Cell, Row, Column or table and do color on appropriate level michael@0: michael@0: NS_ASSERTION(mDocWeak, "Missing Editor DOM Document"); michael@0: michael@0: // Set the background color attribute on the body tag michael@0: nsCOMPtr bodyElement = do_QueryInterface(GetRoot()); michael@0: NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Use the editor method that goes through the transaction system michael@0: return SetAttribute(bodyElement, aAttribute, aValue); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetLinkedObjects(nsISupportsArray** aNodeList) michael@0: { michael@0: NS_ENSURE_TRUE(aNodeList, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult res; michael@0: michael@0: res = NS_NewISupportsArray(aNodeList); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(*aNodeList, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr iter = michael@0: do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res); michael@0: NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); michael@0: if ((NS_SUCCEEDED(res))) michael@0: { michael@0: nsCOMPtr doc = GetDocument(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); michael@0: michael@0: iter->Init(doc->GetRootElement()); michael@0: michael@0: // loop through the content iterator for each content node michael@0: while (!iter->IsDone()) michael@0: { michael@0: nsCOMPtr node (do_QueryInterface(iter->GetCurrentNode())); michael@0: if (node) michael@0: { michael@0: // Let nsURIRefObject make the hard decisions: michael@0: nsCOMPtr refObject; michael@0: res = NS_NewHTMLURIRefObject(getter_AddRefs(refObject), node); michael@0: if (NS_SUCCEEDED(res)) michael@0: { michael@0: nsCOMPtr isupp (do_QueryInterface(refObject)); michael@0: michael@0: (*aNodeList)->AppendElement(isupp); michael@0: } michael@0: } michael@0: iter->Next(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::AddStyleSheet(const nsAString &aURL) michael@0: { michael@0: // Enable existing sheet if already loaded. michael@0: if (EnableExistingStyleSheet(aURL)) michael@0: return NS_OK; michael@0: michael@0: // Lose the previously-loaded sheet so there's nothing to replace michael@0: // This pattern is different from Override methods because michael@0: // we must wait to remove mLastStyleSheetURL and add new sheet michael@0: // at the same time (in StyleSheetLoaded callback) so they are undoable together michael@0: mLastStyleSheetURL.Truncate(); michael@0: return ReplaceStyleSheet(aURL); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::ReplaceStyleSheet(const nsAString& aURL) michael@0: { michael@0: // Enable existing sheet if already loaded. michael@0: if (EnableExistingStyleSheet(aURL)) michael@0: { michael@0: // Disable last sheet if not the same as new one michael@0: if (!mLastStyleSheetURL.IsEmpty() && !mLastStyleSheetURL.Equals(aURL)) michael@0: return EnableStyleSheet(mLastStyleSheetURL, false); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Make sure the pres shell doesn't disappear during the load. michael@0: NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED); michael@0: nsCOMPtr ps = GetPresShell(); michael@0: NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsCOMPtr uaURI; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uaURI), aURL); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return ps->GetDocument()->CSSLoader()-> michael@0: LoadSheet(uaURI, nullptr, EmptyCString(), this); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::RemoveStyleSheet(const nsAString &aURL) michael@0: { michael@0: nsRefPtr sheet; michael@0: nsresult rv = GetStyleSheetForURL(aURL, getter_AddRefs(sheet)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(sheet, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsRefPtr txn; michael@0: rv = CreateTxnForRemoveStyleSheet(sheet, getter_AddRefs(txn)); michael@0: if (!txn) rv = NS_ERROR_NULL_POINTER; michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: rv = DoTransaction(txn); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mLastStyleSheetURL.Truncate(); // forget it michael@0: michael@0: // Remove it from our internal list michael@0: rv = RemoveStyleSheetFromList(aURL); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::AddOverrideStyleSheet(const nsAString& aURL) michael@0: { michael@0: // Enable existing sheet if already loaded. michael@0: if (EnableExistingStyleSheet(aURL)) michael@0: return NS_OK; michael@0: michael@0: // Make sure the pres shell doesn't disappear during the load. michael@0: nsCOMPtr ps = GetPresShell(); michael@0: NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsCOMPtr uaURI; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uaURI), aURL); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We MUST ONLY load synchronous local files (no @import) michael@0: // XXXbz Except this will actually try to load remote files michael@0: // synchronously, of course.. michael@0: nsRefPtr sheet; michael@0: // Editor override style sheets may want to style Gecko anonymous boxes michael@0: rv = ps->GetDocument()->CSSLoader()-> michael@0: LoadSheetSync(uaURI, true, true, getter_AddRefs(sheet)); michael@0: michael@0: // Synchronous loads should ALWAYS return completed michael@0: NS_ENSURE_TRUE(sheet, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Add the override style sheet michael@0: // (This checks if already exists) michael@0: ps->AddOverrideStyleSheet(sheet); michael@0: michael@0: ps->ReconstructStyleData(); michael@0: michael@0: // Save as the last-loaded sheet michael@0: mLastOverrideStyleSheetURL = aURL; michael@0: michael@0: //Add URL and style sheet to our lists michael@0: return AddNewStyleSheetToList(aURL, sheet); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::ReplaceOverrideStyleSheet(const nsAString& aURL) michael@0: { michael@0: // Enable existing sheet if already loaded. michael@0: if (EnableExistingStyleSheet(aURL)) michael@0: { michael@0: // Disable last sheet if not the same as new one michael@0: if (!mLastOverrideStyleSheetURL.IsEmpty() && !mLastOverrideStyleSheetURL.Equals(aURL)) michael@0: return EnableStyleSheet(mLastOverrideStyleSheetURL, false); michael@0: michael@0: return NS_OK; michael@0: } michael@0: // Remove the previous sheet michael@0: if (!mLastOverrideStyleSheetURL.IsEmpty()) michael@0: RemoveOverrideStyleSheet(mLastOverrideStyleSheetURL); michael@0: michael@0: return AddOverrideStyleSheet(aURL); michael@0: } michael@0: michael@0: // Do NOT use transaction system for override style sheets michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::RemoveOverrideStyleSheet(const nsAString &aURL) michael@0: { michael@0: nsRefPtr sheet; michael@0: GetStyleSheetForURL(aURL, getter_AddRefs(sheet)); michael@0: michael@0: // Make sure we remove the stylesheet from our internal list in all michael@0: // cases. michael@0: nsresult rv = RemoveStyleSheetFromList(aURL); michael@0: michael@0: NS_ENSURE_TRUE(sheet, NS_OK); /// Don't fail if sheet not found michael@0: michael@0: NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED); michael@0: nsCOMPtr ps = GetPresShell(); michael@0: NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: ps->RemoveOverrideStyleSheet(sheet); michael@0: ps->ReconstructStyleData(); michael@0: michael@0: // Remove it from our internal list michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::EnableStyleSheet(const nsAString &aURL, bool aEnable) michael@0: { michael@0: nsRefPtr sheet; michael@0: nsresult rv = GetStyleSheetForURL(aURL, getter_AddRefs(sheet)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(sheet, NS_OK); // Don't fail if sheet not found michael@0: michael@0: // Ensure the style sheet is owned by our document. michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: sheet->SetOwningDocument(doc); michael@0: michael@0: return sheet->SetDisabled(!aEnable); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::EnableExistingStyleSheet(const nsAString &aURL) michael@0: { michael@0: nsRefPtr sheet; michael@0: nsresult rv = GetStyleSheetForURL(aURL, getter_AddRefs(sheet)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: // Enable sheet if already loaded. michael@0: if (sheet) michael@0: { michael@0: // Ensure the style sheet is owned by our document. michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: sheet->SetOwningDocument(doc); michael@0: michael@0: sheet->SetDisabled(false); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::AddNewStyleSheetToList(const nsAString &aURL, michael@0: nsCSSStyleSheet *aStyleSheet) michael@0: { michael@0: uint32_t countSS = mStyleSheets.Length(); michael@0: uint32_t countU = mStyleSheetURLs.Length(); michael@0: michael@0: if (countSS != countU) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: if (!mStyleSheetURLs.AppendElement(aURL)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: return mStyleSheets.AppendElement(aStyleSheet) ? NS_OK : NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::RemoveStyleSheetFromList(const nsAString &aURL) michael@0: { michael@0: // is it already in the list? michael@0: uint32_t foundIndex; michael@0: foundIndex = mStyleSheetURLs.IndexOf(aURL); michael@0: if (foundIndex == mStyleSheetURLs.NoIndex) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Attempt both removals; if one fails there's not much we can do. michael@0: mStyleSheets.RemoveElementAt(foundIndex); michael@0: mStyleSheetURLs.RemoveElementAt(foundIndex); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetStyleSheetForURL(const nsAString &aURL, michael@0: nsCSSStyleSheet **aStyleSheet) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aStyleSheet); michael@0: *aStyleSheet = 0; michael@0: michael@0: // is it already in the list? michael@0: uint32_t foundIndex; michael@0: foundIndex = mStyleSheetURLs.IndexOf(aURL); michael@0: if (foundIndex == mStyleSheetURLs.NoIndex) michael@0: return NS_OK; //No sheet -- don't fail! michael@0: michael@0: *aStyleSheet = mStyleSheets[foundIndex]; michael@0: NS_ENSURE_TRUE(*aStyleSheet, NS_ERROR_FAILURE); michael@0: michael@0: NS_ADDREF(*aStyleSheet); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetURLForStyleSheet(nsCSSStyleSheet *aStyleSheet, michael@0: nsAString &aURL) michael@0: { michael@0: // is it already in the list? michael@0: int32_t foundIndex = mStyleSheets.IndexOf(aStyleSheet); michael@0: michael@0: // Don't fail if we don't find it in our list michael@0: // Note: mStyleSheets is nsCOMArray, so its IndexOf() method michael@0: // returns -1 on failure. michael@0: if (foundIndex == -1) michael@0: return NS_OK; michael@0: michael@0: // Found it in the list! michael@0: aURL = mStyleSheetURLs[foundIndex]; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* michael@0: * nsIEditorMailSupport methods michael@0: */ michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList) michael@0: { michael@0: NS_ENSURE_TRUE(aNodeList, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult rv = NS_NewISupportsArray(aNodeList); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(*aNodeList, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr iter = michael@0: do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv); michael@0: NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr doc = GetDocument(); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED); michael@0: michael@0: iter->Init(doc->GetRootElement()); michael@0: michael@0: // Loop through the content iterator for each content node. michael@0: while (!iter->IsDone()) { michael@0: nsINode* node = iter->GetCurrentNode(); michael@0: if (node->IsElement()) { michael@0: dom::Element* element = node->AsElement(); michael@0: michael@0: // See if it's an image or an embed and also include all links. michael@0: // Let mail decide which link to send or not michael@0: if (element->IsHTML(nsGkAtoms::img) || michael@0: element->IsHTML(nsGkAtoms::embed) || michael@0: element->IsHTML(nsGkAtoms::a) || michael@0: (element->IsHTML(nsGkAtoms::body) && michael@0: element->HasAttr(kNameSpaceID_None, nsGkAtoms::background))) { michael@0: nsCOMPtr domNode = do_QueryInterface(node); michael@0: (*aNodeList)->AppendElement(domNode); michael@0: } michael@0: } michael@0: iter->Next(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::DeleteSelectionImpl(EDirection aAction, michael@0: EStripWrappers aStripWrappers) michael@0: { michael@0: MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); michael@0: michael@0: nsresult res = nsEditor::DeleteSelectionImpl(aAction, aStripWrappers); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // If we weren't asked to strip any wrappers, we're done. michael@0: if (aStripWrappers == eNoStrip) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRefPtr selection = GetSelection(); michael@0: // Just checking that the selection itself is collapsed doesn't seem to work michael@0: // right in the multi-range case michael@0: NS_ENSURE_STATE(selection); michael@0: NS_ENSURE_STATE(selection->GetAnchorFocusRange()); michael@0: NS_ENSURE_STATE(selection->GetAnchorFocusRange()->Collapsed()); michael@0: michael@0: NS_ENSURE_STATE(selection->GetAnchorNode()->IsContent()); michael@0: nsCOMPtr content = selection->GetAnchorNode()->AsContent(); michael@0: michael@0: // Don't strip wrappers if this is the only wrapper in the block. Then we'll michael@0: // add a
later, so it won't be an empty wrapper in the end. michael@0: nsCOMPtr blockParent = content; michael@0: while (blockParent && !IsBlockNode(blockParent)) { michael@0: blockParent = blockParent->GetParent(); michael@0: } michael@0: if (!blockParent) { michael@0: return NS_OK; michael@0: } michael@0: bool emptyBlockParent; michael@0: res = IsEmptyNode(blockParent, &emptyBlockParent); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (emptyBlockParent) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (content && !IsBlockNode(content) && !content->Length() && michael@0: content->IsEditable() && content != content->GetEditingHost()) { michael@0: while (content->GetParent() && !IsBlockNode(content->GetParent()) && michael@0: content->GetParent()->Length() == 1 && michael@0: content->GetParent()->IsEditable() && michael@0: content->GetParent() != content->GetEditingHost()) { michael@0: content = content->GetParent(); michael@0: } michael@0: res = DeleteNode(content); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::DeleteNode(nsINode* aNode) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: return DeleteNode(node); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::DeleteNode(nsIDOMNode* aNode) michael@0: { michael@0: // do nothing if the node is read-only michael@0: nsCOMPtr content = do_QueryInterface(aNode); michael@0: if (!IsModifiableNode(aNode) && !IsMozEditorBogusNode(content)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return nsEditor::DeleteNode(aNode); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::DeleteText(nsIDOMCharacterData *aTextNode, michael@0: uint32_t aOffset, michael@0: uint32_t aLength) michael@0: { michael@0: // do nothing if the node is read-only michael@0: if (!IsModifiableNode(aTextNode)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return nsEditor::DeleteText(aTextNode, aOffset, aLength); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHTMLEditor::InsertTextImpl(const nsAString& aStringToInsert, michael@0: nsCOMPtr *aInOutNode, michael@0: int32_t *aInOutOffset, michael@0: nsIDOMDocument *aDoc) michael@0: { michael@0: // do nothing if the node is read-only michael@0: if (!IsModifiableNode(*aInOutNode)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return nsEditor::InsertTextImpl(aStringToInsert, aInOutNode, aInOutOffset, aDoc); michael@0: } michael@0: michael@0: void michael@0: nsHTMLEditor::ContentAppended(nsIDocument *aDocument, nsIContent* aContainer, michael@0: nsIContent* aFirstNewContent, michael@0: int32_t aIndexInContainer) michael@0: { michael@0: DoContentInserted(aDocument, aContainer, aFirstNewContent, aIndexInContainer, michael@0: eAppended); michael@0: } michael@0: michael@0: void michael@0: nsHTMLEditor::ContentInserted(nsIDocument *aDocument, nsIContent* aContainer, michael@0: nsIContent* aChild, int32_t aIndexInContainer) michael@0: { michael@0: DoContentInserted(aDocument, aContainer, aChild, aIndexInContainer, michael@0: eInserted); michael@0: } michael@0: michael@0: void michael@0: nsHTMLEditor::DoContentInserted(nsIDocument* aDocument, nsIContent* aContainer, michael@0: nsIContent* aChild, int32_t aIndexInContainer, michael@0: InsertedOrAppended aInsertedOrAppended) michael@0: { michael@0: if (!aChild) { michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: if (ShouldReplaceRootElement()) { michael@0: nsContentUtils::AddScriptRunner(NS_NewRunnableMethod( michael@0: this, &nsHTMLEditor::ResetRootElementAndEventTarget)); michael@0: } michael@0: // We don't need to handle our own modifications michael@0: else if (!mAction && (aContainer ? aContainer->IsEditable() : aDocument->IsEditable())) { michael@0: if (IsMozEditorBogusNode(aChild)) { michael@0: // Ignore insertion of the bogus node michael@0: return; michael@0: } michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: mRules->DocumentModified(); michael@0: michael@0: // Update spellcheck for only the newly-inserted node (bug 743819) michael@0: if (mInlineSpellChecker) { michael@0: nsRefPtr range = new nsRange(aChild); michael@0: int32_t endIndex = aIndexInContainer + 1; michael@0: if (aInsertedOrAppended == eAppended) { michael@0: // Count all the appended nodes michael@0: nsIContent* sibling = aChild->GetNextSibling(); michael@0: while (sibling) { michael@0: endIndex++; michael@0: sibling = sibling->GetNextSibling(); michael@0: } michael@0: } michael@0: nsresult res = range->Set(aContainer, aIndexInContainer, michael@0: aContainer, endIndex); michael@0: if (NS_SUCCEEDED(res)) { michael@0: mInlineSpellChecker->SpellCheckRange(range); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHTMLEditor::ContentRemoved(nsIDocument *aDocument, nsIContent* aContainer, michael@0: nsIContent* aChild, int32_t aIndexInContainer, michael@0: nsIContent* aPreviousSibling) michael@0: { michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: if (SameCOMIdentity(aChild, mRootElement)) { michael@0: nsContentUtils::AddScriptRunner(NS_NewRunnableMethod( michael@0: this, &nsHTMLEditor::ResetRootElementAndEventTarget)); michael@0: } michael@0: // We don't need to handle our own modifications michael@0: else if (!mAction && (aContainer ? aContainer->IsEditable() : aDocument->IsEditable())) { michael@0: if (aChild && IsMozEditorBogusNode(aChild)) { michael@0: // Ignore removal of the bogus node michael@0: return; michael@0: } michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: mRules->DocumentModified(); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP_(bool) michael@0: nsHTMLEditor::IsModifiableNode(nsIDOMNode *aNode) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: return IsModifiableNode(node); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::IsModifiableNode(nsINode *aNode) michael@0: { michael@0: return !aNode || aNode->IsEditable(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetIsSelectionEditable(bool* aIsSelectionEditable) michael@0: { michael@0: MOZ_ASSERT(aIsSelectionEditable); michael@0: michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Per the editing spec as of June 2012: we have to have a selection whose michael@0: // start and end nodes are editable, and which share an ancestor editing michael@0: // host. (Bug 766387.) michael@0: *aIsSelectionEditable = selection->GetRangeCount() && michael@0: selection->GetAnchorNode()->IsEditable() && michael@0: selection->GetFocusNode()->IsEditable(); michael@0: michael@0: if (*aIsSelectionEditable) { michael@0: nsINode* commonAncestor = michael@0: selection->GetAnchorFocusRange()->GetCommonAncestor(); michael@0: while (commonAncestor && !commonAncestor->IsEditable()) { michael@0: commonAncestor = commonAncestor->GetParentNode(); michael@0: } michael@0: if (!commonAncestor) { michael@0: // No editable common ancestor michael@0: *aIsSelectionEditable = false; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: SetSelectionAroundHeadChildren(nsISelection* aSelection, michael@0: nsIWeakReference* aDocWeak) michael@0: { michael@0: // Set selection around node michael@0: nsCOMPtr doc = do_QueryReferent(aDocWeak); michael@0: NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: dom::Element* headNode = doc->GetHeadElement(); michael@0: NS_ENSURE_STATE(headNode); michael@0: michael@0: // Collapse selection to before first child of the head, michael@0: nsresult rv = aSelection->CollapseNative(headNode, 0); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Then extend it to just after. michael@0: uint32_t childCount = headNode->GetChildCount(); michael@0: return aSelection->ExtendNative(headNode, childCount + 1); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetHeadContentsAsHTML(nsAString& aOutputString) michael@0: { michael@0: nsRefPtr selection = GetSelection(); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); michael@0: michael@0: // Save current selection michael@0: nsAutoSelectionReset selectionResetter(selection, this); michael@0: michael@0: nsresult res = SetSelectionAroundHeadChildren(selection, mDocWeak); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: res = OutputToString(NS_LITERAL_STRING("text/html"), michael@0: nsIDocumentEncoder::OutputSelectionOnly, michael@0: aOutputString); michael@0: if (NS_SUCCEEDED(res)) michael@0: { michael@0: // Selection always includes , michael@0: // so terminate there michael@0: nsReadingIterator findIter,endFindIter; michael@0: aOutputString.BeginReading(findIter); michael@0: aOutputString.EndReading(endFindIter); michael@0: //counting on our parser to always lower case!!! michael@0: if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING(" beginIter; michael@0: aOutputString.BeginReading(beginIter); michael@0: int32_t offset = Distance(beginIter, findIter);//get the distance michael@0: michael@0: nsWritingIterator writeIter; michael@0: aOutputString.BeginWriting(writeIter); michael@0: // Ensure the string ends in a newline michael@0: char16_t newline ('\n'); michael@0: findIter.advance(-1); michael@0: if (offset ==0 || (offset >0 && (*findIter) != newline)) //check for 0 michael@0: { michael@0: writeIter.advance(offset); michael@0: *writeIter = newline; michael@0: aOutputString.Truncate(offset+1); michael@0: } michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::DebugUnitTests(int32_t *outNumTests, int32_t *outNumTestsFailed) michael@0: { michael@0: #ifdef DEBUG michael@0: NS_ENSURE_TRUE(outNumTests && outNumTestsFailed, NS_ERROR_NULL_POINTER); michael@0: michael@0: TextEditorTest *tester = new TextEditorTest(); michael@0: NS_ENSURE_TRUE(tester, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: tester->Run(this, outNumTests, outNumTestsFailed); michael@0: delete tester; michael@0: return NS_OK; michael@0: #else michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: #endif michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::StyleSheetLoaded(nsCSSStyleSheet* aSheet, bool aWasAlternate, michael@0: nsresult aStatus) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: nsAutoEditBatch batchIt(this); michael@0: michael@0: if (!mLastStyleSheetURL.IsEmpty()) michael@0: RemoveStyleSheet(mLastStyleSheetURL); michael@0: michael@0: nsRefPtr txn; michael@0: rv = CreateTxnForAddStyleSheet(aSheet, getter_AddRefs(txn)); michael@0: if (!txn) rv = NS_ERROR_NULL_POINTER; michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: rv = DoTransaction(txn); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: // Get the URI, then url spec from the sheet michael@0: nsAutoCString spec; michael@0: rv = aSheet->GetSheetURI()->GetSpec(spec); michael@0: michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: // Save it so we can remove before applying the next one michael@0: mLastStyleSheetURL.AssignWithConversion(spec.get()); michael@0: michael@0: // Also save in our arrays of urls and sheets michael@0: AddNewStyleSheetToList(mLastStyleSheetURL, aSheet); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /** All editor operations which alter the doc should be prefaced michael@0: * with a call to StartOperation, naming the action and direction */ michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::StartOperation(EditAction opID, michael@0: nsIEditor::EDirection aDirection) michael@0: { michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: nsEditor::StartOperation(opID, aDirection); // will set mAction, mDirection michael@0: if (mRules) return mRules->BeforeEdit(mAction, mDirection); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /** All editor operations which alter the doc should be followed michael@0: * with a call to EndOperation */ michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::EndOperation() michael@0: { michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: // post processing michael@0: nsresult res = NS_OK; michael@0: if (mRules) res = mRules->AfterEdit(mAction, mDirection); michael@0: nsEditor::EndOperation(); // will clear mAction, mDirection michael@0: return res; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::TagCanContainTag(nsIAtom* aParentTag, nsIAtom* aChildTag) michael@0: { michael@0: MOZ_ASSERT(aParentTag && aChildTag); michael@0: michael@0: nsIParserService* parserService = nsContentUtils::GetParserService(); michael@0: michael@0: int32_t childTagEnum; michael@0: // XXX Should this handle #cdata-section too? michael@0: if (aChildTag == nsGkAtoms::textTagName) { michael@0: childTagEnum = eHTMLTag_text; michael@0: } else { michael@0: childTagEnum = parserService->HTMLAtomTagToId(aChildTag); michael@0: } michael@0: michael@0: int32_t parentTagEnum = parserService->HTMLAtomTagToId(aParentTag); michael@0: return nsHTMLEditUtils::CanContain(parentTagEnum, childTagEnum); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::IsContainer(nsIDOMNode *aNode) michael@0: { michael@0: if (!aNode) { michael@0: return false; michael@0: } michael@0: michael@0: nsAutoString stringTag; michael@0: michael@0: nsresult rv = aNode->GetNodeName(stringTag); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: int32_t tagEnum; michael@0: // XXX Should this handle #cdata-section too? michael@0: if (stringTag.EqualsLiteral("#text")) { michael@0: tagEnum = eHTMLTag_text; michael@0: } michael@0: else { michael@0: tagEnum = nsContentUtils::GetParserService()->HTMLStringTagToId(stringTag); michael@0: } michael@0: michael@0: return nsHTMLEditUtils::IsContainer(tagEnum); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::SelectEntireDocument(nsISelection *aSelection) michael@0: { michael@0: if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; } michael@0: michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: // get editor root node michael@0: nsCOMPtr rootElement = do_QueryInterface(GetRoot()); michael@0: michael@0: // is doc empty? michael@0: bool bDocIsEmpty; michael@0: nsresult res = mRules->DocumentIsEmpty(&bDocIsEmpty); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (bDocIsEmpty) michael@0: { michael@0: // if its empty dont select entire doc - that would select the bogus node michael@0: return aSelection->Collapse(rootElement, 0); michael@0: } michael@0: michael@0: return nsEditor::SelectEntireDocument(aSelection); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::SelectAll() michael@0: { michael@0: ForceCompositionEnd(); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr selCon; michael@0: rv = GetSelectionController(getter_AddRefs(selCon)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr selection; michael@0: rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, michael@0: getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr anchorNode; michael@0: rv = selection->GetAnchorNode(getter_AddRefs(anchorNode)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr anchorContent = do_QueryInterface(anchorNode, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If the anchor content has independent selection, we never need to explicitly michael@0: // select its children. michael@0: if (anchorContent->HasIndependentSelection()) { michael@0: nsCOMPtr selPriv = do_QueryInterface(selection); michael@0: NS_ENSURE_TRUE(selPriv, NS_ERROR_UNEXPECTED); michael@0: rv = selPriv->SetAncestorLimiter(nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr rootElement = do_QueryInterface(mRootElement, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return selection->SelectAllChildren(rootElement); michael@0: } michael@0: michael@0: nsCOMPtr ps = GetPresShell(); michael@0: nsIContent *rootContent = anchorContent->GetSelectionRootContent(ps); michael@0: NS_ENSURE_TRUE(rootContent, NS_ERROR_UNEXPECTED); michael@0: michael@0: nsCOMPtr rootElement = do_QueryInterface(rootContent, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return selection->SelectAllChildren(rootElement); michael@0: } michael@0: michael@0: michael@0: // this will NOT find aAttribute unless aAttribute has a non-null value michael@0: // so singleton attributes like will not be matched! michael@0: bool nsHTMLEditor::IsTextPropertySetByContent(nsIContent* aContent, michael@0: nsIAtom* aProperty, michael@0: const nsAString* aAttribute, michael@0: const nsAString* aValue, michael@0: nsAString* outValue) michael@0: { michael@0: MOZ_ASSERT(aContent && aProperty); michael@0: MOZ_ASSERT_IF(aAttribute, aValue); michael@0: bool isSet; michael@0: IsTextPropertySetByContent(aContent->AsDOMNode(), aProperty, aAttribute, michael@0: aValue, isSet, outValue); michael@0: return isSet; michael@0: } michael@0: michael@0: void nsHTMLEditor::IsTextPropertySetByContent(nsIDOMNode *aNode, michael@0: nsIAtom *aProperty, michael@0: const nsAString *aAttribute, michael@0: const nsAString *aValue, michael@0: bool &aIsSet, michael@0: nsAString *outValue) michael@0: { michael@0: nsresult result; michael@0: aIsSet = false; // must be initialized to false for code below to work michael@0: nsAutoString propName; michael@0: aProperty->ToString(propName); michael@0: nsCOMPtrnode = aNode; michael@0: michael@0: while (node) michael@0: { michael@0: nsCOMPtrelement; michael@0: element = do_QueryInterface(node); michael@0: if (element) michael@0: { michael@0: nsAutoString tag, value; michael@0: element->GetTagName(tag); michael@0: if (propName.Equals(tag, nsCaseInsensitiveStringComparator())) michael@0: { michael@0: bool found = false; michael@0: if (aAttribute && 0!=aAttribute->Length()) michael@0: { michael@0: element->GetAttribute(*aAttribute, value); michael@0: if (outValue) *outValue = value; michael@0: if (!value.IsEmpty()) michael@0: { michael@0: if (!aValue) { michael@0: found = true; michael@0: } michael@0: else michael@0: { michael@0: nsString tString(*aValue); michael@0: if (tString.Equals(value, nsCaseInsensitiveStringComparator())) { michael@0: found = true; michael@0: } michael@0: else { // we found the prop with the attribute, but the value doesn't match michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: found = true; michael@0: } michael@0: if (found) michael@0: { michael@0: aIsSet = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: nsCOMPtrtemp; michael@0: result = node->GetParentNode(getter_AddRefs(temp)); michael@0: if (NS_SUCCEEDED(result) && temp) { michael@0: node = temp; michael@0: } michael@0: else { michael@0: node = nullptr; michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: //================================================================ michael@0: // HTML Editor methods michael@0: // michael@0: // Note: Table Editing methods are implemented in nsTableEditor.cpp michael@0: // michael@0: michael@0: michael@0: bool michael@0: nsHTMLEditor::SetCaretInTableCell(nsIDOMElement* aElement) michael@0: { michael@0: nsCOMPtr element = do_QueryInterface(aElement); michael@0: if (!element || !element->IsHTML() || michael@0: !nsHTMLEditUtils::IsTableElement(element) || michael@0: !IsDescendantOfEditorRoot(element)) { michael@0: return false; michael@0: } michael@0: michael@0: nsIContent* node = element; michael@0: while (node->HasChildren()) { michael@0: node = node->GetFirstChild(); michael@0: } michael@0: michael@0: // Set selection at beginning of the found node michael@0: nsCOMPtr selection; michael@0: nsresult rv = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: NS_ENSURE_TRUE(selection, false); michael@0: michael@0: return NS_SUCCEEDED(selection->CollapseNative(node, 0)); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetEnclosingTable: find ancestor who is a table, if any michael@0: // michael@0: nsCOMPtr michael@0: nsHTMLEditor::GetEnclosingTable(nsIDOMNode *aNode) michael@0: { michael@0: NS_PRECONDITION(aNode, "null node passed to nsHTMLEditor::GetEnclosingTable"); michael@0: nsCOMPtr tbl, tmp, node = aNode; michael@0: michael@0: while (!tbl) michael@0: { michael@0: tmp = GetBlockNodeParent(node); michael@0: if (!tmp) break; michael@0: if (nsHTMLEditUtils::IsTable(tmp)) tbl = tmp; michael@0: node = tmp; michael@0: } michael@0: return tbl; michael@0: } michael@0: michael@0: michael@0: /* this method scans the selection for adjacent text nodes michael@0: * and collapses them into a single text node. michael@0: * "adjacent" means literally adjacent siblings of the same parent. michael@0: * Uses nsEditor::JoinNodes so action is undoable. michael@0: * Should be called within the context of a batch transaction. michael@0: */ michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::CollapseAdjacentTextNodes(nsIDOMRange *aInRange) michael@0: { michael@0: NS_ENSURE_TRUE(aInRange, NS_ERROR_NULL_POINTER); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(this); michael@0: nsTArray > textNodes; michael@0: // we can't actually do anything during iteration, so store the text nodes in an array michael@0: // don't bother ref counting them because we know we can hold them for the michael@0: // lifetime of this method michael@0: michael@0: michael@0: // build a list of editable text nodes michael@0: nsresult result; michael@0: nsCOMPtr iter = michael@0: do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &result); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: michael@0: iter->Init(aInRange); michael@0: michael@0: while (!iter->IsDone()) michael@0: { michael@0: nsINode* node = iter->GetCurrentNode(); michael@0: if (node->NodeType() == nsIDOMNode::TEXT_NODE && michael@0: IsEditable(static_cast(node))) { michael@0: nsCOMPtr domNode = do_QueryInterface(node); michael@0: textNodes.AppendElement(domNode); michael@0: } michael@0: michael@0: iter->Next(); michael@0: } michael@0: michael@0: // now that I have a list of text nodes, collapse adjacent text nodes michael@0: // NOTE: assumption that JoinNodes keeps the righthand node michael@0: while (textNodes.Length() > 1) michael@0: { michael@0: // we assume a textNodes entry can't be nullptr michael@0: nsIDOMNode *leftTextNode = textNodes[0]; michael@0: nsIDOMNode *rightTextNode = textNodes[1]; michael@0: NS_ASSERTION(leftTextNode && rightTextNode,"left or rightTextNode null in CollapseAdjacentTextNodes"); michael@0: michael@0: // get the prev sibling of the right node, and see if its leftTextNode michael@0: nsCOMPtr prevSibOfRightNode; michael@0: result = michael@0: rightTextNode->GetPreviousSibling(getter_AddRefs(prevSibOfRightNode)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: if (prevSibOfRightNode && (prevSibOfRightNode == leftTextNode)) michael@0: { michael@0: nsCOMPtr parent; michael@0: result = rightTextNode->GetParentNode(getter_AddRefs(parent)); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); michael@0: result = JoinNodes(leftTextNode, rightTextNode, parent); michael@0: NS_ENSURE_SUCCESS(result, result); michael@0: } michael@0: michael@0: textNodes.RemoveElementAt(0); // remove the leftmost text node from the list michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::SetSelectionAtDocumentStart(nsISelection *aSelection) michael@0: { michael@0: dom::Element* rootElement = GetRoot(); michael@0: NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: return aSelection->CollapseNative(rootElement, 0); michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // RemoveBlockContainer: remove inNode, reparenting its children into their michael@0: // the parent of inNode. In addition, INSERT ANY BR's NEEDED michael@0: // TO PRESERVE IDENTITY OF REMOVED BLOCK. michael@0: // michael@0: nsresult michael@0: nsHTMLEditor::RemoveBlockContainer(nsIDOMNode *inNode) michael@0: { michael@0: NS_ENSURE_TRUE(inNode, NS_ERROR_NULL_POINTER); michael@0: nsresult res; michael@0: nsCOMPtr sibling, child, unused; michael@0: michael@0: // Two possibilities: the container cold be empty of editable content. michael@0: // If that is the case, we need to compare what is before and after inNode michael@0: // to determine if we need a br. michael@0: // Or it could not be empty, in which case we have to compare previous michael@0: // sibling and first child to determine if we need a leading br, michael@0: // and compare following sibling and last child to determine if we need a michael@0: // trailing br. michael@0: michael@0: res = GetFirstEditableChild(inNode, address_of(child)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (child) // the case of inNode not being empty michael@0: { michael@0: // we need a br at start unless: michael@0: // 1) previous sibling of inNode is a block, OR michael@0: // 2) previous sibling of inNode is a br, OR michael@0: // 3) first child of inNode is a block OR michael@0: // 4) either is null michael@0: michael@0: res = GetPriorHTMLSibling(inNode, address_of(sibling)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling)) michael@0: { michael@0: res = GetFirstEditableChild(inNode, address_of(child)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (child && !IsBlockNode(child)) michael@0: { michael@0: // insert br node michael@0: res = CreateBR(inNode, 0, address_of(unused)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: // we need a br at end unless: michael@0: // 1) following sibling of inNode is a block, OR michael@0: // 2) last child of inNode is a block, OR michael@0: // 3) last child of inNode is a block OR michael@0: // 4) either is null michael@0: michael@0: res = GetNextHTMLSibling(inNode, address_of(sibling)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (sibling && !IsBlockNode(sibling)) michael@0: { michael@0: res = GetLastEditableChild(inNode, address_of(child)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (child && !IsBlockNode(child) && !nsTextEditUtils::IsBreak(child)) michael@0: { michael@0: // insert br node michael@0: uint32_t len; michael@0: res = GetLengthOfDOMNode(inNode, len); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = CreateBR(inNode, (int32_t)len, address_of(unused)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: else // the case of inNode being empty michael@0: { michael@0: // we need a br at start unless: michael@0: // 1) previous sibling of inNode is a block, OR michael@0: // 2) previous sibling of inNode is a br, OR michael@0: // 3) following sibling of inNode is a block, OR michael@0: // 4) following sibling of inNode is a br OR michael@0: // 5) either is null michael@0: res = GetPriorHTMLSibling(inNode, address_of(sibling)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling)) michael@0: { michael@0: res = GetNextHTMLSibling(inNode, address_of(sibling)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling)) michael@0: { michael@0: // insert br node michael@0: res = CreateBR(inNode, 0, address_of(unused)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // now remove container michael@0: return RemoveContainer(inNode); michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetPriorHTMLSibling: returns the previous editable sibling, if there is michael@0: // one within the parent michael@0: // michael@0: nsIContent* michael@0: nsHTMLEditor::GetPriorHTMLSibling(nsINode* aNode) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: michael@0: nsIContent* node = aNode->GetPreviousSibling(); michael@0: while (node && !IsEditable(node)) { michael@0: node = node->GetPreviousSibling(); michael@0: } michael@0: michael@0: return node; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetPriorHTMLSibling(nsIDOMNode *inNode, nsCOMPtr *outNode) michael@0: { michael@0: NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER); michael@0: *outNode = nullptr; michael@0: michael@0: nsCOMPtr node = do_QueryInterface(inNode); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: michael@0: *outNode = do_QueryInterface(GetPriorHTMLSibling(node)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetPriorHTMLSibling: returns the previous editable sibling, if there is michael@0: // one within the parent. just like above routine but michael@0: // takes a parent/offset instead of a node. michael@0: // michael@0: nsIContent* michael@0: nsHTMLEditor::GetPriorHTMLSibling(nsINode* aParent, int32_t aOffset) michael@0: { michael@0: MOZ_ASSERT(aParent); michael@0: michael@0: nsIContent* node = aParent->GetChildAt(aOffset - 1); michael@0: if (!node || IsEditable(node)) { michael@0: return node; michael@0: } michael@0: michael@0: return GetPriorHTMLSibling(node); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetPriorHTMLSibling(nsIDOMNode *inParent, int32_t inOffset, nsCOMPtr *outNode) michael@0: { michael@0: NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER); michael@0: *outNode = nullptr; michael@0: michael@0: nsCOMPtr parent = do_QueryInterface(inParent); michael@0: NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); michael@0: michael@0: *outNode = do_QueryInterface(GetPriorHTMLSibling(parent, inOffset)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetNextHTMLSibling: returns the next editable sibling, if there is michael@0: // one within the parent michael@0: // michael@0: nsIContent* michael@0: nsHTMLEditor::GetNextHTMLSibling(nsINode* aNode) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: michael@0: nsIContent* node = aNode->GetNextSibling(); michael@0: while (node && !IsEditable(node)) { michael@0: node = node->GetNextSibling(); michael@0: } michael@0: michael@0: return node; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetNextHTMLSibling(nsIDOMNode *inNode, nsCOMPtr *outNode) michael@0: { michael@0: NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER); michael@0: *outNode = nullptr; michael@0: michael@0: nsCOMPtr node = do_QueryInterface(inNode); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: michael@0: *outNode = do_QueryInterface(GetNextHTMLSibling(node)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetNextHTMLSibling: returns the next editable sibling, if there is michael@0: // one within the parent. just like above routine but michael@0: // takes a parent/offset instead of a node. michael@0: nsIContent* michael@0: nsHTMLEditor::GetNextHTMLSibling(nsINode* aParent, int32_t aOffset) michael@0: { michael@0: MOZ_ASSERT(aParent); michael@0: michael@0: nsIContent* node = aParent->GetChildAt(aOffset + 1); michael@0: if (!node || IsEditable(node)) { michael@0: return node; michael@0: } michael@0: michael@0: return GetNextHTMLSibling(node); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetNextHTMLSibling(nsIDOMNode *inParent, int32_t inOffset, nsCOMPtr *outNode) michael@0: { michael@0: NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER); michael@0: *outNode = nullptr; michael@0: michael@0: nsCOMPtr parent = do_QueryInterface(inParent); michael@0: NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER); michael@0: michael@0: *outNode = do_QueryInterface(GetNextHTMLSibling(parent, inOffset)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetPriorHTMLNode: returns the previous editable leaf node, if there is michael@0: // one within the michael@0: // michael@0: nsIContent* michael@0: nsHTMLEditor::GetPriorHTMLNode(nsINode* aNode, bool aNoBlockCrossing) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: michael@0: if (!GetActiveEditingHost()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return GetPriorNode(aNode, true, aNoBlockCrossing); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode* aNode, michael@0: nsCOMPtr* aResultNode, michael@0: bool aNoBlockCrossing) michael@0: { michael@0: NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aResultNode = do_QueryInterface(GetPriorHTMLNode(node, aNoBlockCrossing)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetPriorHTMLNode: same as above but takes {parent,offset} instead of node michael@0: // michael@0: nsIContent* michael@0: nsHTMLEditor::GetPriorHTMLNode(nsINode* aParent, int32_t aOffset, michael@0: bool aNoBlockCrossing) michael@0: { michael@0: MOZ_ASSERT(aParent); michael@0: michael@0: if (!GetActiveEditingHost()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return GetPriorNode(aParent, aOffset, true, aNoBlockCrossing); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode* aNode, int32_t aOffset, michael@0: nsCOMPtr* aResultNode, michael@0: bool aNoBlockCrossing) michael@0: { michael@0: NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aResultNode = do_QueryInterface(GetPriorHTMLNode(node, aOffset, michael@0: aNoBlockCrossing)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetNextHTMLNode: returns the next editable leaf node, if there is michael@0: // one within the michael@0: // michael@0: nsIContent* michael@0: nsHTMLEditor::GetNextHTMLNode(nsINode* aNode, bool aNoBlockCrossing) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: michael@0: nsIContent* result = GetNextNode(aNode, true, aNoBlockCrossing); michael@0: michael@0: if (result && !IsDescendantOfEditorRoot(result)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetNextHTMLNode(nsIDOMNode* aNode, michael@0: nsCOMPtr* aResultNode, michael@0: bool aNoBlockCrossing) michael@0: { michael@0: NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aResultNode = do_QueryInterface(GetNextHTMLNode(node, aNoBlockCrossing)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // GetNextHTMLNode: same as above but takes {parent,offset} instead of node michael@0: // michael@0: nsIContent* michael@0: nsHTMLEditor::GetNextHTMLNode(nsINode* aParent, int32_t aOffset, michael@0: bool aNoBlockCrossing) michael@0: { michael@0: nsIContent* content = GetNextNode(aParent, aOffset, true, aNoBlockCrossing); michael@0: if (content && !IsDescendantOfEditorRoot(content)) { michael@0: return nullptr; michael@0: } michael@0: return content; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetNextHTMLNode(nsIDOMNode* aNode, int32_t aOffset, michael@0: nsCOMPtr* aResultNode, michael@0: bool aNoBlockCrossing) michael@0: { michael@0: NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aResultNode = do_QueryInterface(GetNextHTMLNode(node, aOffset, michael@0: aNoBlockCrossing)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::IsFirstEditableChild( nsIDOMNode *aNode, bool *aOutIsFirst) michael@0: { michael@0: // check parms michael@0: NS_ENSURE_TRUE(aOutIsFirst && aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: // init out parms michael@0: *aOutIsFirst = false; michael@0: michael@0: // find first editable child and compare it to aNode michael@0: nsCOMPtr parent, firstChild; michael@0: nsresult res = aNode->GetParentNode(getter_AddRefs(parent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE); michael@0: res = GetFirstEditableChild(parent, address_of(firstChild)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: *aOutIsFirst = (firstChild.get() == aNode); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::IsLastEditableChild( nsIDOMNode *aNode, bool *aOutIsLast) michael@0: { michael@0: // check parms michael@0: NS_ENSURE_TRUE(aOutIsLast && aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: // init out parms michael@0: *aOutIsLast = false; michael@0: michael@0: // find last editable child and compare it to aNode michael@0: nsCOMPtr parent, lastChild; michael@0: nsresult res = aNode->GetParentNode(getter_AddRefs(parent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE); michael@0: res = GetLastEditableChild(parent, address_of(lastChild)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: *aOutIsLast = (lastChild.get() == aNode); michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetFirstEditableChild( nsIDOMNode *aNode, nsCOMPtr *aOutFirstChild) michael@0: { michael@0: // check parms michael@0: NS_ENSURE_TRUE(aOutFirstChild && aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: // init out parms michael@0: *aOutFirstChild = nullptr; michael@0: michael@0: // find first editable child michael@0: nsCOMPtr child; michael@0: nsresult res = aNode->GetFirstChild(getter_AddRefs(child)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: while (child && !IsEditable(child)) michael@0: { michael@0: nsCOMPtr tmp; michael@0: res = child->GetNextSibling(getter_AddRefs(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); michael@0: child = tmp; michael@0: } michael@0: michael@0: *aOutFirstChild = child; michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetLastEditableChild( nsIDOMNode *aNode, nsCOMPtr *aOutLastChild) michael@0: { michael@0: // check parms michael@0: NS_ENSURE_TRUE(aOutLastChild && aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: // init out parms michael@0: *aOutLastChild = aNode; michael@0: michael@0: // find last editable child michael@0: nsCOMPtr child; michael@0: nsresult res = aNode->GetLastChild(getter_AddRefs(child)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: while (child && !IsEditable(child)) michael@0: { michael@0: nsCOMPtr tmp; michael@0: res = child->GetPreviousSibling(getter_AddRefs(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); michael@0: child = tmp; michael@0: } michael@0: michael@0: *aOutLastChild = child; michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetFirstEditableLeaf( nsIDOMNode *aNode, nsCOMPtr *aOutFirstLeaf) michael@0: { michael@0: // check parms michael@0: NS_ENSURE_TRUE(aOutFirstLeaf && aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: // init out parms michael@0: *aOutFirstLeaf = aNode; michael@0: michael@0: // find leftmost leaf michael@0: nsCOMPtr child; michael@0: nsresult res = NS_OK; michael@0: child = GetLeftmostChild(aNode); michael@0: while (child && (!IsEditable(child) || !nsEditorUtils::IsLeafNode(child))) michael@0: { michael@0: nsCOMPtr tmp; michael@0: res = GetNextHTMLNode(child, address_of(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); michael@0: michael@0: // only accept nodes that are descendants of aNode michael@0: if (nsEditorUtils::IsDescendantOf(tmp, aNode)) michael@0: child = tmp; michael@0: else michael@0: { michael@0: child = nullptr; // this will abort the loop michael@0: } michael@0: } michael@0: michael@0: *aOutFirstLeaf = child; michael@0: return res; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetLastEditableLeaf(nsIDOMNode *aNode, nsCOMPtr *aOutLastLeaf) michael@0: { michael@0: // check parms michael@0: NS_ENSURE_TRUE(aOutLastLeaf && aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: // init out parms michael@0: *aOutLastLeaf = nullptr; michael@0: michael@0: // find rightmost leaf michael@0: nsCOMPtr child = GetRightmostChild(aNode, false); michael@0: nsresult res = NS_OK; michael@0: while (child && (!IsEditable(child) || !nsEditorUtils::IsLeafNode(child))) michael@0: { michael@0: nsCOMPtr tmp; michael@0: res = GetPriorHTMLNode(child, address_of(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); michael@0: michael@0: // only accept nodes that are descendants of aNode michael@0: if (nsEditorUtils::IsDescendantOf(tmp, aNode)) michael@0: child = tmp; michael@0: else michael@0: { michael@0: child = nullptr; michael@0: } michael@0: } michael@0: michael@0: *aOutLastLeaf = child; michael@0: return res; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // IsVisTextNode: figure out if textnode aTextNode has any visible content. michael@0: // michael@0: nsresult michael@0: nsHTMLEditor::IsVisTextNode(nsIContent* aNode, michael@0: bool* outIsEmptyNode, michael@0: bool aSafeToAskFrames) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: MOZ_ASSERT(aNode->NodeType() == nsIDOMNode::TEXT_NODE); michael@0: MOZ_ASSERT(outIsEmptyNode); michael@0: michael@0: *outIsEmptyNode = true; michael@0: michael@0: uint32_t length = aNode->TextLength(); michael@0: if (aSafeToAskFrames) michael@0: { michael@0: nsCOMPtr selCon; michael@0: nsresult res = GetSelectionController(getter_AddRefs(selCon)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE); michael@0: bool isVisible = false; michael@0: // ask the selection controller for information about whether any michael@0: // of the data in the node is really rendered. This is really michael@0: // something that frames know about, but we aren't supposed to talk to frames. michael@0: // So we put a call in the selection controller interface, since it's already michael@0: // in bed with frames anyway. (this is a fix for bug 22227, and a michael@0: // partial fix for bug 46209) michael@0: res = selCon->CheckVisibilityContent(aNode, 0, length, &isVisible); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (isVisible) michael@0: { michael@0: *outIsEmptyNode = false; michael@0: } michael@0: } michael@0: else if (length) michael@0: { michael@0: if (aNode->TextIsOnlyWhitespace()) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: nsWSRunObject wsRunObj(this, node, 0); michael@0: nsCOMPtr visNode; michael@0: int32_t outVisOffset=0; michael@0: WSType visType; michael@0: wsRunObj.NextVisibleNode(node, 0, address_of(visNode), michael@0: &outVisOffset, &visType); michael@0: if (visType == WSType::normalWS || visType == WSType::text) { michael@0: *outIsEmptyNode = (node != visNode); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: *outIsEmptyNode = false; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // IsEmptyNode: figure out if aNode is an empty node. michael@0: // A block can have children and still be considered empty, michael@0: // if the children are empty or non-editable. michael@0: // michael@0: nsresult michael@0: nsHTMLEditor::IsEmptyNode( nsIDOMNode *aNode, michael@0: bool *outIsEmptyNode, michael@0: bool aSingleBRDoesntCount, michael@0: bool aListOrCellNotEmpty, michael@0: bool aSafeToAskFrames) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: return IsEmptyNode(node, outIsEmptyNode, aSingleBRDoesntCount, michael@0: aListOrCellNotEmpty, aSafeToAskFrames); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::IsEmptyNode(nsINode* aNode, michael@0: bool* outIsEmptyNode, michael@0: bool aSingleBRDoesntCount, michael@0: bool aListOrCellNotEmpty, michael@0: bool aSafeToAskFrames) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && outIsEmptyNode, NS_ERROR_NULL_POINTER); michael@0: *outIsEmptyNode = true; michael@0: bool seenBR = false; michael@0: return IsEmptyNodeImpl(aNode, outIsEmptyNode, aSingleBRDoesntCount, michael@0: aListOrCellNotEmpty, aSafeToAskFrames, &seenBR); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // IsEmptyNodeImpl: workhorse for IsEmptyNode. michael@0: // michael@0: nsresult michael@0: nsHTMLEditor::IsEmptyNodeImpl(nsINode* aNode, michael@0: bool *outIsEmptyNode, michael@0: bool aSingleBRDoesntCount, michael@0: bool aListOrCellNotEmpty, michael@0: bool aSafeToAskFrames, michael@0: bool *aSeenBR) michael@0: { michael@0: NS_ENSURE_TRUE(aNode && outIsEmptyNode && aSeenBR, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (aNode->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: return IsVisTextNode(static_cast(aNode), outIsEmptyNode, aSafeToAskFrames); michael@0: } michael@0: michael@0: // if it's not a text node (handled above) and it's not a container, michael@0: // then we don't call it empty (it's an
, or
, etc). michael@0: // Also, if it's an anchor then don't treat it as empty - even though michael@0: // anchors are containers, named anchors are "empty" but we don't michael@0: // want to treat them as such. Also, don't call ListItems or table michael@0: // cells empty if caller desires. Form Widgets not empty. michael@0: if (!IsContainer(aNode->AsDOMNode()) || michael@0: (nsHTMLEditUtils::IsNamedAnchor(aNode) || michael@0: nsHTMLEditUtils::IsFormWidget(aNode) || michael@0: (aListOrCellNotEmpty && michael@0: (nsHTMLEditUtils::IsListItem(aNode) || michael@0: nsHTMLEditUtils::IsTableCell(aNode))))) { michael@0: *outIsEmptyNode = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // need this for later michael@0: bool isListItemOrCell = nsHTMLEditUtils::IsListItem(aNode) || michael@0: nsHTMLEditUtils::IsTableCell(aNode); michael@0: michael@0: // loop over children of node. if no children, or all children are either michael@0: // empty text nodes or non-editable, then node qualifies as empty michael@0: for (nsCOMPtr child = aNode->GetFirstChild(); michael@0: child; michael@0: child = child->GetNextSibling()) { michael@0: // Is the child editable and non-empty? if so, return false michael@0: if (nsEditor::IsEditable(child)) { michael@0: if (child->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: nsresult rv = IsVisTextNode(child, outIsEmptyNode, aSafeToAskFrames); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // break out if we find we aren't emtpy michael@0: if (!*outIsEmptyNode) { michael@0: return NS_OK; michael@0: } michael@0: } else { michael@0: // An editable, non-text node. We need to check its content. michael@0: // Is it the node we are iterating over? michael@0: if (child == aNode) { michael@0: break; michael@0: } michael@0: michael@0: if (aSingleBRDoesntCount && !*aSeenBR && child->IsHTML(nsGkAtoms::br)) { michael@0: // the first br in a block doesn't count if the caller so indicated michael@0: *aSeenBR = true; michael@0: } else { michael@0: // is it an empty node of some sort? michael@0: // note: list items or table cells are not considered empty michael@0: // if they contain other lists or tables michael@0: if (child->IsElement()) { michael@0: if (isListItemOrCell) { michael@0: if (nsHTMLEditUtils::IsList(child) || michael@0: child->IsHTML(nsGkAtoms::table)) { michael@0: // break out if we find we aren't empty michael@0: *outIsEmptyNode = false; michael@0: return NS_OK; michael@0: } michael@0: } else if (nsHTMLEditUtils::IsFormWidget(child)) { michael@0: // is it a form widget? michael@0: // break out if we find we aren't empty michael@0: *outIsEmptyNode = false; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: bool isEmptyNode = true; michael@0: nsresult rv = IsEmptyNodeImpl(child, &isEmptyNode, michael@0: aSingleBRDoesntCount, michael@0: aListOrCellNotEmpty, aSafeToAskFrames, michael@0: aSeenBR); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!isEmptyNode) { michael@0: // otherwise it ain't empty michael@0: *outIsEmptyNode = false; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // add to aElement the CSS inline styles corresponding to the HTML attribute michael@0: // aAttribute with its value aValue michael@0: nsresult michael@0: nsHTMLEditor::SetAttributeOrEquivalent(nsIDOMElement * aElement, michael@0: const nsAString & aAttribute, michael@0: const nsAString & aValue, michael@0: bool aSuppressTransaction) michael@0: { michael@0: nsAutoScriptBlocker scriptBlocker; michael@0: michael@0: nsresult res = NS_OK; michael@0: if (IsCSSEnabled() && mHTMLCSSUtils) { michael@0: int32_t count; michael@0: res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(aElement, nullptr, &aAttribute, &aValue, &count, michael@0: aSuppressTransaction); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (count) { michael@0: // we found an equivalence ; let's remove the HTML attribute itself if it is set michael@0: nsAutoString existingValue; michael@0: bool wasSet = false; michael@0: res = GetAttributeValue(aElement, aAttribute, existingValue, &wasSet); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (wasSet) { michael@0: if (aSuppressTransaction) michael@0: res = aElement->RemoveAttribute(aAttribute); michael@0: else michael@0: res = RemoveAttribute(aElement, aAttribute); michael@0: } michael@0: } michael@0: else { michael@0: // count is an integer that represents the number of CSS declarations applied to the michael@0: // element. If it is zero, we found no equivalence in this implementation for the michael@0: // attribute michael@0: if (aAttribute.EqualsLiteral("style")) { michael@0: // if it is the style attribute, just add the new value to the existing style michael@0: // attribute's value michael@0: nsAutoString existingValue; michael@0: bool wasSet = false; michael@0: res = GetAttributeValue(aElement, NS_LITERAL_STRING("style"), existingValue, &wasSet); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: existingValue.AppendLiteral(" "); michael@0: existingValue.Append(aValue); michael@0: if (aSuppressTransaction) michael@0: res = aElement->SetAttribute(aAttribute, existingValue); michael@0: else michael@0: res = SetAttribute(aElement, aAttribute, existingValue); michael@0: } michael@0: else { michael@0: // we have no CSS equivalence for this attribute and it is not the style michael@0: // attribute; let's set it the good'n'old HTML way michael@0: if (aSuppressTransaction) michael@0: res = aElement->SetAttribute(aAttribute, aValue); michael@0: else michael@0: res = SetAttribute(aElement, aAttribute, aValue); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: // we are not in an HTML+CSS editor; let's set the attribute the HTML way michael@0: if (aSuppressTransaction) michael@0: res = aElement->SetAttribute(aAttribute, aValue); michael@0: else michael@0: res = SetAttribute(aElement, aAttribute, aValue); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::RemoveAttributeOrEquivalent(nsIDOMElement* aElement, michael@0: const nsAString& aAttribute, michael@0: bool aSuppressTransaction) michael@0: { michael@0: nsCOMPtr element = do_QueryInterface(aElement); michael@0: NS_ENSURE_TRUE(element, NS_OK); michael@0: michael@0: nsCOMPtr attribute = do_GetAtom(aAttribute); michael@0: MOZ_ASSERT(attribute); michael@0: michael@0: nsresult res = NS_OK; michael@0: if (IsCSSEnabled() && mHTMLCSSUtils) { michael@0: res = mHTMLCSSUtils->RemoveCSSEquivalentToHTMLStyle( michael@0: element, nullptr, &aAttribute, nullptr, aSuppressTransaction); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: michael@0: if (element->HasAttr(kNameSpaceID_None, attribute)) { michael@0: if (aSuppressTransaction) { michael@0: res = element->UnsetAttr(kNameSpaceID_None, attribute, michael@0: /* aNotify = */ true); michael@0: } else { michael@0: res = RemoveAttribute(aElement, aAttribute); michael@0: } michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::SetIsCSSEnabled(bool aIsCSSPrefChecked) michael@0: { michael@0: if (!mHTMLCSSUtils) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: mHTMLCSSUtils->SetCSSEnabled(aIsCSSPrefChecked); michael@0: michael@0: // Disable the eEditorNoCSSMask flag if we're enabling StyleWithCSS. michael@0: uint32_t flags = mFlags; michael@0: if (aIsCSSPrefChecked) { michael@0: // Turn off NoCSS as we're enabling CSS michael@0: flags &= ~eEditorNoCSSMask; michael@0: } else { michael@0: // Turn on NoCSS, as we're disabling CSS. michael@0: flags |= eEditorNoCSSMask; michael@0: } michael@0: michael@0: return SetFlags(flags); michael@0: } michael@0: michael@0: // Set the block background color michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::SetCSSBackgroundColor(const nsAString& aColor) michael@0: { michael@0: if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } michael@0: ForceCompositionEnd(); michael@0: michael@0: // Protect the edit rules object from dying michael@0: nsCOMPtr kungFuDeathGrip(mRules); michael@0: michael@0: nsRefPtr selection = GetSelection(); michael@0: michael@0: bool isCollapsed = selection->Collapsed(); michael@0: michael@0: nsAutoEditBatch batchIt(this); michael@0: nsAutoRules beginRulesSniffing(this, EditAction::insertElement, nsIEditor::eNext); michael@0: nsAutoSelectionReset selectionResetter(selection, this); michael@0: nsAutoTxnsConserveSelection dontSpazMySelection(this); michael@0: michael@0: bool cancel, handled; michael@0: nsTextRulesInfo ruleInfo(EditAction::setTextProperty); michael@0: nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (!cancel && !handled) michael@0: { michael@0: // loop thru the ranges in the selection michael@0: nsAutoString bgcolor; bgcolor.AssignLiteral("bgcolor"); michael@0: uint32_t rangeCount = selection->GetRangeCount(); michael@0: for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { michael@0: nsCOMPtr cachedBlockParent = nullptr; michael@0: nsRefPtr range = selection->GetRangeAt(rangeIdx); michael@0: NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); michael@0: michael@0: // check for easy case: both range endpoints in same text node michael@0: nsCOMPtr startNode, endNode; michael@0: int32_t startOffset, endOffset; michael@0: res = range->GetStartContainer(getter_AddRefs(startNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndContainer(getter_AddRefs(endNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetStartOffset(&startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndOffset(&endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if ((startNode == endNode) && IsTextNode(startNode)) michael@0: { michael@0: // let's find the block container of the text node michael@0: nsCOMPtr blockParent; michael@0: blockParent = GetBlockNodeParent(startNode); michael@0: // and apply the background color to that block container michael@0: if (cachedBlockParent != blockParent) michael@0: { michael@0: cachedBlockParent = blockParent; michael@0: nsCOMPtr element = do_QueryInterface(blockParent); michael@0: int32_t count; michael@0: res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nullptr, &bgcolor, &aColor, &count, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: else if ((startNode == endNode) && nsTextEditUtils::IsBody(startNode) && isCollapsed) michael@0: { michael@0: // we have no block in the document, let's apply the background to the body michael@0: nsCOMPtr element = do_QueryInterface(startNode); michael@0: int32_t count; michael@0: res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nullptr, &bgcolor, &aColor, &count, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: else if ((startNode == endNode) && (((endOffset-startOffset) == 1) || (!startOffset && !endOffset))) michael@0: { michael@0: // a unique node is selected, let's also apply the background color michael@0: // to the containing block, possibly the node itself michael@0: nsCOMPtr selectedNode = GetChildAt(startNode, startOffset); michael@0: bool isBlock =false; michael@0: res = NodeIsBlockStatic(selectedNode, &isBlock); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsCOMPtr blockParent = selectedNode; michael@0: if (!isBlock) { michael@0: blockParent = GetBlockNodeParent(selectedNode); michael@0: } michael@0: if (cachedBlockParent != blockParent) michael@0: { michael@0: cachedBlockParent = blockParent; michael@0: nsCOMPtr element = do_QueryInterface(blockParent); michael@0: int32_t count; michael@0: res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nullptr, &bgcolor, &aColor, &count, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // not the easy case. range not contained in single text node. michael@0: // there are up to three phases here. There are all the nodes michael@0: // reported by the subtree iterator to be processed. And there michael@0: // are potentially a starting textnode and an ending textnode michael@0: // which are only partially contained by the range. michael@0: michael@0: // lets handle the nodes reported by the iterator. These nodes michael@0: // are entirely contained in the selection range. We build up michael@0: // a list of them (since doing operations on the document during michael@0: // iteration would perturb the iterator). michael@0: michael@0: nsCOMPtr iter = michael@0: do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &res); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(iter, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMArray arrayOfNodes; michael@0: nsCOMPtr node; michael@0: michael@0: // iterate range and build up array michael@0: res = iter->Init(range); michael@0: // init returns an error if no nodes in range. michael@0: // this can easily happen with the subtree michael@0: // iterator if the selection doesn't contain michael@0: // any *whole* nodes. michael@0: if (NS_SUCCEEDED(res)) michael@0: { michael@0: while (!iter->IsDone()) michael@0: { michael@0: node = do_QueryInterface(iter->GetCurrentNode()); michael@0: NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); michael@0: michael@0: if (IsEditable(node)) michael@0: { michael@0: arrayOfNodes.AppendObject(node); michael@0: } michael@0: michael@0: iter->Next(); michael@0: } michael@0: } michael@0: // first check the start parent of the range to see if it needs to michael@0: // be separately handled (it does if it's a text node, due to how the michael@0: // subtree iterator works - it will not have reported it). michael@0: if (IsTextNode(startNode) && IsEditable(startNode)) michael@0: { michael@0: nsCOMPtr blockParent; michael@0: blockParent = GetBlockNodeParent(startNode); michael@0: if (cachedBlockParent != blockParent) michael@0: { michael@0: cachedBlockParent = blockParent; michael@0: nsCOMPtr element = do_QueryInterface(blockParent); michael@0: int32_t count; michael@0: res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nullptr, &bgcolor, &aColor, &count, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: michael@0: // then loop through the list, set the property on each node michael@0: int32_t listCount = arrayOfNodes.Count(); michael@0: int32_t j; michael@0: for (j = 0; j < listCount; j++) michael@0: { michael@0: node = arrayOfNodes[j]; michael@0: // do we have a block here ? michael@0: bool isBlock =false; michael@0: res = NodeIsBlockStatic(node, &isBlock); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsCOMPtr blockParent = node; michael@0: if (!isBlock) { michael@0: // no we don't, let's find the block ancestor michael@0: blockParent = GetBlockNodeParent(node); michael@0: } michael@0: if (cachedBlockParent != blockParent) michael@0: { michael@0: cachedBlockParent = blockParent; michael@0: nsCOMPtr element = do_QueryInterface(blockParent); michael@0: int32_t count; michael@0: // and set the property on it michael@0: res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nullptr, &bgcolor, &aColor, &count, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: arrayOfNodes.Clear(); michael@0: michael@0: // last check the end parent of the range to see if it needs to michael@0: // be separately handled (it does if it's a text node, due to how the michael@0: // subtree iterator works - it will not have reported it). michael@0: if (IsTextNode(endNode) && IsEditable(endNode)) michael@0: { michael@0: nsCOMPtr blockParent; michael@0: blockParent = GetBlockNodeParent(endNode); michael@0: if (cachedBlockParent != blockParent) michael@0: { michael@0: cachedBlockParent = blockParent; michael@0: nsCOMPtr element = do_QueryInterface(blockParent); michael@0: int32_t count; michael@0: res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nullptr, &bgcolor, &aColor, &count, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (!cancel) michael@0: { michael@0: // post-process michael@0: res = mRules->DidDoAction(selection, &ruleInfo, res); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::SetBackgroundColor(const nsAString& aColor) michael@0: { michael@0: nsresult res; michael@0: if (IsCSSEnabled()) { michael@0: // if we are in CSS mode, we have to apply the background color to the michael@0: // containing block (or the body if we have no block-level element in michael@0: // the document) michael@0: res = SetCSSBackgroundColor(aColor); michael@0: } michael@0: else { michael@0: // but in HTML mode, we can only set the document's background color michael@0: res = SetHTMLBackgroundColor(aColor); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: // NodesSameType: do these nodes have the same tag? michael@0: // michael@0: /* virtual */ michael@0: bool michael@0: nsHTMLEditor::AreNodesSameType(nsIContent* aNode1, nsIContent* aNode2) michael@0: { michael@0: MOZ_ASSERT(aNode1); michael@0: MOZ_ASSERT(aNode2); michael@0: michael@0: if (aNode1->Tag() != aNode2->Tag()) { michael@0: return false; michael@0: } michael@0: michael@0: if (!IsCSSEnabled() || !aNode1->IsHTML(nsGkAtoms::span)) { michael@0: return true; michael@0: } michael@0: michael@0: // If CSS is enabled, we are stricter about span nodes. michael@0: return mHTMLCSSUtils->ElementsSameStyle(aNode1->AsDOMNode(), michael@0: aNode2->AsDOMNode()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::CopyLastEditableChildStyles(nsIDOMNode * aPreviousBlock, nsIDOMNode * aNewBlock, michael@0: nsIDOMNode **aOutBrNode) michael@0: { michael@0: *aOutBrNode = nullptr; michael@0: nsCOMPtr child, tmp; michael@0: nsresult res; michael@0: // first, clear out aNewBlock. Contract is that we want only the styles from previousBlock. michael@0: res = aNewBlock->GetFirstChild(getter_AddRefs(child)); michael@0: while (NS_SUCCEEDED(res) && child) michael@0: { michael@0: res = DeleteNode(child); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = aNewBlock->GetFirstChild(getter_AddRefs(child)); michael@0: } michael@0: // now find and clone the styles michael@0: child = aPreviousBlock; michael@0: tmp = aPreviousBlock; michael@0: while (tmp) { michael@0: child = tmp; michael@0: res = GetLastEditableChild(child, address_of(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: while (child && nsTextEditUtils::IsBreak(child)) { michael@0: nsCOMPtr priorNode; michael@0: res = GetPriorHTMLNode(child, address_of(priorNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: child = priorNode; michael@0: } michael@0: nsCOMPtr newStyles = nullptr, deepestStyle = nullptr; michael@0: while (child && (child != aPreviousBlock)) { michael@0: if (nsHTMLEditUtils::IsInlineStyle(child) || michael@0: nsEditor::NodeIsType(child, nsEditProperty::span)) { michael@0: nsAutoString domTagName; michael@0: child->GetNodeName(domTagName); michael@0: ToLowerCase(domTagName); michael@0: if (newStyles) { michael@0: nsCOMPtr newContainer; michael@0: res = InsertContainerAbove(newStyles, address_of(newContainer), domTagName); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: newStyles = newContainer; michael@0: } michael@0: else { michael@0: res = CreateNode(domTagName, aNewBlock, 0, getter_AddRefs(newStyles)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: deepestStyle = newStyles; michael@0: } michael@0: res = CloneAttributes(newStyles, child); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: nsCOMPtr tmp; michael@0: res = child->GetParentNode(getter_AddRefs(tmp)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: child = tmp; michael@0: } michael@0: if (deepestStyle) { michael@0: nsCOMPtr outBRNode; michael@0: res = CreateBR(deepestStyle, 0, address_of(outBRNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // Getters must addref michael@0: outBRNode.forget(aOutBrNode); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetElementOrigin(nsIDOMElement * aElement, int32_t & aX, int32_t & aY) michael@0: { michael@0: aX = 0; michael@0: aY = 0; michael@0: michael@0: NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED); michael@0: nsCOMPtr ps = GetPresShell(); michael@0: NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsCOMPtr content = do_QueryInterface(aElement); michael@0: nsIFrame *frame = content->GetPrimaryFrame(); michael@0: NS_ENSURE_TRUE(frame, NS_OK); michael@0: michael@0: nsIFrame *container = ps->GetAbsoluteContainingBlock(frame); michael@0: NS_ENSURE_TRUE(container, NS_OK); michael@0: nsPoint off = frame->GetOffsetTo(container); michael@0: aX = nsPresContext::AppUnitsToIntCSSPixels(off.x); michael@0: aY = nsPresContext::AppUnitsToIntCSSPixels(off.y); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::EndUpdateViewBatch() michael@0: { michael@0: nsresult res = nsEditor::EndUpdateViewBatch(); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // We may need to show resizing handles or update existing ones after michael@0: // all transactions are done. This way of doing is preferred to DOM michael@0: // mutation events listeners because all the changes the user can apply michael@0: // to a document may result in multiple events, some of them quite hard michael@0: // to listen too (in particular when an ancestor of the selection is michael@0: // changed but the selection itself is not changed). michael@0: if (mUpdateCount == 0) { michael@0: nsCOMPtr selection; michael@0: res = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED); michael@0: res = CheckSelectionStateForAnonymousButtons(selection); michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetSelectionContainer(nsIDOMElement ** aReturn) michael@0: { michael@0: nsCOMPtrselection; michael@0: nsresult res = GetSelection(getter_AddRefs(selection)); michael@0: // if we don't get the selection, just skip this michael@0: if (NS_FAILED(res) || !selection) return res; michael@0: michael@0: nsCOMPtr focusNode; michael@0: michael@0: if (selection->Collapsed()) { michael@0: res = selection->GetFocusNode(getter_AddRefs(focusNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } else { michael@0: michael@0: int32_t rangeCount; michael@0: res = selection->GetRangeCount(&rangeCount); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: if (rangeCount == 1) { michael@0: michael@0: nsCOMPtr range; michael@0: res = selection->GetRangeAt(0, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsCOMPtr startContainer, endContainer; michael@0: res = range->GetStartContainer(getter_AddRefs(startContainer)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndContainer(getter_AddRefs(endContainer)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: int32_t startOffset, endOffset; michael@0: res = range->GetStartOffset(&startOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: res = range->GetEndOffset(&endOffset); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr focusElement; michael@0: if (startContainer == endContainer && startOffset + 1 == endOffset) { michael@0: res = GetSelectedElement(EmptyString(), getter_AddRefs(focusElement)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: if (focusElement) michael@0: focusNode = do_QueryInterface(focusElement); michael@0: } michael@0: if (!focusNode) { michael@0: res = range->GetCommonAncestorContainer(getter_AddRefs(focusNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: } michael@0: else { michael@0: int32_t i; michael@0: nsCOMPtr range; michael@0: for (i = 0; i < rangeCount; i++) michael@0: { michael@0: res = selection->GetRangeAt(i, getter_AddRefs(range)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: nsCOMPtr startContainer; michael@0: res = range->GetStartContainer(getter_AddRefs(startContainer)); michael@0: if (NS_FAILED(res)) continue; michael@0: if (!focusNode) michael@0: focusNode = startContainer; michael@0: else if (focusNode != startContainer) { michael@0: res = startContainer->GetParentNode(getter_AddRefs(focusNode)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (focusNode) { michael@0: uint16_t nodeType; michael@0: focusNode->GetNodeType(&nodeType); michael@0: if (nsIDOMNode::TEXT_NODE == nodeType) { michael@0: nsCOMPtr parent; michael@0: res = focusNode->GetParentNode(getter_AddRefs(parent)); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: focusNode = parent; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr focusElement = do_QueryInterface(focusNode); michael@0: focusElement.forget(aReturn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::IsAnonymousElement(nsIDOMElement * aElement, bool * aReturn) michael@0: { michael@0: NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); michael@0: nsCOMPtr content = do_QueryInterface(aElement); michael@0: *aReturn = content->IsRootOfNativeAnonymousSubtree(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::SetReturnInParagraphCreatesNewParagraph(bool aCreatesNewParagraph) michael@0: { michael@0: mCRInParagraphCreatesParagraph = aCreatesNewParagraph; michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::GetReturnInParagraphCreatesNewParagraph() michael@0: { michael@0: return mCRInParagraphCreatesParagraph; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetReturnInParagraphCreatesNewParagraph(bool *aCreatesNewParagraph) michael@0: { michael@0: *aCreatesNewParagraph = mCRInParagraphCreatesParagraph; michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsHTMLEditor::GetFocusedContent() michael@0: { michael@0: NS_ENSURE_TRUE(mDocWeak, nullptr); michael@0: michael@0: nsFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: NS_ENSURE_TRUE(fm, nullptr); michael@0: michael@0: nsCOMPtr focusedContent = fm->GetFocusedContent(); michael@0: michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: bool inDesignMode = doc->HasFlag(NODE_IS_EDITABLE); michael@0: if (!focusedContent) { michael@0: // in designMode, nobody gets focus in most cases. michael@0: if (inDesignMode && OurWindowHasFocus()) { michael@0: nsCOMPtr docRoot = doc->GetRootElement(); michael@0: return docRoot.forget(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: if (inDesignMode) { michael@0: return OurWindowHasFocus() && michael@0: nsContentUtils::ContentIsDescendantOf(focusedContent, doc) ? michael@0: focusedContent.forget() : nullptr; michael@0: } michael@0: michael@0: // We're HTML editor for contenteditable michael@0: michael@0: // If the focused content isn't editable, or it has independent selection, michael@0: // we don't have focus. michael@0: if (!focusedContent->HasFlag(NODE_IS_EDITABLE) || michael@0: focusedContent->HasIndependentSelection()) { michael@0: return nullptr; michael@0: } michael@0: // If our window is focused, we're focused. michael@0: return OurWindowHasFocus() ? focusedContent.forget() : nullptr; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsHTMLEditor::GetFocusedContentForIME() michael@0: { michael@0: nsCOMPtr focusedContent = GetFocusedContent(); michael@0: if (!focusedContent) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: NS_ENSURE_TRUE(doc, nullptr); michael@0: return doc->HasFlag(NODE_IS_EDITABLE) ? nullptr : focusedContent.forget(); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::IsActiveInDOMWindow() michael@0: { michael@0: NS_ENSURE_TRUE(mDocWeak, false); michael@0: michael@0: nsFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: NS_ENSURE_TRUE(fm, false); michael@0: michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: bool inDesignMode = doc->HasFlag(NODE_IS_EDITABLE); michael@0: michael@0: // If we're in designMode, we're always active in the DOM window. michael@0: if (inDesignMode) { michael@0: return true; michael@0: } michael@0: michael@0: nsPIDOMWindow* ourWindow = doc->GetWindow(); michael@0: nsCOMPtr win; michael@0: nsIContent* content = michael@0: nsFocusManager::GetFocusedDescendant(ourWindow, false, michael@0: getter_AddRefs(win)); michael@0: if (!content) { michael@0: return false; michael@0: } michael@0: michael@0: // We're HTML editor for contenteditable michael@0: michael@0: // If the active content isn't editable, or it has independent selection, michael@0: // we're not active). michael@0: if (!content->HasFlag(NODE_IS_EDITABLE) || michael@0: content->HasIndependentSelection()) { michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: dom::Element* michael@0: nsHTMLEditor::GetActiveEditingHost() michael@0: { michael@0: NS_ENSURE_TRUE(mDocWeak, nullptr); michael@0: michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: NS_ENSURE_TRUE(doc, nullptr); michael@0: if (doc->HasFlag(NODE_IS_EDITABLE)) { michael@0: return doc->GetBodyElement(); michael@0: } michael@0: michael@0: // We're HTML editor for contenteditable michael@0: nsCOMPtr selection; michael@0: nsresult rv = GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: nsCOMPtr focusNode; michael@0: rv = selection->GetFocusNode(getter_AddRefs(focusNode)); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: nsCOMPtr content = do_QueryInterface(focusNode); michael@0: if (!content) { michael@0: return nullptr; michael@0: } michael@0: michael@0: // If the active content isn't editable, or it has independent selection, michael@0: // we're not active. michael@0: if (!content->HasFlag(NODE_IS_EDITABLE) || michael@0: content->HasIndependentSelection()) { michael@0: return nullptr; michael@0: } michael@0: return content->GetEditingHost(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsHTMLEditor::GetDOMEventTarget() michael@0: { michael@0: // Don't use getDocument here, because we have no way of knowing michael@0: // whether Init() was ever called. So we need to get the document michael@0: // ourselves, if it exists. michael@0: NS_PRECONDITION(mDocWeak, "This editor has not been initialized yet"); michael@0: nsCOMPtr target = do_QueryReferent(mDocWeak); michael@0: return target.forget(); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::ShouldReplaceRootElement() michael@0: { michael@0: if (!mRootElement) { michael@0: // If we don't know what is our root element, we should find our root. michael@0: return true; michael@0: } michael@0: michael@0: // If we temporary set document root element to mRootElement, but there is michael@0: // body element now, we should replace the root element by the body element. michael@0: nsCOMPtr docBody; michael@0: GetBodyElement(getter_AddRefs(docBody)); michael@0: return !SameCOMIdentity(docBody, mRootElement); michael@0: } michael@0: michael@0: void michael@0: nsHTMLEditor::ResetRootElementAndEventTarget() michael@0: { michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: // Need to remove the event listeners first because BeginningOfDocument michael@0: // could set a new root (and event target is set by InstallEventListeners()) michael@0: // and we won't be able to remove them from the old event target then. michael@0: RemoveEventListeners(); michael@0: mRootElement = nullptr; michael@0: nsresult rv = InstallEventListeners(); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: // We must have mRootElement now. michael@0: nsCOMPtr root; michael@0: rv = GetRootElement(getter_AddRefs(root)); michael@0: if (NS_FAILED(rv) || !mRootElement) { michael@0: return; michael@0: } michael@0: michael@0: rv = BeginningOfDocument(); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: michael@0: // When this editor has focus, we need to reset the selection limiter to michael@0: // new root. Otherwise, that is going to be done when this gets focus. michael@0: nsCOMPtr node = GetFocusedNode(); michael@0: nsCOMPtr target = do_QueryInterface(node); michael@0: if (target) { michael@0: InitializeSelection(target); michael@0: } michael@0: michael@0: SyncRealTimeSpell(); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLEditor::GetBodyElement(nsIDOMHTMLElement** aBody) michael@0: { michael@0: NS_PRECONDITION(mDocWeak, "bad state, null mDocWeak"); michael@0: nsCOMPtr htmlDoc = do_QueryReferent(mDocWeak); michael@0: if (!htmlDoc) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: return htmlDoc->GetBody(aBody); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsHTMLEditor::GetFocusedNode() michael@0: { michael@0: nsCOMPtr focusedContent = GetFocusedContent(); michael@0: if (!focusedContent) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: NS_ASSERTION(fm, "Focus manager is null"); michael@0: nsCOMPtr focusedElement; michael@0: fm->GetFocusedElement(getter_AddRefs(focusedElement)); michael@0: if (focusedElement) { michael@0: nsCOMPtr node = do_QueryInterface(focusedElement); michael@0: return node.forget(); michael@0: } michael@0: michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: return doc.forget(); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::OurWindowHasFocus() michael@0: { michael@0: NS_ENSURE_TRUE(mDocWeak, false); michael@0: nsIFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: NS_ENSURE_TRUE(fm, false); michael@0: nsCOMPtr focusedWindow; michael@0: fm->GetFocusedWindow(getter_AddRefs(focusedWindow)); michael@0: if (!focusedWindow) { michael@0: return false; michael@0: } michael@0: nsCOMPtr doc = do_QueryReferent(mDocWeak); michael@0: nsCOMPtr ourWindow = do_QueryInterface(doc->GetWindow()); michael@0: return ourWindow == focusedWindow; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::IsAcceptableInputEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: if (!nsEditor::IsAcceptableInputEvent(aEvent)) { michael@0: return false; michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(mDocWeak, false); michael@0: michael@0: nsCOMPtr target; michael@0: aEvent->GetTarget(getter_AddRefs(target)); michael@0: NS_ENSURE_TRUE(target, false); michael@0: michael@0: nsCOMPtr document = do_QueryReferent(mDocWeak); michael@0: if (document->HasFlag(NODE_IS_EDITABLE)) { michael@0: // If this editor is in designMode and the event target is the document, michael@0: // the event is for this editor. michael@0: nsCOMPtr targetDocument = do_QueryInterface(target); michael@0: if (targetDocument) { michael@0: return targetDocument == document; michael@0: } michael@0: // Otherwise, check whether the event target is in this document or not. michael@0: nsCOMPtr targetContent = do_QueryInterface(target); michael@0: NS_ENSURE_TRUE(targetContent, false); michael@0: return document == targetContent->GetCurrentDoc(); michael@0: } michael@0: michael@0: // This HTML editor is for contenteditable. We need to check the validity of michael@0: // the target. michael@0: nsCOMPtr targetContent = do_QueryInterface(target); michael@0: NS_ENSURE_TRUE(targetContent, false); michael@0: michael@0: // If the event is a mouse event, we need to check if the target content is michael@0: // the focused editing host or its descendant. michael@0: nsCOMPtr mouseEvent = do_QueryInterface(aEvent); michael@0: if (mouseEvent) { michael@0: nsIContent* editingHost = GetActiveEditingHost(); michael@0: // If there is no active editing host, we cannot handle the mouse event michael@0: // correctly. michael@0: if (!editingHost) { michael@0: return false; michael@0: } michael@0: // If clicked on non-editable root element but the body element is the michael@0: // active editing host, we should assume that the click event is targetted. michael@0: if (targetContent == document->GetRootElement() && michael@0: !targetContent->HasFlag(NODE_IS_EDITABLE) && michael@0: editingHost == document->GetBodyElement()) { michael@0: targetContent = editingHost; michael@0: } michael@0: // If the target element is neither the active editing host nor a descendant michael@0: // of it, we may not be able to handle the event. michael@0: if (!nsContentUtils::ContentIsDescendantOf(targetContent, editingHost)) { michael@0: return false; michael@0: } michael@0: // If the clicked element has an independent selection, we shouldn't michael@0: // handle this click event. michael@0: if (targetContent->HasIndependentSelection()) { michael@0: return false; michael@0: } michael@0: // If the target content is editable, we should handle this event. michael@0: return targetContent->HasFlag(NODE_IS_EDITABLE); michael@0: } michael@0: michael@0: // If the target of the other events which target focused element isn't michael@0: // editable or has an independent selection, this editor shouldn't handle the michael@0: // event. michael@0: if (!targetContent->HasFlag(NODE_IS_EDITABLE) || michael@0: targetContent->HasIndependentSelection()) { michael@0: return false; michael@0: } michael@0: michael@0: // Finally, check whether we're actually focused or not. When we're not michael@0: // focused, we should ignore the dispatched event by script (or something) michael@0: // because content editable element needs selection in itself for editing. michael@0: // However, when we're not focused, it's not guaranteed. michael@0: return IsActiveInDOMWindow(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTMLEditor::GetPreferredIMEState(IMEState *aState) michael@0: { michael@0: // HTML editor don't prefer the CSS ime-mode because IE didn't do so too. michael@0: aState->mOpen = IMEState::DONT_CHANGE_OPEN_STATE; michael@0: if (IsReadonly() || IsDisabled()) { michael@0: aState->mEnabled = IMEState::DISABLED; michael@0: } else { michael@0: aState->mEnabled = IMEState::ENABLED; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsHTMLEditor::GetInputEventTargetContent() michael@0: { michael@0: nsCOMPtr target = GetActiveEditingHost(); michael@0: return target.forget(); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLEditor::IsEditable(nsIContent* aNode) { michael@0: if (!nsPlaintextEditor::IsEditable(aNode)) { michael@0: return false; michael@0: } michael@0: if (aNode->IsElement()) { michael@0: // If we're dealing with an element, then ask it whether it's editable. michael@0: return aNode->IsEditable(); michael@0: } michael@0: // We might be dealing with a text node for example, which we always consider michael@0: // to be editable. michael@0: return true; michael@0: } michael@0: michael@0: // virtual MOZ_OVERRIDE michael@0: dom::Element* michael@0: nsHTMLEditor::GetEditorRoot() michael@0: { michael@0: return GetActiveEditingHost(); michael@0: }