/* 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 "EditorDOMAPIWrapper.h"
#include "EditorLineBreak.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 "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;
}

NS_IMETHODIMP HTMLEditor::GetIsAbsolutePositioningActive(bool* aIsActive) {
  MOZ_ASSERT(aIsActive);
  *aIsActive = !!mAbsolutelyPositionedObject;
  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.
  const 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");
    }
  }

  {
    AutoElementAttrAPIWrapper elementWrapper(*this, *absolutePositioningObject);
    if (NS_FAILED(elementWrapper.UnsetAttr(nsGkAtoms::_moz_abspos, true))) {
      NS_WARNING("AutoElementAttrAPIWrapper::UnsetAttr() failed, but ignored");
    } else {
      NS_WARNING_ASSERTION(
          elementWrapper.IsExpectedResult(EmptyString()),
          "Removing _moz_abspos attribute caused other mutations, 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;
  }

  {
    AutoElementAttrAPIWrapper elementWrapper(*this, aElement);
    nsresult rv =
        elementWrapper.SetAttr(nsGkAtoms::_moz_abspos, classValue, true);
    if (NS_FAILED(rv)) {
      NS_WARNING("AutoElementAttrAPIWrapper::SetAttr() failed");
      return rv;
    }
    NS_WARNING_ASSERTION(
        elementWrapper.IsExpectedResult(classValue),
        "Setting _moz_abspos attribute caused other mutations, but ignored");
  }

  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(
        *this, *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(
        *this, *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;
}

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");
  }

  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<CreateLineBreakResult, nsresult> insertBRElementResultOrError =
      InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement,
                      EditorDOMPoint(parentNode, 0u));
  if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) {
    NS_WARNING(
        "HTMLEditor::InsertLineBreak(WithTransaction::Yes, "
        "LineBreakType::BRElement) failed");
    return insertBRElementResultOrError.unwrapErr();
  }
  CreateLineBreakResult insertBRElementResult =
      insertBRElementResultOrError.unwrap();
  MOZ_ASSERT(insertBRElementResult.Handled());
  // XXX Is this intentional selection change?
  nsresult rv = insertBRElementResult.SuggestCaretPointTo(
      *this, {SuggestCaret::OnlyIfHasSuggestion,
              SuggestCaret::OnlyIfTransactionsAllowedToDoIt});
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                       "CreateElementResult::SuggestCaretPointTo() failed");
  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::IsImageElement(*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::background_color,
                                         backgroundColorValue);
  if (NS_FAILED(rv)) {
    NS_WARNING(
        "CSSEditUtils::GetComputedProperty(nsGkAtoms::background_color) "
        "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;

  auto color = style->StyleText()->mColor.ToColor();
  if (NS_GET_R(color) >= kBlackBgTrigger &&
      NS_GET_G(color) >= kBlackBgTrigger &&
      NS_GET_B(color) >= kBlackBgTrigger) {
    aReturn.AssignLiteral("black");
  } else {
    aReturn.AssignLiteral("white");
  }

  return NS_OK;
}

}  // namespace mozilla
