diff options
Diffstat (limited to 'editor/libeditor/HTMLEditorObjectResizer.cpp')
-rw-r--r-- | editor/libeditor/HTMLEditorObjectResizer.cpp | 1496 |
1 files changed, 1496 insertions, 0 deletions
diff --git a/editor/libeditor/HTMLEditorObjectResizer.cpp b/editor/libeditor/HTMLEditorObjectResizer.cpp new file mode 100644 index 0000000000..630b2f467c --- /dev/null +++ b/editor/libeditor/HTMLEditorObjectResizer.cpp @@ -0,0 +1,1496 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "ErrorList.h" +#include "HTMLEditor.h" + +#include "CSSEditUtils.h" +#include "HTMLEditorEventListener.h" +#include "HTMLEditUtils.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/mozalloc.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/StaticPrefs_editor.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/MouseEvent.h" +#include "mozilla/dom/EventTarget.h" +#include "nsAString.h" +#include "nsAlgorithm.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsGkAtoms.h" +#include "nsAtom.h" +#include "nsIContent.h" +#include "nsID.h" +#include "mozilla/dom/Document.h" +#include "nsISupportsUtils.h" +#include "nsPIDOMWindow.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsStyledElement.h" +#include "nsTextNode.h" +#include "nscore.h" +#include <algorithm> + +#define kTopLeft u"nw"_ns +#define kTop u"n"_ns +#define kTopRight u"ne"_ns +#define kLeft u"w"_ns +#define kRight u"e"_ns +#define kBottomLeft u"sw"_ns +#define kBottom u"s"_ns +#define kBottomRight u"se"_ns + +namespace mozilla { + +using namespace dom; + +/****************************************************************************** + * mozilla::HTMLEditor + ******************************************************************************/ + +ManualNACPtr HTMLEditor::CreateResizer(int16_t aLocation, + nsIContent& aParentContent) { + ManualNACPtr resizer = CreateAnonymousElement(nsGkAtoms::span, aParentContent, + u"mozResizer"_ns, false); + if (!resizer) { + NS_WARNING( + "HTMLEditor::CreateAnonymousElement(nsGkAtoms::span, mozResizer) " + "failed"); + return nullptr; + } + + // add the mouse listener so we can detect a click on a resizer + DebugOnly<nsresult> rvIgnored = + resizer->AddEventListener(u"mousedown"_ns, mEventListener, true); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "EventTarget::AddEventListener(mousedown) failed, but ignored"); + + nsAutoString locationStr; + switch (aLocation) { + case nsIHTMLObjectResizer::eTopLeft: + locationStr = kTopLeft; + break; + case nsIHTMLObjectResizer::eTop: + locationStr = kTop; + break; + case nsIHTMLObjectResizer::eTopRight: + locationStr = kTopRight; + break; + + case nsIHTMLObjectResizer::eLeft: + locationStr = kLeft; + break; + case nsIHTMLObjectResizer::eRight: + locationStr = kRight; + break; + + case nsIHTMLObjectResizer::eBottomLeft: + locationStr = kBottomLeft; + break; + case nsIHTMLObjectResizer::eBottom: + locationStr = kBottom; + break; + case nsIHTMLObjectResizer::eBottomRight: + locationStr = kBottomRight; + break; + } + + if (NS_FAILED(resizer->SetAttr(kNameSpaceID_None, nsGkAtoms::anonlocation, + locationStr, true))) { + NS_WARNING("Element::SetAttr(nsGkAtoms::anonlocation) failed"); + return nullptr; + } + return resizer; +} + +ManualNACPtr HTMLEditor::CreateShadow(nsIContent& aParentContent, + Element& aOriginalObject) { + // let's create an image through the element factory + RefPtr<nsAtom> name; + if (HTMLEditUtils::IsImage(&aOriginalObject)) { + name = nsGkAtoms::img; + } else { + name = nsGkAtoms::span; + } + + return CreateAnonymousElement(name, aParentContent, u"mozResizingShadow"_ns, + true); +} + +ManualNACPtr HTMLEditor::CreateResizingInfo(nsIContent& aParentContent) { + // let's create an info box through the element factory + return CreateAnonymousElement(nsGkAtoms::span, aParentContent, + u"mozResizingInfo"_ns, true); +} + +nsresult HTMLEditor::SetAllResizersPosition() { + if (NS_WARN_IF(!mTopLeftHandle)) { + return NS_ERROR_FAILURE; // There are no resizers. + } + + int32_t x = mResizedObjectX; + int32_t y = mResizedObjectY; + int32_t w = mResizedObjectWidth; + int32_t h = mResizedObjectHeight; + + nsAutoString value; + float resizerWidth, resizerHeight; + RefPtr<nsAtom> dummyUnit; + DebugOnly<nsresult> rvIgnored = NS_OK; + OwningNonNull<Element> topLeftHandle = *mTopLeftHandle.get(); + // XXX Do we really need to computed value rather than specified value? + // Because it's an anonymous node. + rvIgnored = CSSEditUtils::GetComputedProperty(*topLeftHandle, + *nsGkAtoms::width, value); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + if (NS_WARN_IF(topLeftHandle != mTopLeftHandle)) { + return NS_ERROR_FAILURE; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "CSSEditUtils::GetComputedProperty(nsGkAtoms::width) " + "failed, but ignored"); + rvIgnored = CSSEditUtils::GetComputedProperty(*topLeftHandle, + *nsGkAtoms::height, value); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + if (NS_WARN_IF(topLeftHandle != mTopLeftHandle)) { + return NS_ERROR_FAILURE; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "CSSEditUtils::GetComputedProperty(nsGkAtoms::height) " + "failed, but ignored"); + CSSEditUtils::ParseLength(value, &resizerWidth, getter_AddRefs(dummyUnit)); + CSSEditUtils::ParseLength(value, &resizerHeight, getter_AddRefs(dummyUnit)); + + int32_t rw = static_cast<int32_t>((resizerWidth + 1) / 2); + int32_t rh = static_cast<int32_t>((resizerHeight + 1) / 2); + + // While moving each resizer, mutation event listener may hide the resizers. + // And in worst case, new resizers may be recreated. So, we need to store + // all resizers here, and then, if we detect a resizer is removed or replaced, + // we should do nothing anymore. + // FYI: Note that only checking if mTopLeftHandle is replaced is enough. + // We're may be in hot path if user resizes an element a lot. So, + // we should just add-ref mTopLeftHandle. + auto setHandlePosition = + [this](ManualNACPtr& aHandleElement, int32_t aNewX, int32_t aNewY) + MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> nsresult { + RefPtr<nsStyledElement> handleStyledElement = + nsStyledElement::FromNodeOrNull(aHandleElement.get()); + if (!handleStyledElement) { + return NS_OK; + } + nsresult rv = SetAnonymousElementPositionWithoutTransaction( + *handleStyledElement, aNewX, aNewY); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::SetAnonymousElementPositionWithoutTransaction() " + "failed"); + return rv; + } + return NS_WARN_IF(handleStyledElement != aHandleElement.get()) + ? NS_ERROR_FAILURE + : NS_OK; + }; + nsresult rv; + rv = setHandlePosition(mTopLeftHandle, x - rw, y - rh); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to set top-left handle position"); + return rv; + } + rv = setHandlePosition(mTopHandle, x + w / 2 - rw, y - rh); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to set top handle position"); + return rv; + } + rv = setHandlePosition(mTopRightHandle, x + w - rw - 1, y - rh); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to set top-right handle position"); + return rv; + } + + rv = setHandlePosition(mLeftHandle, x - rw, y + h / 2 - rh); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to set left handle position"); + return rv; + } + rv = setHandlePosition(mRightHandle, x + w - rw - 1, y + h / 2 - rh); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to set right handle position"); + return rv; + } + + rv = setHandlePosition(mBottomLeftHandle, x - rw, y + h - rh - 1); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to set bottom-left handle position"); + return rv; + } + rv = setHandlePosition(mBottomHandle, x + w / 2 - rw, y + h - rh - 1); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to set bottom handle position"); + return rv; + } + rv = setHandlePosition(mBottomRightHandle, x + w - rw - 1, y + h - rh - 1); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to set bottom-right handle position"); + return rv; + } + + return NS_OK; +} + +nsresult HTMLEditor::RefreshResizers() { + AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); + if (NS_WARN_IF(!editActionData.CanHandle())) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv = RefreshResizersInternal(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::RefreshResizersInternal() failed"); + return EditorBase::ToGenericNSResult(rv); +} + +nsresult HTMLEditor::RefreshResizersInternal() { + MOZ_ASSERT(IsEditActionDataAvailable()); + + // Don't warn even if resizers are not visible since script cannot check + // if they are visible and this is non-virtual method. So, the cost of + // calling this can be ignored. + if (!mResizedObject) { + return NS_OK; + } + + OwningNonNull<Element> resizedObject = *mResizedObject; + nsresult rv = GetPositionAndDimensions( + resizedObject, mResizedObjectX, mResizedObjectY, mResizedObjectWidth, + mResizedObjectHeight, mResizedObjectBorderLeft, mResizedObjectBorderTop, + mResizedObjectMarginLeft, mResizedObjectMarginTop); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::GetPositionAndDimensions() failed"); + return rv; + } + if (NS_WARN_IF(resizedObject != mResizedObject)) { + return NS_ERROR_FAILURE; + } + + rv = SetAllResizersPosition(); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::SetAllResizersPosition() failed"); + return rv; + } + if (NS_WARN_IF(resizedObject != mResizedObject)) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT( + mResizingShadow, + "SetAllResizersPosition() should return error if resizers are hidden"); + RefPtr<Element> resizingShadow = mResizingShadow.get(); + rv = SetShadowPosition(*resizingShadow, resizedObject, mResizedObjectX, + mResizedObjectY); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::SetShadowPosition() failed"); + return rv; + } + if (NS_WARN_IF(resizedObject != mResizedObject)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult HTMLEditor::ShowResizersInternal(Element& aResizedElement) { + // When we have visible resizers, we cannot show new resizers. + // So, the caller should call HideResizersInternal() first if this + // returns error. + if (NS_WARN_IF(mResizedObject)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIContent> parentContent = aResizedElement.GetParent(); + if (NS_WARN_IF(!parentContent)) { + return NS_ERROR_FAILURE; + } + + const RefPtr<Element> editingHost = ComputeEditingHost(); + if (NS_WARN_IF(!editingHost) || + NS_WARN_IF(!aResizedElement.IsInclusiveDescendantOf(editingHost))) { + return NS_ERROR_UNEXPECTED; + } + + // Let's create and setup resizers. If we failed something, we should + // cancel everything which we do in this method. + do { + mResizedObject = &aResizedElement; + + // The resizers and the shadow will be anonymous siblings of the element. + // Note that creating a resizer or shadow may causes calling + // HideRisizersInternal() via a mutation event listener. So, we should + // store new resizer to a local variable, then, check: + // - whether creating resizer is already set to the member or not + // - whether resizing element is changed to another element + // If showing resizers are canceled, we hit the latter check. + // If resizers for another element is shown during this, we hit the latter + // check too. + // If resizers are just shown again for same element, we hit the former + // check. + ManualNACPtr newResizer = + CreateResizer(nsIHTMLObjectResizer::eTopLeft, *parentContent); + if (!newResizer) { + NS_WARNING("HTMLEditor::CreateResizer(eTopLeft) failed"); + break; + } + if (NS_WARN_IF(mTopLeftHandle) || + NS_WARN_IF(mResizedObject != &aResizedElement)) { + // Don't hide current resizers in this case because they are not what + // we're creating. + return NS_ERROR_FAILURE; + } + mTopLeftHandle = std::move(newResizer); + newResizer = CreateResizer(nsIHTMLObjectResizer::eTop, *parentContent); + if (!newResizer) { + NS_WARNING("HTMLEditor::CreateResizer(eTop) failed"); + break; + } + if (NS_WARN_IF(mTopHandle) || + NS_WARN_IF(mResizedObject != &aResizedElement)) { + return NS_ERROR_FAILURE; + } + mTopHandle = std::move(newResizer); + newResizer = CreateResizer(nsIHTMLObjectResizer::eTopRight, *parentContent); + if (!newResizer) { + NS_WARNING("HTMLEditor::CreateResizer(eTopRight) failed"); + break; + } + if (NS_WARN_IF(mTopRightHandle) || + NS_WARN_IF(mResizedObject != &aResizedElement)) { + return NS_ERROR_FAILURE; + } + mTopRightHandle = std::move(newResizer); + + newResizer = CreateResizer(nsIHTMLObjectResizer::eLeft, *parentContent); + if (!newResizer) { + NS_WARNING("HTMLEditor::CreateResizer(eLeft) failed"); + break; + } + if (NS_WARN_IF(mLeftHandle) || + NS_WARN_IF(mResizedObject != &aResizedElement)) { + return NS_ERROR_FAILURE; + } + mLeftHandle = std::move(newResizer); + newResizer = CreateResizer(nsIHTMLObjectResizer::eRight, *parentContent); + if (!newResizer) { + NS_WARNING("HTMLEditor::CreateResizer(eRight) failed"); + break; + } + if (NS_WARN_IF(mRightHandle) || + NS_WARN_IF(mResizedObject != &aResizedElement)) { + return NS_ERROR_FAILURE; + } + mRightHandle = std::move(newResizer); + + newResizer = + CreateResizer(nsIHTMLObjectResizer::eBottomLeft, *parentContent); + if (!newResizer) { + NS_WARNING("HTMLEditor::CreateResizer(eBottomLeft) failed"); + break; + } + if (NS_WARN_IF(mBottomLeftHandle) || + NS_WARN_IF(mResizedObject != &aResizedElement)) { + return NS_ERROR_FAILURE; + } + mBottomLeftHandle = std::move(newResizer); + newResizer = CreateResizer(nsIHTMLObjectResizer::eBottom, *parentContent); + if (!newResizer) { + NS_WARNING("HTMLEditor::CreateResizer(eBottom) failed"); + break; + } + if (NS_WARN_IF(mBottomHandle) || + NS_WARN_IF(mResizedObject != &aResizedElement)) { + return NS_ERROR_FAILURE; + } + mBottomHandle = std::move(newResizer); + newResizer = + CreateResizer(nsIHTMLObjectResizer::eBottomRight, *parentContent); + if (!newResizer) { + NS_WARNING("HTMLEditor::CreateResizer(eBottomRight) failed"); + break; + } + if (NS_WARN_IF(mBottomRightHandle) || + NS_WARN_IF(mResizedObject != &aResizedElement)) { + return NS_ERROR_FAILURE; + } + mBottomRightHandle = std::move(newResizer); + + // Store the last resizer which we created. This is useful when we + // need to check whether our resizers are hiddedn and recreated another + // set of resizers or not. + RefPtr<Element> createdBottomRightHandle = mBottomRightHandle.get(); + + nsresult rv = GetPositionAndDimensions( + aResizedElement, mResizedObjectX, mResizedObjectY, mResizedObjectWidth, + mResizedObjectHeight, mResizedObjectBorderLeft, mResizedObjectBorderTop, + mResizedObjectMarginLeft, mResizedObjectMarginTop); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::GetPositionAndDimensions() failed"); + break; + } + + // and let's set their absolute positions in the document + rv = SetAllResizersPosition(); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::SetAllResizersPosition() failed"); + if (NS_WARN_IF(mBottomRightHandle.get() != createdBottomRightHandle)) { + return NS_ERROR_FAILURE; + } + break; + } + + // now, let's create the resizing shadow + ManualNACPtr newShadow = CreateShadow(*parentContent, aResizedElement); + if (!newShadow) { + NS_WARNING("HTMLEditor::CreateShadow() failed"); + break; + } + if (NS_WARN_IF(mResizingShadow) || + NS_WARN_IF(mResizedObject != &aResizedElement)) { + return NS_ERROR_FAILURE; + } + mResizingShadow = std::move(newShadow); + + // and set its position + RefPtr<Element> resizingShadow = mResizingShadow.get(); + rv = SetShadowPosition(*resizingShadow, aResizedElement, mResizedObjectX, + mResizedObjectY); + if (NS_FAILED(rv)) { + NS_WARNING("HTMLEditor::SetShadowPosition() failed"); + if (NS_WARN_IF(mBottomRightHandle.get() != createdBottomRightHandle)) { + return NS_ERROR_FAILURE; + } + break; + } + + // and then the resizing info tooltip + ManualNACPtr newResizingInfo = CreateResizingInfo(*parentContent); + if (!newResizingInfo) { + NS_WARNING("HTMLEditor::CreateResizingInfo() failed"); + break; + } + if (NS_WARN_IF(mResizingInfo) || + NS_WARN_IF(mResizedObject != &aResizedElement)) { + return NS_ERROR_FAILURE; + } + mResizingInfo = std::move(newResizingInfo); + + // and listen to the "resize" event on the window first, get the + // window from the document... + if (NS_WARN_IF(!mEventListener)) { + break; + } + + rv = static_cast<HTMLEditorEventListener*>(mEventListener.get()) + ->ListenToWindowResizeEvent(true); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditorEventListener::ListenToWindowResizeEvent(true) failed"); + break; + } + + MOZ_ASSERT(mResizedObject == &aResizedElement); + + // XXX Even when it failed to add event listener, should we need to set + // _moz_resizing attribute? + DebugOnly<nsresult> rvIgnored = aResizedElement.SetAttr( + kNameSpaceID_None, nsGkAtoms::_moz_resizing, u"true"_ns, true); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "Element::SetAttr(nsGkAtoms::_moz_resizing, true) failed, but ignored"); + return NS_OK; + } while (true); + + DebugOnly<nsresult> rv = HideResizersInternal(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::HideResizersInternal() failed to clean up"); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP HTMLEditor::HideResizers() { + AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); + if (NS_WARN_IF(!editActionData.CanHandle())) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv = HideResizersInternal(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::HideResizersInternal() failed"); + return EditorBase::ToGenericNSResult(rv); +} + +nsresult HTMLEditor::HideResizersInternal() { + // Don't warn even if resizers are visible since script cannot check + // if they are visible and this is non-virtual method. So, the cost of + // calling this can be ignored. + if (!mResizedObject) { + return NS_OK; + } + + // get the presshell's document observer interface. + RefPtr<PresShell> presShell = GetPresShell(); + NS_WARNING_ASSERTION(presShell, "There is no presShell"); + // 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. + + constexpr auto mousedown = u"mousedown"_ns; + + // HTMLEditor should forget all members related to resizers first since + // removing a part of UI may cause showing the resizers again. In such + // case, the members may be overwritten by ShowResizers() and this will + // lose the chance to release the old resizers. + ManualNACPtr topLeftHandle(std::move(mTopLeftHandle)); + ManualNACPtr topHandle(std::move(mTopHandle)); + ManualNACPtr topRightHandle(std::move(mTopRightHandle)); + ManualNACPtr leftHandle(std::move(mLeftHandle)); + ManualNACPtr rightHandle(std::move(mRightHandle)); + ManualNACPtr bottomLeftHandle(std::move(mBottomLeftHandle)); + ManualNACPtr bottomHandle(std::move(mBottomHandle)); + ManualNACPtr bottomRightHandle(std::move(mBottomRightHandle)); + ManualNACPtr resizingShadow(std::move(mResizingShadow)); + ManualNACPtr resizingInfo(std::move(mResizingInfo)); + RefPtr<Element> activatedHandle(std::move(mActivatedHandle)); + RefPtr<Element> resizedObject(std::move(mResizedObject)); + + // Remvoe all handles. + RemoveListenerAndDeleteRef(mousedown, mEventListener, true, + std::move(topLeftHandle), presShell); + + RemoveListenerAndDeleteRef(mousedown, mEventListener, true, + std::move(topHandle), presShell); + + RemoveListenerAndDeleteRef(mousedown, mEventListener, true, + std::move(topRightHandle), presShell); + + RemoveListenerAndDeleteRef(mousedown, mEventListener, true, + std::move(leftHandle), presShell); + + RemoveListenerAndDeleteRef(mousedown, mEventListener, true, + std::move(rightHandle), presShell); + + RemoveListenerAndDeleteRef(mousedown, mEventListener, true, + std::move(bottomLeftHandle), presShell); + + RemoveListenerAndDeleteRef(mousedown, mEventListener, true, + std::move(bottomHandle), presShell); + + RemoveListenerAndDeleteRef(mousedown, mEventListener, true, + std::move(bottomRightHandle), presShell); + + RemoveListenerAndDeleteRef(mousedown, mEventListener, true, + std::move(resizingShadow), presShell); + + RemoveListenerAndDeleteRef(mousedown, mEventListener, true, + std::move(resizingInfo), presShell); + + // Remove active state of a resizer. + if (activatedHandle) { + DebugOnly<nsresult> rvIgnored = activatedHandle->UnsetAttr( + kNameSpaceID_None, nsGkAtoms::_moz_activated, true); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "Element::UnsetAttr(nsGkAtoms::_moz_activated) failed, but ignored"); + } + + // Remove resizing state of the target element. + DebugOnly<nsresult> rvIgnored = resizedObject->UnsetAttr( + kNameSpaceID_None, nsGkAtoms::_moz_resizing, true); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "Element::UnsetAttr(nsGkAtoms::_moz_resizing) failed, but ignored"); + + if (!mEventListener) { + return NS_OK; + } + + nsresult rv = static_cast<HTMLEditorEventListener*>(mEventListener.get()) + ->ListenToMouseMoveEventForResizers(false); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditorEventListener::ListenToMouseMoveEventForResizers(false) " + "failed"); + return rv; + } + + // Remove resize event listener from the window. + if (!mEventListener) { + return NS_OK; + } + + rv = static_cast<HTMLEditorEventListener*>(mEventListener.get()) + ->ListenToWindowResizeEvent(false); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "HTMLEditorEventListener::ListenToWindowResizeEvent(false) failed"); + return rv; +} + +void HTMLEditor::HideShadowAndInfo() { + if (mResizingShadow) { + DebugOnly<nsresult> rvIgnored = mResizingShadow->SetAttr( + kNameSpaceID_None, nsGkAtoms::_class, u"hidden"_ns, true); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "Element::SetAttr(nsGkAtoms::_class, hidden) failed, but ignored"); + } + if (mResizingInfo) { + DebugOnly<nsresult> rvIgnored = mResizingInfo->SetAttr( + kNameSpaceID_None, nsGkAtoms::_class, u"hidden"_ns, true); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "Element::SetAttr(nsGkAtoms::_class, hidden) failed, but ignored"); + } +} + +nsresult HTMLEditor::StartResizing(Element& aHandleElement) { + mIsResizing = true; + mActivatedHandle = &aHandleElement; + DebugOnly<nsresult> rvIgnored = mActivatedHandle->SetAttr( + kNameSpaceID_None, nsGkAtoms::_moz_activated, u"true"_ns, true); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "Element::SetAttr(nsGkAtoms::_moz_activated, true) failed"); + + // do we want to preserve ratio or not? + const bool preserveRatio = StaticPrefs::editor_resizing_preserve_ratio() && + HTMLEditUtils::IsImage(mResizedObject); + + // the way we change the position/size of the shadow depends on + // the handle + nsAutoString locationStr; + mActivatedHandle->GetAttr(nsGkAtoms::anonlocation, locationStr); + if (locationStr.Equals(kTopLeft)) { + SetResizeIncrements(1, 1, -1, -1, preserveRatio); + } else if (locationStr.Equals(kTop)) { + SetResizeIncrements(0, 1, 0, -1, false); + } else if (locationStr.Equals(kTopRight)) { + SetResizeIncrements(0, 1, 1, -1, preserveRatio); + } else if (locationStr.Equals(kLeft)) { + SetResizeIncrements(1, 0, -1, 0, false); + } else if (locationStr.Equals(kRight)) { + SetResizeIncrements(0, 0, 1, 0, false); + } else if (locationStr.Equals(kBottomLeft)) { + SetResizeIncrements(1, 0, -1, 1, preserveRatio); + } else if (locationStr.Equals(kBottom)) { + SetResizeIncrements(0, 0, 0, 1, false); + } else if (locationStr.Equals(kBottomRight)) { + SetResizeIncrements(0, 0, 1, 1, preserveRatio); + } + + // make the shadow appear + rvIgnored = + mResizingShadow->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_class, true); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "Element::UnsetAttr(nsGkAtoms::_class) failed"); + + // position it + if (RefPtr<nsStyledElement> resizingShadowStyledElement = + nsStyledElement::FromNodeOrNull(mResizingShadow.get())) { + nsresult rv; + rv = CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction( + *resizingShadowStyledElement, *nsGkAtoms::width, mResizedObjectWidth); + 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"); + rv = CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction( + *resizingShadowStyledElement, *nsGkAtoms::height, mResizedObjectHeight); + 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"); + } + + // add a mouse move listener to the editor + if (NS_WARN_IF(!mEventListener)) { + return NS_ERROR_NOT_INITIALIZED; + } + nsresult rv = static_cast<HTMLEditorEventListener*>(mEventListener.get()) + ->ListenToMouseMoveEventForResizers(true); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditorEventListener::" + "ListenToMouseMoveEventForResizers(true) failed"); + return rv; +} + +nsresult HTMLEditor::StartToDragResizerOrHandleDragGestureOnGrabber( + MouseEvent& aMouseDownEvent, Element& aEventTargetElement) { + MOZ_ASSERT(aMouseDownEvent.GetExplicitOriginalTarget() == + &aEventTargetElement); + MOZ_ASSERT(!aMouseDownEvent.DefaultPrevented()); + MOZ_ASSERT(aMouseDownEvent.WidgetEventPtr()->mMessage == eMouseDown); + + nsAutoString anonclass; + aEventTargetElement.GetAttr(nsGkAtoms::_moz_anonclass, anonclass); + + if (anonclass.EqualsLiteral("mozResizer")) { + AutoEditActionDataSetter editActionData(*this, + EditAction::eResizingElement); + if (NS_WARN_IF(!editActionData.CanHandle())) { + return NS_ERROR_NOT_INITIALIZED; + } + + // If we have an anonymous element and that element is a resizer, + // let's start resizing! + aMouseDownEvent.PreventDefault(); + mOriginalX = aMouseDownEvent.ClientX(); + mOriginalY = aMouseDownEvent.ClientY(); + nsresult rv = StartResizing(aEventTargetElement); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::StartResizing() failed"); + return EditorBase::ToGenericNSResult(rv); + } + + if (anonclass.EqualsLiteral("mozGrabber")) { + AutoEditActionDataSetter editActionData(*this, EditAction::eMovingElement); + if (NS_WARN_IF(!editActionData.CanHandle())) { + return NS_ERROR_NOT_INITIALIZED; + } + + // If we have an anonymous element and that element is a grabber, + // let's start moving the element! + mOriginalX = aMouseDownEvent.ClientX(); + mOriginalY = aMouseDownEvent.ClientY(); + nsresult rv = GrabberClicked(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::GrabberClicked() failed"); + return EditorBase::ToGenericNSResult(rv); + } + + return NS_OK; +} + +nsresult HTMLEditor::StopDraggingResizerOrGrabberAt( + const CSSIntPoint& aClientPoint) { + if (mIsResizing) { + AutoEditActionDataSetter editActionData(*this, EditAction::eResizeElement); + if (NS_WARN_IF(!editActionData.CanHandle())) { + return NS_ERROR_NOT_INITIALIZED; + } + + // we are resizing and release the mouse button, so let's + // end the resizing process + mIsResizing = false; + HideShadowAndInfo(); + + nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); + if (NS_FAILED(rv)) { + NS_WARNING_ASSERTION( + rv == NS_ERROR_EDITOR_ACTION_CANCELED, + "EditorBase::MaybeDispatchBeforeInputEvent(), failed"); + return EditorBase::ToGenericNSResult(rv); + } + + rv = SetFinalSizeWithTransaction(aClientPoint.x, aClientPoint.y); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "HTMLEditor::SetFinalSizeWithTransaction() destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "HTMLEditor::SetFinalSizeWithTransaction() failed, but ignored"); + return NS_OK; + } + + if (mIsMoving || mGrabberClicked) { + AutoEditActionDataSetter editActionData(*this, EditAction::eMoveElement); + nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); + if (rv != NS_ERROR_EDITOR_ACTION_CANCELED && NS_WARN_IF(NS_FAILED(rv))) { + NS_WARNING("CanHandleAndMaybeDispatchBeforeInputEvent() failed"); + return EditorBase::ToGenericNSResult(rv); + } + + if (mIsMoving) { + DebugOnly<nsresult> rvIgnored = mPositioningShadow->SetAttr( + kNameSpaceID_None, nsGkAtoms::_class, u"hidden"_ns, true); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "Element::SetAttr(nsGkAtoms::_class, hidden) failed"); + if (rv != NS_ERROR_EDITOR_ACTION_CANCELED) { + SetFinalPosition(aClientPoint.x, aClientPoint.y); + } + } + if (mGrabberClicked) { + DebugOnly<nsresult> rvIgnored = EndMoving(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "HTMLEditor::EndMoving() failed"); + } + return EditorBase::ToGenericNSResult(rv); + } + + return NS_OK; +} + +void HTMLEditor::SetResizeIncrements(int32_t aX, int32_t aY, int32_t aW, + int32_t aH, bool aPreserveRatio) { + mXIncrementFactor = aX; + mYIncrementFactor = aY; + mWidthIncrementFactor = aW; + mHeightIncrementFactor = aH; + mPreserveRatio = aPreserveRatio; +} + +nsresult HTMLEditor::SetResizingInfoPosition(int32_t aX, int32_t aY, int32_t aW, + int32_t aH) { + // Determine the position of the resizing info box based upon the new + // position and size of the element (aX, aY, aW, aH), and which + // resizer is the "activated handle". For example, place the resizing + // info box at the bottom-right corner of the new element, if the element + // is being resized by the bottom-right resizer. + int32_t infoXPosition; + int32_t infoYPosition; + + if (mActivatedHandle == mTopLeftHandle || mActivatedHandle == mLeftHandle || + mActivatedHandle == mBottomLeftHandle) { + infoXPosition = aX; + } else if (mActivatedHandle == mTopHandle || + mActivatedHandle == mBottomHandle) { + infoXPosition = aX + (aW / 2); + } else { + // should only occur when mActivatedHandle is one of the 3 right-side + // handles, but this is a reasonable default if it isn't any of them (?) + infoXPosition = aX + aW; + } + + if (mActivatedHandle == mTopLeftHandle || mActivatedHandle == mTopHandle || + mActivatedHandle == mTopRightHandle) { + infoYPosition = aY; + } else if (mActivatedHandle == mLeftHandle || + mActivatedHandle == mRightHandle) { + infoYPosition = aY + (aH / 2); + } else { + // should only occur when mActivatedHandle is one of the 3 bottom-side + // handles, but this is a reasonable default if it isn't any of them (?) + infoYPosition = aY + aH; + } + + // Offset info box by 20 so it's not directly under the mouse cursor. + const int mouseCursorOffset = 20; + if (RefPtr<nsStyledElement> resizingInfoStyledElement = + nsStyledElement::FromNodeOrNull(mResizingInfo.get())) { + nsresult rv; + rv = CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction( + *resizingInfoStyledElement, *nsGkAtoms::left, + infoXPosition + mouseCursorOffset); + 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::SetCSSPropertyPixelsWithoutTransaction( + *resizingInfoStyledElement, *nsGkAtoms::top, + infoYPosition + mouseCursorOffset); + 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"); + } + + nsCOMPtr<nsIContent> textInfo = mResizingInfo->GetFirstChild(); + ErrorResult error; + if (textInfo) { + mResizingInfo->RemoveChild(*textInfo, error); + if (error.Failed()) { + NS_WARNING("nsINode::RemoveChild() failed"); + return error.StealNSResult(); + } + textInfo = nullptr; + } + + nsAutoString widthStr, heightStr, diffWidthStr, diffHeightStr; + widthStr.AppendInt(aW); + heightStr.AppendInt(aH); + int32_t diffWidth = aW - mResizedObjectWidth; + int32_t diffHeight = aH - mResizedObjectHeight; + if (diffWidth > 0) { + diffWidthStr.Assign('+'); + } + if (diffHeight > 0) { + diffHeightStr.Assign('+'); + } + diffWidthStr.AppendInt(diffWidth); + diffHeightStr.AppendInt(diffHeight); + + nsAutoString info(widthStr + u" x "_ns + heightStr + u" ("_ns + diffWidthStr + + u", "_ns + diffHeightStr + u")"_ns); + + RefPtr<Document> document = GetDocument(); + textInfo = document->CreateTextNode(info); + if (!textInfo) { + NS_WARNING("Document::CreateTextNode() failed"); + return NS_ERROR_FAILURE; + } + mResizingInfo->AppendChild(*textInfo, error); + if (error.Failed()) { + NS_WARNING("nsINode::AppendChild() failed"); + return error.StealNSResult(); + } + + nsresult rv = + mResizingInfo->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_class, true); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Element::UnsetAttr(nsGkAtoms::_class) failed"); + return rv; +} + +nsresult HTMLEditor::SetShadowPosition(Element& aShadowElement, + Element& aElement, int32_t aElementX, + int32_t aElementY) { + MOZ_ASSERT(&aShadowElement == mResizingShadow || + &aShadowElement == mPositioningShadow); + RefPtr<Element> handlingShadowElement = &aShadowElement == mResizingShadow + ? mResizingShadow.get() + : mPositioningShadow.get(); + + if (nsStyledElement* styledShadowElement = + nsStyledElement::FromNode(&aShadowElement)) { + // MOZ_KnownLive(*styledShadowElement): It's aShadowElement whose lifetime + // must be guaranteed by caller because of MOZ_CAN_RUN_SCRIPT method. + nsresult rv = SetAnonymousElementPositionWithoutTransaction( + MOZ_KnownLive(*styledShadowElement), aElementX, aElementY); + if (NS_FAILED(rv)) { + NS_WARNING( + "HTMLEditor::SetAnonymousElementPositionWithoutTransaction() " + "failed"); + return rv; + } + } + + if (!HTMLEditUtils::IsImage(&aElement)) { + return NS_OK; + } + + nsAutoString imageSource; + aElement.GetAttr(kNameSpaceID_None, nsGkAtoms::src, imageSource); + nsresult rv = aShadowElement.SetAttr(kNameSpaceID_None, nsGkAtoms::src, + imageSource, true); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + if (NS_FAILED(rv)) { + NS_WARNING("Element::SetAttr(nsGkAtoms::src) failed"); + return NS_ERROR_FAILURE; + } + return NS_WARN_IF(&aShadowElement != handlingShadowElement) ? NS_ERROR_FAILURE + : NS_OK; +} + +int32_t HTMLEditor::GetNewResizingIncrement(int32_t aX, int32_t aY, + ResizeAt aResizeAt) const { + int32_t result = 0; + if (!mPreserveRatio) { + switch (aResizeAt) { + case ResizeAt::eX: + case ResizeAt::eWidth: + result = aX - mOriginalX; + break; + case ResizeAt::eY: + case ResizeAt::eHeight: + result = aY - mOriginalY; + break; + default: + MOZ_ASSERT_UNREACHABLE("Invalid resizing request"); + } + return result; + } + + int32_t xi = (aX - mOriginalX) * mWidthIncrementFactor; + int32_t yi = (aY - mOriginalY) * mHeightIncrementFactor; + float objectSizeRatio = + ((float)mResizedObjectWidth) / ((float)mResizedObjectHeight); + result = (xi > yi) ? xi : yi; + switch (aResizeAt) { + case ResizeAt::eX: + case ResizeAt::eWidth: + if (result == yi) result = (int32_t)(((float)result) * objectSizeRatio); + result = (int32_t)(((float)result) * mWidthIncrementFactor); + break; + case ResizeAt::eY: + case ResizeAt::eHeight: + if (result == xi) result = (int32_t)(((float)result) / objectSizeRatio); + result = (int32_t)(((float)result) * mHeightIncrementFactor); + break; + } + return result; +} + +int32_t HTMLEditor::GetNewResizingX(int32_t aX, int32_t aY) { + int32_t resized = + mResizedObjectX + + GetNewResizingIncrement(aX, aY, ResizeAt::eX) * mXIncrementFactor; + int32_t max = mResizedObjectX + mResizedObjectWidth; + return std::min(resized, max); +} + +int32_t HTMLEditor::GetNewResizingY(int32_t aX, int32_t aY) { + int32_t resized = + mResizedObjectY + + GetNewResizingIncrement(aX, aY, ResizeAt::eY) * mYIncrementFactor; + int32_t max = mResizedObjectY + mResizedObjectHeight; + return std::min(resized, max); +} + +int32_t HTMLEditor::GetNewResizingWidth(int32_t aX, int32_t aY) { + int32_t resized = + mResizedObjectWidth + + GetNewResizingIncrement(aX, aY, ResizeAt::eWidth) * mWidthIncrementFactor; + return std::max(resized, 1); +} + +int32_t HTMLEditor::GetNewResizingHeight(int32_t aX, int32_t aY) { + int32_t resized = mResizedObjectHeight + + GetNewResizingIncrement(aX, aY, ResizeAt::eHeight) * + mHeightIncrementFactor; + return std::max(resized, 1); +} + +nsresult HTMLEditor::UpdateResizerOrGrabberPositionTo( + const CSSIntPoint& aClientPoint) { + if (mIsResizing) { + AutoEditActionDataSetter editActionData(*this, + EditAction::eResizingElement); + if (NS_WARN_IF(!editActionData.CanHandle())) { + return NS_ERROR_NOT_INITIALIZED; + } + + // we are resizing and the mouse pointer's position has changed + // we have to resdisplay the shadow + const int32_t newX = GetNewResizingX(aClientPoint.x, aClientPoint.y); + const int32_t newY = GetNewResizingY(aClientPoint.x, aClientPoint.y); + const int32_t newWidth = + GetNewResizingWidth(aClientPoint.x, aClientPoint.y); + const int32_t newHeight = + GetNewResizingHeight(aClientPoint.x, aClientPoint.y); + + if (RefPtr<nsStyledElement> resizingShadowStyledElement = + nsStyledElement::FromNodeOrNull(mResizingShadow.get())) { + nsresult rv; + rv = CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction( + *resizingShadowStyledElement, *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"); + rv = CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction( + *resizingShadowStyledElement, *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::SetCSSPropertyPixelsWithoutTransaction( + *resizingShadowStyledElement, *nsGkAtoms::width, newWidth); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::" + "width) destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::width) " + "failed, but ignored"); + rv = CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction( + *resizingShadowStyledElement, *nsGkAtoms::height, newHeight); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::" + "height) destroyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::height)" + " failed, but ignored"); + } + + nsresult rv = SetResizingInfoPosition(newX, newY, newWidth, newHeight); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "HTMLEditor::SetResizingInfoPosition() failed"); + return rv; + } + + AutoEditActionDataSetter editActionData(*this, EditAction::eMovingElement); + if (NS_WARN_IF(!editActionData.CanHandle())) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (mGrabberClicked) { + int32_t xThreshold = + LookAndFeel::GetInt(LookAndFeel::IntID::DragThresholdX, 1); + int32_t yThreshold = + LookAndFeel::GetInt(LookAndFeel::IntID::DragThresholdY, 1); + + if (DeprecatedAbs(aClientPoint.x - mOriginalX) * 2 >= xThreshold || + DeprecatedAbs(aClientPoint.y - mOriginalY) * 2 >= yThreshold) { + mGrabberClicked = false; + DebugOnly<nsresult> rvIgnored = StartMoving(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), + "HTMLEditor::StartMoving() failed, but ignored"); + } + } + if (mIsMoving) { + int32_t newX = mPositionedObjectX + aClientPoint.x - mOriginalX; + int32_t newY = mPositionedObjectY + aClientPoint.y - mOriginalY; + + // Maybe align newX and newY to the grid. + SnapToGrid(newX, newY); + + if (RefPtr<nsStyledElement> positioningShadowStyledElement = + nsStyledElement::FromNodeOrNull(mPositioningShadow.get())) { + nsresult rv; + rv = CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction( + *positioningShadowStyledElement, *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"); + rv = CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction( + *positioningShadowStyledElement, *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"); + } + } + return NS_OK; +} + +nsresult HTMLEditor::SetFinalSizeWithTransaction(int32_t aX, int32_t aY) { + if (!mResizedObject) { + // paranoia + return NS_OK; + } + + if (mActivatedHandle) { + DebugOnly<nsresult> rvIgnored = mActivatedHandle->UnsetAttr( + kNameSpaceID_None, nsGkAtoms::_moz_activated, true); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "Element::UnsetAttr(nsGkAtoms::_moz_activated) failed, but ignored"); + mActivatedHandle = nullptr; + } + + // 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 left = GetNewResizingX(aX, aY); + int32_t top = GetNewResizingY(aX, aY); + int32_t width = GetNewResizingWidth(aX, aY); + int32_t height = GetNewResizingHeight(aX, aY); + bool setWidth = + !mResizedObjectIsAbsolutelyPositioned || (width != mResizedObjectWidth); + bool setHeight = + !mResizedObjectIsAbsolutelyPositioned || (height != mResizedObjectHeight); + + int32_t x, y; + x = left - ((mResizedObjectIsAbsolutelyPositioned) + ? mResizedObjectBorderLeft + mResizedObjectMarginLeft + : 0); + y = top - ((mResizedObjectIsAbsolutelyPositioned) + ? mResizedObjectBorderTop + mResizedObjectMarginTop + : 0); + + // we want one transaction only from a user's point of view + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + RefPtr<Element> resizedElement(mResizedObject); + RefPtr<nsStyledElement> resizedStyleElement = + nsStyledElement::FromNodeOrNull(mResizedObject); + + if (mResizedObjectIsAbsolutelyPositioned && resizedStyleElement) { + if (setHeight) { + nsresult rv = CSSEditUtils::SetCSSPropertyPixelsWithTransaction( + *this, *resizedStyleElement, *nsGkAtoms::top, y); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::" + "top) destoyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::top) " + "failed, but ignored"); + } + if (setWidth) { + nsresult rv = CSSEditUtils::SetCSSPropertyPixelsWithTransaction( + *this, *resizedStyleElement, *nsGkAtoms::left, x); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::" + "left) destoyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::left) " + "failed, but ignored"); + } + } + if (IsCSSEnabled() || mResizedObjectIsAbsolutelyPositioned) { + if (setWidth && + resizedElement->HasAttr(kNameSpaceID_None, nsGkAtoms::width)) { + nsresult rv = + RemoveAttributeWithTransaction(*resizedElement, *nsGkAtoms::width); + if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { + NS_WARNING( + "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::width) " + "failed"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::width) " + "failed, but ignored"); + } + + if (setHeight && + resizedElement->HasAttr(kNameSpaceID_None, nsGkAtoms::height)) { + nsresult rv = + RemoveAttributeWithTransaction(*resizedElement, *nsGkAtoms::height); + if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { + NS_WARNING( + "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::height) " + "failed"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::height) " + "failed, but ignored"); + } + + if (resizedStyleElement) { + if (setWidth) { + nsresult rv = CSSEditUtils::SetCSSPropertyPixelsWithTransaction( + *this, *resizedStyleElement, *nsGkAtoms::width, width); + if (NS_FAILED(rv)) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::" + "width) destoyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixels(nsGkAtoms::width) " + "failed, but ignored"); + } + if (setHeight) { + nsresult rv = CSSEditUtils::SetCSSPropertyPixelsWithTransaction( + *this, *resizedStyleElement, *nsGkAtoms::height, height); + if (NS_FAILED(rv)) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::" + "height) destoyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixels(nsGkAtoms::height) " + "failed, but ignored"); + } + } + } else { + // we use HTML size and remove all equivalent CSS properties + + // we set the CSS width and height to remove it later, + // triggering an immediate reflow; otherwise, we have problems + // with asynchronous reflow + if (resizedStyleElement) { + if (setWidth) { + nsresult rv = CSSEditUtils::SetCSSPropertyPixelsWithTransaction( + *this, *resizedStyleElement, *nsGkAtoms::width, width); + if (NS_FAILED(rv)) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::" + "width) destoyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::" + "width) failed, but ignored"); + } + if (setHeight) { + nsresult rv = CSSEditUtils::SetCSSPropertyPixelsWithTransaction( + *this, *resizedStyleElement, *nsGkAtoms::height, height); + if (NS_FAILED(rv)) { + NS_WARNING( + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::" + "height) destoyed the editor"); + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "CSSEditUtils::SetCSSPropertyPixelsWithTransaction(nsGkAtoms::" + "height) failed, but ignored"); + } + } + if (setWidth) { + nsAutoString w; + w.AppendInt(width); + DebugOnly<nsresult> rvIgnored = + SetAttributeWithTransaction(*resizedElement, *nsGkAtoms::width, w); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "EditorBase::SetAttributeWithTransaction(nsGkAtoms::width) " + "failed, but ignored"); + } + if (setHeight) { + nsAutoString h; + h.AppendInt(height); + DebugOnly<nsresult> rvIgnored = + SetAttributeWithTransaction(*resizedElement, *nsGkAtoms::height, h); + if (NS_WARN_IF(Destroyed())) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rvIgnored), + "EditorBase::SetAttributeWithTransaction(nsGkAtoms::height) " + "failed, but ignored"); + } + + if (resizedStyleElement) { + if (setWidth) { + nsresult rv = CSSEditUtils::RemoveCSSPropertyWithTransaction( + *this, *resizedStyleElement, *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"); + } + if (setHeight) { + nsresult rv = CSSEditUtils::RemoveCSSPropertyWithTransaction( + *this, *resizedStyleElement, *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"); + } + } + } + + // keep track of that size + mResizedObjectWidth = width; + mResizedObjectHeight = height; + + nsresult rv = RefreshResizersInternal(); + if (rv == NS_ERROR_EDITOR_DESTROYED) { + return NS_ERROR_EDITOR_DESTROYED; + } + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "HTMLEditor::RefreshResizersInternal() failed, but ignored"); + return NS_OK; +} + +NS_IMETHODIMP HTMLEditor::GetObjectResizingEnabled( + bool* aIsObjectResizingEnabled) { + *aIsObjectResizingEnabled = IsObjectResizerEnabled(); + return NS_OK; +} + +NS_IMETHODIMP HTMLEditor::SetObjectResizingEnabled( + bool aObjectResizingEnabled) { + EnableObjectResizer(aObjectResizingEnabled); + return NS_OK; +} + +#undef kTopLeft +#undef kTop +#undef kTopRight +#undef kLeft +#undef kRight +#undef kBottomLeft +#undef kBottom +#undef kBottomRight + +} // namespace mozilla |