summaryrefslogtreecommitdiffstats
path: root/editor/libeditor/HTMLEditorObjectResizer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'editor/libeditor/HTMLEditorObjectResizer.cpp')
-rw-r--r--editor/libeditor/HTMLEditorObjectResizer.cpp1493
1 files changed, 1493 insertions, 0 deletions
diff --git a/editor/libeditor/HTMLEditorObjectResizer.cpp b/editor/libeditor/HTMLEditorObjectResizer.cpp
new file mode 100644
index 0000000000..8a11c8487c
--- /dev/null
+++ b/editor/libeditor/HTMLEditorObjectResizer.cpp
@@ -0,0 +1,1493 @@
+/* -*- 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 = 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(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(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(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