diff options
Diffstat (limited to 'editor/libeditor/HTMLAbsPositionEditor.cpp')
-rw-r--r-- | editor/libeditor/HTMLAbsPositionEditor.cpp | 1000 |
1 files changed, 1000 insertions, 0 deletions
diff --git a/editor/libeditor/HTMLAbsPositionEditor.cpp b/editor/libeditor/HTMLAbsPositionEditor.cpp new file mode 100644 index 0000000000..c9f387a7e4 --- /dev/null +++ b/editor/libeditor/HTMLAbsPositionEditor.cpp @@ -0,0 +1,1000 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "HTMLEditor.h" + +#include <math.h> + +#include "CSSEditUtils.h" +#include "EditAction.h" +#include "HTMLEditHelpers.h" +#include "HTMLEditorEventListener.h" +#include "HTMLEditUtils.h" + +#include "mozilla/EventListenerManager.h" +#include "mozilla/mozalloc.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_editor.h" +#include "mozilla/dom/AncestorIterator.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/EventTarget.h" +#include "nsAString.h" +#include "nsAlgorithm.h" +#include "nsCOMPtr.h" +#include "nsComputedDOMStyle.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsROCSSPrimitiveValue.h" +#include "nsINode.h" +#include "nsIPrincipal.h" +#include "nsISupportsImpl.h" +#include "nsISupportsUtils.h" +#include "nsLiteralString.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsStyledElement.h" +#include "nscore.h" +#include <algorithm> + +namespace mozilla { + +using namespace dom; + +nsresult HTMLEditor::SetSelectionToAbsoluteOrStaticAsAction( + bool aEnabled, nsIPrincipal* aPrincipal) { + AutoEditActionDataSetter editActionData( + *this, EditAction::eSetPositionToAbsoluteOrStatic, aPrincipal); + nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); + if (NS_FAILED(rv)) { + NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, + "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); + return rv; + } + + const RefPtr<Element> editingHost = ComputeEditingHost(); + if (!editingHost) { + return NS_SUCCESS_DOM_NO_OPERATION; + } + + if (aEnabled) { + Result<EditActionResult, nsresult> result = + SetSelectionToAbsoluteAsSubAction(*editingHost); + if (MOZ_UNLIKELY(result.isErr())) { + NS_WARNING("HTMLEditor::SetSelectionToAbsoluteAsSubAction() failed"); + return result.unwrapErr(); + } + return NS_OK; + } + Result<EditActionResult, nsresult> result = SetSelectionToStaticAsSubAction(); + if (MOZ_UNLIKELY(result.isErr())) { + NS_WARNING("HTMLEditor::SetSelectionToStaticAsSubAction() failed"); + return result.unwrapErr(); + } + return NS_OK; +} + +already_AddRefed<Element> +HTMLEditor::GetAbsolutelyPositionedSelectionContainer() const { + AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); + if (NS_WARN_IF(!editActionData.CanHandle())) { + return nullptr; + } + + Element* selectionContainerElement = GetSelectionContainerElement(); + if (NS_WARN_IF(!selectionContainerElement)) { + return nullptr; + } + + AutoTArray<RefPtr<Element>, 24> arrayOfParentElements; + for (Element* element : + selectionContainerElement->InclusiveAncestorsOfType<Element>()) { + arrayOfParentElements.AppendElement(element); + } + + nsAutoString positionValue; + for (RefPtr<Element> element = selectionContainerElement; element; + element = element->GetParentElement()) { + if (element->IsHTMLElement(nsGkAtoms::html)) { + NS_WARNING( + "HTMLEditor::GetAbsolutelyPositionedSelectionContainer() reached " + "<html> element"); + return nullptr; + } + nsCOMPtr<nsINode> parentNode = element->GetParentNode(); + nsresult rv = CSSEditUtils::GetComputedProperty( + MOZ_KnownLive(*element), *nsGkAtoms::position, positionValue); + if (NS_FAILED(rv)) { + NS_WARNING( + "CSSEditUtils::GetComputedProperty(nsGkAtoms::position) failed"); + return nullptr; + } + if (NS_WARN_IF(Destroyed()) || + NS_WARN_IF(parentNode != element->GetParentNode())) { + return nullptr; + } + if (positionValue.EqualsLiteral("absolute")) { + return element.forget(); + } + } + return nullptr; +} + +NS_IMETHODIMP HTMLEditor::GetAbsolutePositioningEnabled(bool* aIsEnabled) { + *aIsEnabled = IsAbsolutePositionEditorEnabled(); + return NS_OK; +} + +NS_IMETHODIMP HTMLEditor::SetAbsolutePositioningEnabled(bool aIsEnabled) { + EnableAbsolutePositionEditor(aIsEnabled); + return NS_OK; +} + +Result<int32_t, nsresult> HTMLEditor::AddZIndexWithTransaction( + nsStyledElement& aStyledElement, int32_t aChange) { + if (!aChange) { + return 0; // XXX Why don't we return current z-index value in this case? + } + + int32_t zIndex = GetZIndex(aStyledElement); + if (NS_WARN_IF(Destroyed())) { + return Err(NS_ERROR_EDITOR_DESTROYED); + } + zIndex = std::max(zIndex + aChange, 0); + nsresult rv = SetZIndexWithTransaction(aStyledElement, zIndex); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING("HTMLEditor::SetZIndexWithTransaction() destroyed the editor"); + return Err(NS_ERROR_EDITOR_DESTROYED); + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "HTMLEditor::SetZIndexWithTransaction() failed, but ignored"); + return zIndex; +} + +nsresult HTMLEditor::SetZIndexWithTransaction(nsStyledElement& aStyledElement, + int32_t aZIndex) { + nsAutoString zIndexValue; + zIndexValue.AppendInt(aZIndex); + + nsresult rv = CSSEditUtils::SetCSSPropertyWithTransaction( + *this, aStyledElement, *nsGkAtoms::z_index, zIndexValue); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyWithTransaction(nsGkAtoms::z_index) " + "destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyWithTransaction(nsGkAtoms::" + "z_index) failed, but ignored"); + return NS_OK; +} + +nsresult HTMLEditor::AddZIndexAsAction(int32_t aChange, + nsIPrincipal* aPrincipal) { + MOZ_ASSERT(IsEditActionDataAvailable()); + + AutoEditActionDataSetter editActionData( + *this, EditAction::eIncreaseOrDecreaseZIndex, aPrincipal); + nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); + if (NS_FAILED(rv)) { + NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, + "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); + return EditorBase::ToGenericNSResult(rv); + } + + Result<EditActionResult, nsresult> result = AddZIndexAsSubAction(aChange); + if (MOZ_UNLIKELY(result.isErr())) { + NS_WARNING("HTMLEditor::AddZIndexAsSubAction() failed"); + return EditorBase::ToGenericNSResult(result.unwrapErr()); + } + return NS_OK; +} + +int32_t HTMLEditor::GetZIndex(Element& aElement) { + AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); + if (NS_WARN_IF(!editActionData.CanHandle())) { + return 0; + } + + nsAutoString zIndexValue; + + nsresult rv = CSSEditUtils::GetSpecifiedProperty( + aElement, *nsGkAtoms::z_index, zIndexValue); + if (NS_FAILED(rv)) { + NS_WARNING("CSSEditUtils::GetSpecifiedProperty(nsGkAtoms::z_index) failed"); + return 0; + } + if (zIndexValue.EqualsLiteral("auto")) { + if (!aElement.GetParentElement()) { + NS_WARNING("aElement was an orphan node or the root node"); + return 0; + } + // we have to look at the positioned ancestors + // cf. CSS 2 spec section 9.9.1 + nsAutoString positionValue; + for (RefPtr<Element> element = aElement.GetParentElement(); element; + element = element->GetParentElement()) { + if (element->IsHTMLElement(nsGkAtoms::body)) { + return 0; + } + nsCOMPtr<nsINode> parentNode = element->GetParentElement(); + nsresult rv = CSSEditUtils::GetComputedProperty( + *element, *nsGkAtoms::position, positionValue); + if (NS_FAILED(rv)) { + NS_WARNING( + "CSSEditUtils::GetComputedProperty(nsGkAtoms::position) failed"); + return 0; + } + if (NS_WARN_IF(Destroyed()) || + NS_WARN_IF(parentNode != element->GetParentNode())) { + return 0; + } + if (!positionValue.EqualsLiteral("absolute")) { + continue; + } + // ah, we found one, what's its z-index ? If its z-index is auto, + // we have to continue climbing the document's tree + rv = CSSEditUtils::GetComputedProperty(*element, *nsGkAtoms::z_index, + zIndexValue); + if (NS_FAILED(rv)) { + NS_WARNING( + "CSSEditUtils::GetComputedProperty(nsGkAtoms::z_index) failed"); + return 0; + } + if (NS_WARN_IF(Destroyed()) || + NS_WARN_IF(parentNode != element->GetParentNode())) { + return 0; + } + if (!zIndexValue.EqualsLiteral("auto")) { + break; + } + } + } + + if (zIndexValue.EqualsLiteral("auto")) { + return 0; + } + + nsresult rvIgnored; + int32_t result = zIndexValue.ToInteger(&rvIgnored); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "nsAString::ToInteger() failed, but ignored"); + return result; +} + +bool HTMLEditor::CreateGrabberInternal(nsIContent& aParentContent) { + if (NS_WARN_IF(mGrabber)) { + return false; + } + + mGrabber = CreateAnonymousElement(nsGkAtoms::span, aParentContent, + u"mozGrabber"_ns, false); + + // mGrabber may be destroyed during creation due to there may be + // mutation event listener. + if (!mGrabber) { + NS_WARNING( + "HTMLEditor::CreateAnonymousElement(nsGkAtoms::span, mozGrabber) " + "failed"); + return false; + } + + EventListenerManager* eventListenerManager = + mGrabber->GetOrCreateListenerManager(); + eventListenerManager->AddEventListenerByType( + mEventListener, u"mousedown"_ns, TrustedEventsAtSystemGroupBubble()); + MOZ_ASSERT(mGrabber); + return true; +} + +nsresult HTMLEditor::RefreshGrabberInternal() { + MOZ_ASSERT(IsEditActionDataAvailable()); + + if (!mAbsolutelyPositionedObject) { + return NS_OK; + } + + OwningNonNull<Element> absolutelyPositionedObject = + *mAbsolutelyPositionedObject; + nsresult rv = GetPositionAndDimensions( + absolutelyPositionedObject, mPositionedObjectX, mPositionedObjectY, + mPositionedObjectWidth, mPositionedObjectHeight, + mPositionedObjectBorderLeft, mPositionedObjectBorderTop, + mPositionedObjectMarginLeft, mPositionedObjectMarginTop); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::GetPositionAndDimensions() failed"); + return rv; + } + if (NS_WARN_IF(absolutelyPositionedObject != mAbsolutelyPositionedObject)) { + return NS_ERROR_FAILURE; + } + + RefPtr<nsStyledElement> grabberStyledElement = + nsStyledElement::FromNodeOrNull(mGrabber.get()); + if (!grabberStyledElement) { + return NS_OK; + } + rv = SetAnonymousElementPositionWithoutTransaction( + *grabberStyledElement, mPositionedObjectX + 12, mPositionedObjectY - 14); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::SetAnonymousElementPositionWithoutTransaction() failed"); + return rv; + } + if (NS_WARN_IF(grabberStyledElement != mGrabber.get())) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +void HTMLEditor::HideGrabberInternal() { + if (NS_WARN_IF(!mAbsolutelyPositionedObject)) { + return; + } + + // Move all members to the local variables first since mutation event + // listener may try to show grabber while we're hiding them. + RefPtr<Element> absolutePositioningObject = + std::move(mAbsolutelyPositionedObject); + ManualNACPtr grabber = std::move(mGrabber); + ManualNACPtr positioningShadow = std::move(mPositioningShadow); + + // If we're still in dragging mode, it means that the dragging is canceled + // by the web app. + if (mGrabberClicked || mIsMoving) { + mGrabberClicked = false; + mIsMoving = false; + if (mEventListener) { + DebugOnly<nsresult> rvIgnored = + static_cast<HTMLEditorEventListener*>(mEventListener.get()) + ->ListenToMouseMoveEventForGrabber(false); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "HTMLEditorEventListener::" + "ListenToMouseMoveEventForGrabber(false) failed"); + } + } + + DebugOnly<nsresult> rv = absolutePositioningObject->UnsetAttr( + kNameSpaceID_None, nsGkAtoms::_moz_abspos, true); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Element::UnsetAttr(nsGkAtoms::_moz_abspos) failed, but ignored"); + + // We allow the pres shell to be null; when it is, we presume there + // are no document observers to notify, but we still want to + // UnbindFromTree. + RefPtr<PresShell> presShell = GetPresShell(); + if (grabber) { + DeleteRefToAnonymousNode(std::move(grabber), presShell); + } + if (positioningShadow) { + DeleteRefToAnonymousNode(std::move(positioningShadow), presShell); + } +} + +nsresult HTMLEditor::ShowGrabberInternal(Element& aElement) { + MOZ_ASSERT(IsEditActionDataAvailable()); + + const RefPtr<Element> editingHost = ComputeEditingHost(); + if (NS_WARN_IF(!editingHost) || + NS_WARN_IF(!aElement.IsInclusiveDescendantOf(editingHost))) { + return NS_ERROR_UNEXPECTED; + } + + if (NS_WARN_IF(mGrabber)) { + return NS_ERROR_UNEXPECTED; + } + + nsAutoString classValue; + nsresult rv = + GetTemporaryStyleForFocusedPositionedElement(aElement, classValue); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::GetTemporaryStyleForFocusedPositionedElement() failed"); + return rv; + } + + rv = aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::_moz_abspos, classValue, + true); + if (NS_FAILED(rv)) { + NS_WARNING("Element::SetAttr(nsGkAtoms::_moz_abspos) failed"); + return rv; + } + + mAbsolutelyPositionedObject = &aElement; + + Element* parentElement = aElement.GetParentElement(); + if (NS_WARN_IF(!parentElement)) { + return NS_ERROR_FAILURE; + } + + if (!CreateGrabberInternal(*parentElement)) { + NS_WARNING("HTMLEditor::CreateGrabberInternal() failed"); + return NS_ERROR_FAILURE; + } + + // If we succeeded to create the grabber, HideGrabberInternal() hasn't been + // called yet. So, mAbsolutelyPositionedObject should be non-nullptr. + MOZ_ASSERT(mAbsolutelyPositionedObject); + + // Finally, move the grabber to proper position. + rv = RefreshGrabberInternal(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::RefereshGrabberInternal() failed"); + return rv; +} + +nsresult HTMLEditor::StartMoving() { + MOZ_ASSERT(mGrabber); + + RefPtr<Element> parentElement = mGrabber->GetParentElement(); + if (NS_WARN_IF(!parentElement) || NS_WARN_IF(!mAbsolutelyPositionedObject)) { + return NS_ERROR_FAILURE; + } + + // now, let's create the resizing shadow + mPositioningShadow = + CreateShadow(*parentElement, *mAbsolutelyPositionedObject); + if (!mPositioningShadow) { + NS_WARNING("HTMLEditor::CreateShadow() failed"); + return NS_ERROR_FAILURE; + } + if (!mAbsolutelyPositionedObject) { + NS_WARNING("The target has gone during HTMLEditor::CreateShadow()"); + return NS_ERROR_FAILURE; + } + RefPtr<Element> positioningShadow = mPositioningShadow.get(); + RefPtr<Element> absolutelyPositionedObject = mAbsolutelyPositionedObject; + nsresult rv = + SetShadowPosition(*positioningShadow, *absolutelyPositionedObject, + mPositionedObjectX, mPositionedObjectY); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::SetShadowPosition() failed"); + return rv; + } + + // make the shadow appear + DebugOnly<nsresult> rvIgnored = + mPositioningShadow->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_class, true); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "Element::UnsetAttr(nsGkAtoms::_class) failed, but ignored"); + + // position it + if (RefPtr<nsStyledElement> positioningShadowStyledElement = + nsStyledElement::FromNode(mPositioningShadow.get())) { + nsresult rv; + rv = CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction( + *positioningShadowStyledElement, *nsGkAtoms::width, + mPositionedObjectWidth); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction(" + "nsGkAtoms::width) destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction(" + "nsGkAtoms::width) failed, but ignored"); + rv = CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction( + *positioningShadowStyledElement, *nsGkAtoms::height, + mPositionedObjectHeight); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction(" + "nsGkAtoms::height) destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction(" + "nsGkAtoms::height) failed, but ignored"); + } + + mIsMoving = true; + return NS_OK; // XXX Looks like nobody refers this result +} + +void HTMLEditor::SnapToGrid(int32_t& newX, int32_t& newY) const { + if (mSnapToGridEnabled && mGridSize) { + newX = (int32_t)floor(((float)newX / (float)mGridSize) + 0.5f) * mGridSize; + newY = (int32_t)floor(((float)newY / (float)mGridSize) + 0.5f) * mGridSize; + } +} + +nsresult HTMLEditor::GrabberClicked() { + if (NS_WARN_IF(!mEventListener)) { + return NS_ERROR_NOT_INITIALIZED; + } + nsresult rv = static_cast<HTMLEditorEventListener*>(mEventListener.get()) + ->ListenToMouseMoveEventForGrabber(true); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditorEventListener::ListenToMouseMoveEventForGrabber(true) " + "failed, but ignored"); + return NS_OK; + } + mGrabberClicked = true; + return NS_OK; +} + +nsresult HTMLEditor::EndMoving() { + if (mPositioningShadow) { + RefPtr<PresShell> presShell = GetPresShell(); + if (NS_WARN_IF(!presShell)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DeleteRefToAnonymousNode(std::move(mPositioningShadow), presShell); + + mPositioningShadow = nullptr; + } + + if (mEventListener) { + DebugOnly<nsresult> rvIgnored = + static_cast<HTMLEditorEventListener*>(mEventListener.get()) + ->ListenToMouseMoveEventForGrabber(false); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "HTMLEditorEventListener::" + "ListenToMouseMoveEventForGrabber(false) failed"); + } + + mGrabberClicked = false; + mIsMoving = false; + nsresult rv = RefreshEditingUI(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::RefreshEditingUI() failed"); + return rv; +} + +nsresult HTMLEditor::SetFinalPosition(int32_t aX, int32_t aY) { + MOZ_ASSERT(IsEditActionDataAvailable()); + + nsresult rv = EndMoving(); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::EndMoving() failed"); + return rv; + } + + // we have now to set the new width and height of the resized object + // we don't set the x and y position because we don't control that in + // a normal HTML layout + int32_t newX = mPositionedObjectX + aX - mOriginalX - + (mPositionedObjectBorderLeft + mPositionedObjectMarginLeft); + int32_t newY = mPositionedObjectY + aY - mOriginalY - + (mPositionedObjectBorderTop + mPositionedObjectMarginTop); + + SnapToGrid(newX, newY); + + nsAutoString x, y; + x.AppendInt(newX); + y.AppendInt(newY); + + // we want one transaction only from a user's point of view + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + + if (NS_WARN_IF(!mAbsolutelyPositionedObject)) { + return NS_ERROR_FAILURE; + } + if (RefPtr<nsStyledElement> styledAbsolutelyPositionedElement = + nsStyledElement::FromNode(mAbsolutelyPositionedObject)) { + nsresult rv; + rv = CSSEditUtils::SetCSSPropertyPixelsWithTransaction( + *this, *styledAbsolutelyPositionedElement, *nsGkAtoms::top, newY); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::top) " + "destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::top) " + "failed, but ignored"); + rv = CSSEditUtils::SetCSSPropertyPixelsWithTransaction( + *this, *styledAbsolutelyPositionedElement, *nsGkAtoms::left, newX); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::left) " + "destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::left) " + "failed, but ignored"); + } + // keep track of that size + mPositionedObjectX = newX; + mPositionedObjectY = newY; + + rv = RefreshResizersInternal(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::RefreshResizersInternal() failed"); + return rv; +} + +void HTMLEditor::AddPositioningOffset(int32_t& aX, int32_t& aY) { + // Get the positioning offset + const int32_t positioningOffset = StaticPrefs::editor_positioning_offset(); + aX += positioningOffset; + aY += positioningOffset; +} + +nsresult HTMLEditor::SetPositionToAbsoluteOrStatic(Element& aElement, + bool aEnabled) { + nsAutoString positionValue; + DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty( + aElement, *nsGkAtoms::position, positionValue); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "CSSEditUtils::GetComputedProperty(nsGkAtoms::position) " + "failed, but ignored"); + // nothing to do if the element is already in the state we want + if (positionValue.EqualsLiteral("absolute") == aEnabled) { + return NS_OK; + } + + if (aEnabled) { + nsresult rv = SetPositionToAbsolute(aElement); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::SetPositionToAbsolute() failed"); + return rv; + } + + nsresult rv = SetPositionToStatic(aElement); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::SetPositionToStatic() failed"); + return rv; +} + +nsresult HTMLEditor::SetPositionToAbsolute(Element& aElement) { + MOZ_ASSERT(IsEditActionDataAvailable()); + + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + + int32_t x, y; + DebugOnly<nsresult> rvIgnored = GetElementOrigin(aElement, x, y); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "HTMLEditor::GetElementOrigin() failed, but ignored"); + + nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement); + if (styledElement) { + // MOZ_KnownLive(*styledElement): aElement's lifetime must be guarantted + // by the caller because of MOZ_CAN_RUN_SCRIPT method. + nsresult rv = CSSEditUtils::SetCSSPropertyWithTransaction( + *this, MOZ_KnownLive(*styledElement), *nsGkAtoms::position, + u"absolute"_ns); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::SetCSSProperyWithTransaction(nsGkAtoms::Position) " + "destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "CSSEditUtils::SetCSSPropertyWithTransaction(nsGkAtoms::position, " + "absolute) failed, but ignored"); + } + + AddPositioningOffset(x, y); + SnapToGrid(x, y); + if (styledElement) { + // MOZ_KnownLive(*styledElement): aElement's lifetime must be guarantted + // by the caller because of MOZ_CAN_RUN_SCRIPT method. + nsresult rv = + SetTopAndLeftWithTransaction(MOZ_KnownLive(*styledElement), x, y); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::SetTopAndLeftWithTransaction() failed"); + return rv; + } + } + + // we may need to create a br if the positioned element is alone in its + // container + nsINode* parentNode = aElement.GetParentNode(); + if (parentNode->GetChildCount() != 1) { + return NS_OK; + } + Result<CreateElementResult, nsresult> insertBRElementResult = + InsertBRElement(WithTransaction::Yes, EditorDOMPoint(parentNode, 0u)); + if (MOZ_UNLIKELY(insertBRElementResult.isErr())) { + NS_WARNING("HTMLEditor::InsertBRElement(WithTransaction::Yes) failed"); + return insertBRElementResult.unwrapErr(); + } + // XXX Is this intentional selection change? + nsresult rv = insertBRElementResult.inspect().SuggestCaretPointTo( + *this, {SuggestCaret::OnlyIfHasSuggestion, + SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "CreateElementResult::SuggestCaretPointTo() failed"); + MOZ_ASSERT(insertBRElementResult.inspect().GetNewNode()); + return rv; +} + +nsresult HTMLEditor::SetPositionToStatic(Element& aElement) { + nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement); + if (NS_WARN_IF(!styledElement)) { + return NS_ERROR_INVALID_ARG; + } + + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + + nsresult rv; + // MOZ_KnownLive(*styledElement): aElement's lifetime must be guarantted + // by the caller because of MOZ_CAN_RUN_SCRIPT method. + rv = CSSEditUtils::RemoveCSSPropertyWithTransaction( + *this, MOZ_KnownLive(*styledElement), *nsGkAtoms::position, u""_ns); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::RemoveCSSPropertyWithTransaction(nsGkAtoms::position) " + "destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::RemoveCSSPropertyWithTransaction(nsGkAtoms::position) " + "failed, but ignored"); + // MOZ_KnownLive(*styledElement): aElement's lifetime must be guarantted + // by the caller because of MOZ_CAN_RUN_SCRIPT method. + rv = CSSEditUtils::RemoveCSSPropertyWithTransaction( + *this, MOZ_KnownLive(*styledElement), *nsGkAtoms::top, u""_ns); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::RemoveCSSPropertyWithTransaction(nsGkAtoms::top) " + "destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::RemoveCSSPropertyWithTransaction(nsGkAtoms::top) " + "failed, but ignored"); + // MOZ_KnownLive(*styledElement): aElement's lifetime must be guarantted + // by the caller because of MOZ_CAN_RUN_SCRIPT method. + rv = CSSEditUtils::RemoveCSSPropertyWithTransaction( + *this, MOZ_KnownLive(*styledElement), *nsGkAtoms::left, u""_ns); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::RemoveCSSPropertyWithTransaction(nsGkAtoms::left) " + "destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::RemoveCSSPropertyWithTransaction(nsGkAtoms::left) " + "failed, but ignored"); + // MOZ_KnownLive(*styledElement): aElement's lifetime must be guarantted + // by the caller because of MOZ_CAN_RUN_SCRIPT method. + rv = CSSEditUtils::RemoveCSSPropertyWithTransaction( + *this, MOZ_KnownLive(*styledElement), *nsGkAtoms::z_index, u""_ns); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::RemoveCSSPropertyWithTransaction(nsGkAtoms::z_index) " + "destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::RemoveCSSPropertyWithTransaction(nsGkAtoms::z_index) " + "failed, but ignored"); + + if (!HTMLEditUtils::IsImage(styledElement)) { + // MOZ_KnownLive(*styledElement): aElement's lifetime must be guarantted + // by the caller because of MOZ_CAN_RUN_SCRIPT method. + rv = CSSEditUtils::RemoveCSSPropertyWithTransaction( + *this, MOZ_KnownLive(*styledElement), *nsGkAtoms::width, u""_ns); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::RemoveCSSPropertyWithTransaction(nsGkAtoms::width) " + "destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::RemoveCSSPropertyWithTransaction(nsGkAtoms::width) " + "failed, but ignored"); + // MOZ_KnownLive(*styledElement): aElement's lifetime must be guarantted + // by the caller because of MOZ_CAN_RUN_SCRIPT method. + rv = CSSEditUtils::RemoveCSSPropertyWithTransaction( + *this, MOZ_KnownLive(*styledElement), *nsGkAtoms::height, u""_ns); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::RemoveCSSPropertyWithTransaction(nsGkAtoms::height) " + "destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::RemoveCSSPropertyWithTransaction(nsGkAtoms::height) " + "failed, but ignored"); + } + + if (!styledElement->IsHTMLElement(nsGkAtoms::div) || + HTMLEditor::HasStyleOrIdOrClassAttribute(*styledElement)) { + return NS_OK; + } + + EditorDOMPoint pointToPutCaret; + // Make sure the first fild and last child of aElement starts/ends hard + // line(s) even after removing `aElement`. + { + // MOZ_KnownLive(*styledElement): aElement's lifetime must be guarantted + // by the caller because of MOZ_CAN_RUN_SCRIPT method. + Result<CreateElementResult, nsresult> + maybeInsertBRElementBeforeFirstChildResult = + EnsureHardLineBeginsWithFirstChildOf(MOZ_KnownLive(*styledElement)); + if (MOZ_UNLIKELY(maybeInsertBRElementBeforeFirstChildResult.isErr())) { + NS_WARNING("HTMLEditor::EnsureHardLineBeginsWithFirstChildOf() failed"); + return maybeInsertBRElementBeforeFirstChildResult.unwrapErr(); + } + CreateElementResult unwrappedResult = + maybeInsertBRElementBeforeFirstChildResult.unwrap(); + if (unwrappedResult.HasCaretPointSuggestion()) { + pointToPutCaret = unwrappedResult.UnwrapCaretPoint(); + } + } + { + // MOZ_KnownLive(*styledElement): aElement's lifetime must be guarantted + // by the caller because of MOZ_CAN_RUN_SCRIPT method. + Result<CreateElementResult, nsresult> + maybeInsertBRElementAfterLastChildResult = + EnsureHardLineEndsWithLastChildOf(MOZ_KnownLive(*styledElement)); + if (MOZ_UNLIKELY(maybeInsertBRElementAfterLastChildResult.isErr())) { + NS_WARNING("HTMLEditor::EnsureHardLineEndsWithLastChildOf() failed"); + return maybeInsertBRElementAfterLastChildResult.unwrapErr(); + } + CreateElementResult unwrappedResult = + maybeInsertBRElementAfterLastChildResult.unwrap(); + if (unwrappedResult.HasCaretPointSuggestion()) { + pointToPutCaret = unwrappedResult.UnwrapCaretPoint(); + } + } + { + // MOZ_KnownLive(*styledElement): aElement's lifetime must be guarantted + // by the caller because of MOZ_CAN_RUN_SCRIPT method. + Result<EditorDOMPoint, nsresult> unwrapStyledElementResult = + RemoveContainerWithTransaction(MOZ_KnownLive(*styledElement)); + if (MOZ_UNLIKELY(unwrapStyledElementResult.isErr())) { + NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed"); + return unwrapStyledElementResult.unwrapErr(); + } + if (unwrapStyledElementResult.inspect().IsSet()) { + pointToPutCaret = unwrapStyledElementResult.unwrap(); + } + } + if (!AllowsTransactionsToChangeSelection() || !pointToPutCaret.IsSet()) { + return NS_OK; + } + rv = CollapseSelectionTo(pointToPutCaret); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "EditorBase::CollapseSelectionTo() failed"); + return rv; +} + +NS_IMETHODIMP HTMLEditor::SetSnapToGridEnabled(bool aEnabled) { + mSnapToGridEnabled = aEnabled; + return NS_OK; +} + +NS_IMETHODIMP HTMLEditor::GetSnapToGridEnabled(bool* aIsEnabled) { + *aIsEnabled = mSnapToGridEnabled; + return NS_OK; +} + +NS_IMETHODIMP HTMLEditor::SetGridSize(uint32_t aSize) { + mGridSize = aSize; + return NS_OK; +} + +NS_IMETHODIMP HTMLEditor::GetGridSize(uint32_t* aSize) { + *aSize = mGridSize; + return NS_OK; +} + +nsresult HTMLEditor::SetTopAndLeftWithTransaction( + nsStyledElement& aStyledElement, int32_t aX, int32_t aY) { + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + nsresult rv; + rv = CSSEditUtils::SetCSSPropertyPixelsWithTransaction(*this, aStyledElement, + *nsGkAtoms::left, aX); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::left) " + "destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::left) " + "failed, but ignored"); + rv = CSSEditUtils::SetCSSPropertyPixelsWithTransaction(*this, aStyledElement, + *nsGkAtoms::top, aY); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::top) " + "destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::top) " + "failed, but ignored"); + return NS_OK; +} + +nsresult HTMLEditor::GetTemporaryStyleForFocusedPositionedElement( + Element& aElement, nsAString& aReturn) { + // we are going to outline the positioned element and bring it to the + // front to overlap any other element intersecting with it. But + // first, let's see what's the background and foreground colors of the + // positioned element. + // if background-image computed value is 'none, + // If the background color is 'auto' and R G B values of the foreground are + // each above #d0, use a black background + // If the background color is 'auto' and at least one of R G B values of + // the foreground is below #d0, use a white background + // Otherwise don't change background/foreground + aReturn.Truncate(); + + nsAutoString backgroundImageValue; + nsresult rv = CSSEditUtils::GetComputedProperty( + aElement, *nsGkAtoms::background_image, backgroundImageValue); + if (NS_FAILED(rv)) { + NS_WARNING( + "CSSEditUtils::GetComputedProperty(nsGkAtoms::background_image) " + "failed"); + return rv; + } + if (!backgroundImageValue.EqualsLiteral("none")) { + return NS_OK; + } + + nsAutoString backgroundColorValue; + rv = CSSEditUtils::GetComputedProperty(aElement, *nsGkAtoms::backgroundColor, + backgroundColorValue); + if (NS_FAILED(rv)) { + NS_WARNING( + "CSSEditUtils::GetComputedProperty(nsGkAtoms::backgroundColor) " + "failed"); + return rv; + } + if (!backgroundColorValue.EqualsLiteral("rgba(0, 0, 0, 0)")) { + return NS_OK; + } + + RefPtr<const ComputedStyle> style = + nsComputedDOMStyle::GetComputedStyle(&aElement); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + if (!style) { + NS_WARNING("nsComputedDOMStyle::GetComputedStyle() failed"); + return NS_ERROR_FAILURE; + } + + static const uint8_t kBlackBgTrigger = 0xd0; + + const auto& color = style->StyleText()->mColor; + if (color.red >= kBlackBgTrigger && color.green >= kBlackBgTrigger && + color.blue >= kBlackBgTrigger) { + aReturn.AssignLiteral("black"); + } else { + aReturn.AssignLiteral("white"); + } + + return NS_OK; +} + +} // namespace mozilla |