1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "mozilla/HTMLEditor.h"
7 #include "HTMLEditorObjectResizerUtils.h"
8 
9 #include "HTMLEditUtils.h"
10 #include "mozilla/DebugOnly.h"
11 #include "mozilla/EditorUtils.h"
12 #include "mozilla/LookAndFeel.h"
13 #include "mozilla/MathAlgorithms.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/mozalloc.h"
16 #include "nsAString.h"
17 #include "nsAlgorithm.h"
18 #include "nsCOMPtr.h"
19 #include "nsDebug.h"
20 #include "nsError.h"
21 #include "nsGkAtoms.h"
22 #include "nsIAtom.h"
23 #include "nsIContent.h"
24 #include "nsID.h"
25 #include "nsIDOMDocument.h"
26 #include "nsIDOMElement.h"
27 #include "nsIDOMEvent.h"
28 #include "nsIDOMEventTarget.h"
29 #include "nsIDOMMouseEvent.h"
30 #include "nsIDOMNode.h"
31 #include "nsIDOMText.h"
32 #include "nsIDocument.h"
33 #include "nsIEditor.h"
34 #include "nsIHTMLObjectResizeListener.h"
35 #include "nsIHTMLObjectResizer.h"
36 #include "nsIPresShell.h"
37 #include "nsISupportsUtils.h"
38 #include "nsPIDOMWindow.h"
39 #include "nsReadableUtils.h"
40 #include "nsString.h"
41 #include "nsStringFwd.h"
42 #include "nsSubstringTuple.h"
43 #include "nscore.h"
44 #include <algorithm>
45 
46 class nsISelection;
47 
48 namespace mozilla {
49 
50 using namespace dom;
51 
52 /******************************************************************************
53  * mozilla::DocumentResizeEventListener
54  ******************************************************************************/
55 
NS_IMPL_ISUPPORTS(DocumentResizeEventListener,nsIDOMEventListener)56 NS_IMPL_ISUPPORTS(DocumentResizeEventListener, nsIDOMEventListener)
57 
58 DocumentResizeEventListener::DocumentResizeEventListener(nsIHTMLEditor* aEditor)
59 {
60   mEditor = do_GetWeakReference(aEditor);
61 }
62 
63 NS_IMETHODIMP
HandleEvent(nsIDOMEvent * aMouseEvent)64 DocumentResizeEventListener::HandleEvent(nsIDOMEvent* aMouseEvent)
65 {
66   nsCOMPtr<nsIHTMLObjectResizer> objectResizer = do_QueryReferent(mEditor);
67   if (objectResizer) {
68     return objectResizer->RefreshResizers();
69   }
70   return NS_OK;
71 }
72 
73 /******************************************************************************
74  * mozilla::ResizerSelectionListener
75  ******************************************************************************/
76 
NS_IMPL_ISUPPORTS(ResizerSelectionListener,nsISelectionListener)77 NS_IMPL_ISUPPORTS(ResizerSelectionListener, nsISelectionListener)
78 
79 ResizerSelectionListener::ResizerSelectionListener(nsIHTMLEditor* aEditor)
80 {
81   mEditor = do_GetWeakReference(aEditor);
82 }
83 
84 NS_IMETHODIMP
NotifySelectionChanged(nsIDOMDocument * aDOMDocument,nsISelection * aSelection,int16_t aReason)85 ResizerSelectionListener::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
86                                                  nsISelection* aSelection,
87                                                  int16_t aReason)
88 {
89   if ((aReason & (nsISelectionListener::MOUSEDOWN_REASON |
90                   nsISelectionListener::KEYPRESS_REASON |
91                   nsISelectionListener::SELECTALL_REASON)) && aSelection) {
92     // the selection changed and we need to check if we have to
93     // hide and/or redisplay resizing handles
94     nsCOMPtr<nsIHTMLEditor> editor = do_QueryReferent(mEditor);
95     if (editor) {
96       editor->CheckSelectionStateForAnonymousButtons(aSelection);
97     }
98   }
99 
100   return NS_OK;
101 }
102 
103 /******************************************************************************
104  * mozilla::ResizerMouseMotionListener
105  ******************************************************************************/
106 
NS_IMPL_ISUPPORTS(ResizerMouseMotionListener,nsIDOMEventListener)107 NS_IMPL_ISUPPORTS(ResizerMouseMotionListener, nsIDOMEventListener)
108 
109 ResizerMouseMotionListener::ResizerMouseMotionListener(nsIHTMLEditor* aEditor)
110 {
111   mEditor = do_GetWeakReference(aEditor);
112 }
113 
114 NS_IMETHODIMP
HandleEvent(nsIDOMEvent * aMouseEvent)115 ResizerMouseMotionListener::HandleEvent(nsIDOMEvent* aMouseEvent)
116 {
117   nsCOMPtr<nsIDOMMouseEvent> mouseEvent ( do_QueryInterface(aMouseEvent) );
118   if (!mouseEvent) {
119     //non-ui event passed in.  bad things.
120     return NS_OK;
121   }
122 
123   // Don't do anything special if not an HTML object resizer editor
124   nsCOMPtr<nsIHTMLObjectResizer> objectResizer = do_QueryReferent(mEditor);
125   if (objectResizer) {
126     // check if we have to redisplay a resizing shadow
127     objectResizer->MouseMove(aMouseEvent);
128   }
129 
130   return NS_OK;
131 }
132 
133 /******************************************************************************
134  * mozilla::HTMLEditor
135  ******************************************************************************/
136 
137 already_AddRefed<Element>
CreateResizer(int16_t aLocation,nsIDOMNode * aParentNode)138 HTMLEditor::CreateResizer(int16_t aLocation,
139                           nsIDOMNode* aParentNode)
140 {
141   nsCOMPtr<nsIDOMElement> retDOM;
142   nsresult rv = CreateAnonymousElement(NS_LITERAL_STRING("span"),
143                                        aParentNode,
144                                        NS_LITERAL_STRING("mozResizer"),
145                                        false,
146                                        getter_AddRefs(retDOM));
147 
148   NS_ENSURE_SUCCESS(rv, nullptr);
149   NS_ENSURE_TRUE(retDOM, nullptr);
150 
151   // add the mouse listener so we can detect a click on a resizer
152   nsCOMPtr<nsIDOMEventTarget> evtTarget = do_QueryInterface(retDOM);
153   evtTarget->AddEventListener(NS_LITERAL_STRING("mousedown"), mEventListener,
154                               true);
155 
156   nsAutoString locationStr;
157   switch (aLocation) {
158     case nsIHTMLObjectResizer::eTopLeft:
159       locationStr = kTopLeft;
160       break;
161     case nsIHTMLObjectResizer::eTop:
162       locationStr = kTop;
163       break;
164     case nsIHTMLObjectResizer::eTopRight:
165       locationStr = kTopRight;
166       break;
167 
168     case nsIHTMLObjectResizer::eLeft:
169       locationStr = kLeft;
170       break;
171     case nsIHTMLObjectResizer::eRight:
172       locationStr = kRight;
173       break;
174 
175     case nsIHTMLObjectResizer::eBottomLeft:
176       locationStr = kBottomLeft;
177       break;
178     case nsIHTMLObjectResizer::eBottom:
179       locationStr = kBottom;
180       break;
181     case nsIHTMLObjectResizer::eBottomRight:
182       locationStr = kBottomRight;
183       break;
184   }
185 
186   nsCOMPtr<Element> ret = do_QueryInterface(retDOM);
187   rv = ret->SetAttr(kNameSpaceID_None, nsGkAtoms::anonlocation, locationStr,
188                     true);
189   NS_ENSURE_SUCCESS(rv, nullptr);
190   return ret.forget();
191 }
192 
193 already_AddRefed<Element>
CreateShadow(nsIDOMNode * aParentNode,nsIDOMElement * aOriginalObject)194 HTMLEditor::CreateShadow(nsIDOMNode* aParentNode,
195                          nsIDOMElement* aOriginalObject)
196 {
197   // let's create an image through the element factory
198   nsAutoString name;
199   if (HTMLEditUtils::IsImage(aOriginalObject)) {
200     name.AssignLiteral("img");
201   } else {
202     name.AssignLiteral("span");
203   }
204   nsCOMPtr<nsIDOMElement> retDOM;
205   CreateAnonymousElement(name, aParentNode,
206                          NS_LITERAL_STRING("mozResizingShadow"), true,
207                          getter_AddRefs(retDOM));
208 
209   NS_ENSURE_TRUE(retDOM, nullptr);
210 
211   nsCOMPtr<Element> ret = do_QueryInterface(retDOM);
212   return ret.forget();
213 }
214 
215 already_AddRefed<Element>
CreateResizingInfo(nsIDOMNode * aParentNode)216 HTMLEditor::CreateResizingInfo(nsIDOMNode* aParentNode)
217 {
218   // let's create an info box through the element factory
219   nsCOMPtr<nsIDOMElement> retDOM;
220   CreateAnonymousElement(NS_LITERAL_STRING("span"), aParentNode,
221                          NS_LITERAL_STRING("mozResizingInfo"), true,
222                          getter_AddRefs(retDOM));
223 
224   nsCOMPtr<Element> ret = do_QueryInterface(retDOM);
225   return ret.forget();
226 }
227 
228 nsresult
SetAllResizersPosition()229 HTMLEditor::SetAllResizersPosition()
230 {
231   NS_ENSURE_TRUE(mTopLeftHandle, NS_ERROR_FAILURE);
232 
233   int32_t x = mResizedObjectX;
234   int32_t y = mResizedObjectY;
235   int32_t w = mResizedObjectWidth;
236   int32_t h = mResizedObjectHeight;
237 
238   // now let's place all the resizers around the image
239 
240   // get the size of resizers
241   nsAutoString value;
242   float resizerWidth, resizerHeight;
243   nsCOMPtr<nsIAtom> dummyUnit;
244   mCSSEditUtils->GetComputedProperty(*mTopLeftHandle, *nsGkAtoms::width,
245                                      value);
246   mCSSEditUtils->ParseLength(value, &resizerWidth, getter_AddRefs(dummyUnit));
247   mCSSEditUtils->GetComputedProperty(*mTopLeftHandle, *nsGkAtoms::height,
248                                      value);
249   mCSSEditUtils->ParseLength(value, &resizerHeight, getter_AddRefs(dummyUnit));
250 
251   int32_t rw  = (int32_t)((resizerWidth + 1) / 2);
252   int32_t rh =  (int32_t)((resizerHeight+ 1) / 2);
253 
254   SetAnonymousElementPosition(x-rw,     y-rh, static_cast<nsIDOMElement*>(GetAsDOMNode(mTopLeftHandle)));
255   SetAnonymousElementPosition(x+w/2-rw, y-rh, static_cast<nsIDOMElement*>(GetAsDOMNode(mTopHandle)));
256   SetAnonymousElementPosition(x+w-rw-1, y-rh, static_cast<nsIDOMElement*>(GetAsDOMNode(mTopRightHandle)));
257 
258   SetAnonymousElementPosition(x-rw,     y+h/2-rh, static_cast<nsIDOMElement*>(GetAsDOMNode(mLeftHandle)));
259   SetAnonymousElementPosition(x+w-rw-1, y+h/2-rh, static_cast<nsIDOMElement*>(GetAsDOMNode(mRightHandle)));
260 
261   SetAnonymousElementPosition(x-rw,     y+h-rh-1, static_cast<nsIDOMElement*>(GetAsDOMNode(mBottomLeftHandle)));
262   SetAnonymousElementPosition(x+w/2-rw, y+h-rh-1, static_cast<nsIDOMElement*>(GetAsDOMNode(mBottomHandle)));
263   SetAnonymousElementPosition(x+w-rw-1, y+h-rh-1, static_cast<nsIDOMElement*>(GetAsDOMNode(mBottomRightHandle)));
264 
265   return NS_OK;
266 }
267 
268 NS_IMETHODIMP
RefreshResizers()269 HTMLEditor::RefreshResizers()
270 {
271   // nothing to do if resizers are not displayed...
272   NS_ENSURE_TRUE(mResizedObject, NS_OK);
273 
274   nsresult rv =
275     GetPositionAndDimensions(
276       static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)),
277       mResizedObjectX,
278       mResizedObjectY,
279       mResizedObjectWidth,
280       mResizedObjectHeight,
281       mResizedObjectBorderLeft,
282       mResizedObjectBorderTop,
283       mResizedObjectMarginLeft,
284       mResizedObjectMarginTop);
285 
286   NS_ENSURE_SUCCESS(rv, rv);
287   rv = SetAllResizersPosition();
288   NS_ENSURE_SUCCESS(rv, rv);
289   return SetShadowPosition(mResizingShadow, mResizedObject,
290                            mResizedObjectX, mResizedObjectY);
291 }
292 
293 NS_IMETHODIMP
ShowResizers(nsIDOMElement * aResizedElement)294 HTMLEditor::ShowResizers(nsIDOMElement* aResizedElement)
295 {
296   nsresult rv = ShowResizersInner(aResizedElement);
297   if (NS_FAILED(rv)) {
298     HideResizers();
299   }
300   return rv;
301 }
302 
303 nsresult
ShowResizersInner(nsIDOMElement * aResizedElement)304 HTMLEditor::ShowResizersInner(nsIDOMElement* aResizedElement)
305 {
306   NS_ENSURE_ARG_POINTER(aResizedElement);
307 
308   nsCOMPtr<nsIDOMNode> parentNode;
309   nsresult rv = aResizedElement->GetParentNode(getter_AddRefs(parentNode));
310   NS_ENSURE_SUCCESS(rv, rv);
311 
312   if (mResizedObject) {
313     NS_ERROR("call HideResizers first");
314     return NS_ERROR_UNEXPECTED;
315   }
316 
317   nsCOMPtr<nsINode> resizedNode = do_QueryInterface(aResizedElement);
318   if (NS_WARN_IF(!IsDescendantOfEditorRoot(resizedNode))) {
319     return NS_ERROR_UNEXPECTED;
320   }
321 
322   mResizedObject = do_QueryInterface(aResizedElement);
323   NS_ENSURE_STATE(mResizedObject);
324 
325   // The resizers and the shadow will be anonymous siblings of the element.
326   mTopLeftHandle = CreateResizer(nsIHTMLObjectResizer::eTopLeft, parentNode);
327   NS_ENSURE_TRUE(mTopLeftHandle, NS_ERROR_FAILURE);
328   mTopHandle = CreateResizer(nsIHTMLObjectResizer::eTop, parentNode);
329   NS_ENSURE_TRUE(mTopHandle, NS_ERROR_FAILURE);
330   mTopRightHandle = CreateResizer(nsIHTMLObjectResizer::eTopRight, parentNode);
331   NS_ENSURE_TRUE(mTopRightHandle, NS_ERROR_FAILURE);
332 
333   mLeftHandle = CreateResizer(nsIHTMLObjectResizer::eLeft, parentNode);
334   NS_ENSURE_TRUE(mLeftHandle, NS_ERROR_FAILURE);
335   mRightHandle = CreateResizer(nsIHTMLObjectResizer::eRight, parentNode);
336   NS_ENSURE_TRUE(mRightHandle, NS_ERROR_FAILURE);
337 
338   mBottomLeftHandle = CreateResizer(nsIHTMLObjectResizer::eBottomLeft,  parentNode);
339   NS_ENSURE_TRUE(mBottomLeftHandle, NS_ERROR_FAILURE);
340   mBottomHandle = CreateResizer(nsIHTMLObjectResizer::eBottom,      parentNode);
341   NS_ENSURE_TRUE(mBottomHandle, NS_ERROR_FAILURE);
342   mBottomRightHandle = CreateResizer(nsIHTMLObjectResizer::eBottomRight, parentNode);
343   NS_ENSURE_TRUE(mBottomRightHandle, NS_ERROR_FAILURE);
344 
345   rv = GetPositionAndDimensions(aResizedElement,
346                                 mResizedObjectX,
347                                 mResizedObjectY,
348                                 mResizedObjectWidth,
349                                 mResizedObjectHeight,
350                                 mResizedObjectBorderLeft,
351                                 mResizedObjectBorderTop,
352                                 mResizedObjectMarginLeft,
353                                 mResizedObjectMarginTop);
354   NS_ENSURE_SUCCESS(rv, rv);
355 
356   // and let's set their absolute positions in the document
357   rv = SetAllResizersPosition();
358   NS_ENSURE_SUCCESS(rv, rv);
359 
360   // now, let's create the resizing shadow
361   mResizingShadow = CreateShadow(parentNode, aResizedElement);
362   NS_ENSURE_TRUE(mResizingShadow, NS_ERROR_FAILURE);
363   // and set its position
364   rv = SetShadowPosition(mResizingShadow, mResizedObject,
365                          mResizedObjectX, mResizedObjectY);
366   NS_ENSURE_SUCCESS(rv, rv);
367 
368   // and then the resizing info tooltip
369   mResizingInfo = CreateResizingInfo(parentNode);
370   NS_ENSURE_TRUE(mResizingInfo, NS_ERROR_FAILURE);
371 
372   // and listen to the "resize" event on the window first, get the
373   // window from the document...
374   nsCOMPtr<nsIDocument> doc = GetDocument();
375   NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
376 
377   nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(doc->GetWindow());
378   if (!target) {
379     return NS_ERROR_NULL_POINTER;
380   }
381 
382   mResizeEventListenerP = new DocumentResizeEventListener(this);
383   if (!mResizeEventListenerP) {
384     return NS_ERROR_OUT_OF_MEMORY;
385   }
386   rv = target->AddEventListener(NS_LITERAL_STRING("resize"),
387                                 mResizeEventListenerP, false);
388   // XXX Even when it failed to add event listener, should we need to set
389   //     _moz_resizing attribute?
390   aResizedElement->SetAttribute(NS_LITERAL_STRING("_moz_resizing"), NS_LITERAL_STRING("true"));
391   return rv;
392 }
393 
394 NS_IMETHODIMP
HideResizers()395 HTMLEditor::HideResizers()
396 {
397   NS_ENSURE_TRUE(mResizedObject, NS_OK);
398 
399   // get the presshell's document observer interface.
400   nsCOMPtr<nsIPresShell> ps = GetPresShell();
401   // We allow the pres shell to be null; when it is, we presume there
402   // are no document observers to notify, but we still want to
403   // UnbindFromTree.
404 
405   nsCOMPtr<nsIContent> parentContent;
406 
407   if (mTopLeftHandle) {
408     parentContent = mTopLeftHandle->GetParent();
409   }
410 
411   NS_NAMED_LITERAL_STRING(mousedown, "mousedown");
412 
413   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
414                              mTopLeftHandle, parentContent, ps);
415   mTopLeftHandle = nullptr;
416 
417   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
418                              mTopHandle, parentContent, ps);
419   mTopHandle = nullptr;
420 
421   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
422                              mTopRightHandle, parentContent, ps);
423   mTopRightHandle = nullptr;
424 
425   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
426                              mLeftHandle, parentContent, ps);
427   mLeftHandle = nullptr;
428 
429   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
430                              mRightHandle, parentContent, ps);
431   mRightHandle = nullptr;
432 
433   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
434                              mBottomLeftHandle, parentContent, ps);
435   mBottomLeftHandle = nullptr;
436 
437   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
438                              mBottomHandle, parentContent, ps);
439   mBottomHandle = nullptr;
440 
441   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
442                              mBottomRightHandle, parentContent, ps);
443   mBottomRightHandle = nullptr;
444 
445   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
446                              mResizingShadow, parentContent, ps);
447   mResizingShadow = nullptr;
448 
449   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
450                              mResizingInfo, parentContent, ps);
451   mResizingInfo = nullptr;
452 
453   if (mActivatedHandle) {
454     mActivatedHandle->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_moz_activated,
455                                 true);
456     mActivatedHandle = nullptr;
457   }
458 
459   // don't forget to remove the listeners !
460 
461   nsCOMPtr<nsIDOMEventTarget> target = GetDOMEventTarget();
462 
463   if (target && mMouseMotionListenerP) {
464     DebugOnly<nsresult> rv =
465       target->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
466                                   mMouseMotionListenerP, true);
467     NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove mouse motion listener");
468   }
469   mMouseMotionListenerP = nullptr;
470 
471   nsCOMPtr<nsIDocument> doc = GetDocument();
472   if (!doc) {
473     return NS_ERROR_NULL_POINTER;
474   }
475   target = do_QueryInterface(doc->GetWindow());
476   if (!target) {
477     return NS_ERROR_NULL_POINTER;
478   }
479 
480   if (mResizeEventListenerP) {
481     DebugOnly<nsresult> rv =
482       target->RemoveEventListener(NS_LITERAL_STRING("resize"),
483                                   mResizeEventListenerP, false);
484     NS_ASSERTION(NS_SUCCEEDED(rv), "failed to remove resize event listener");
485   }
486   mResizeEventListenerP = nullptr;
487 
488   mResizedObject->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_moz_resizing, true);
489   mResizedObject = nullptr;
490 
491   return NS_OK;
492 }
493 
494 void
HideShadowAndInfo()495 HTMLEditor::HideShadowAndInfo()
496 {
497   if (mResizingShadow) {
498     mResizingShadow->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
499                              NS_LITERAL_STRING("hidden"), true);
500   }
501   if (mResizingInfo) {
502     mResizingInfo->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
503                            NS_LITERAL_STRING("hidden"), true);
504   }
505 }
506 
507 nsresult
StartResizing(nsIDOMElement * aHandle)508 HTMLEditor::StartResizing(nsIDOMElement* aHandle)
509 {
510   // First notify the listeners if any
511   for (auto& listener : mObjectResizeEventListeners) {
512     listener->OnStartResizing(static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)));
513   }
514 
515   mIsResizing = true;
516   mActivatedHandle = do_QueryInterface(aHandle);
517   NS_ENSURE_STATE(mActivatedHandle || !aHandle);
518   mActivatedHandle->SetAttr(kNameSpaceID_None, nsGkAtoms::_moz_activated,
519                             NS_LITERAL_STRING("true"), true);
520 
521   // do we want to preserve ratio or not?
522   bool preserveRatio = HTMLEditUtils::IsImage(mResizedObject) &&
523     Preferences::GetBool("editor.resizing.preserve_ratio", true);
524 
525   // the way we change the position/size of the shadow depends on
526   // the handle
527   nsAutoString locationStr;
528   aHandle->GetAttribute(NS_LITERAL_STRING("anonlocation"), locationStr);
529   if (locationStr.Equals(kTopLeft)) {
530     SetResizeIncrements(1, 1, -1, -1, preserveRatio);
531   } else if (locationStr.Equals(kTop)) {
532     SetResizeIncrements(0, 1, 0, -1, false);
533   } else if (locationStr.Equals(kTopRight)) {
534     SetResizeIncrements(0, 1, 1, -1, preserveRatio);
535   } else if (locationStr.Equals(kLeft)) {
536     SetResizeIncrements(1, 0, -1, 0, false);
537   } else if (locationStr.Equals(kRight)) {
538     SetResizeIncrements(0, 0, 1, 0, false);
539   } else if (locationStr.Equals(kBottomLeft)) {
540     SetResizeIncrements(1, 0, -1, 1, preserveRatio);
541   } else if (locationStr.Equals(kBottom)) {
542     SetResizeIncrements(0, 0, 0, 1, false);
543   } else if (locationStr.Equals(kBottomRight)) {
544     SetResizeIncrements(0, 0, 1, 1, preserveRatio);
545   }
546 
547   // make the shadow appear
548   mResizingShadow->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_class, true);
549 
550   // position it
551   mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::width,
552                                       mResizedObjectWidth);
553   mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::height,
554                                       mResizedObjectHeight);
555 
556   // add a mouse move listener to the editor
557   nsresult result = NS_OK;
558   if (!mMouseMotionListenerP) {
559     mMouseMotionListenerP = new ResizerMouseMotionListener(this);
560     if (!mMouseMotionListenerP) {
561       return NS_ERROR_OUT_OF_MEMORY;
562     }
563 
564     nsCOMPtr<nsIDOMEventTarget> target = GetDOMEventTarget();
565     NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
566 
567     result = target->AddEventListener(NS_LITERAL_STRING("mousemove"),
568                                       mMouseMotionListenerP, true);
569     NS_ASSERTION(NS_SUCCEEDED(result),
570                  "failed to register mouse motion listener");
571   }
572   return result;
573 }
574 
575 NS_IMETHODIMP
MouseDown(int32_t aClientX,int32_t aClientY,nsIDOMElement * aTarget,nsIDOMEvent * aEvent)576 HTMLEditor::MouseDown(int32_t aClientX,
577                       int32_t aClientY,
578                       nsIDOMElement* aTarget,
579                       nsIDOMEvent* aEvent)
580 {
581   bool anonElement = false;
582   if (aTarget && NS_SUCCEEDED(aTarget->HasAttribute(NS_LITERAL_STRING("_moz_anonclass"), &anonElement)))
583     // we caught a click on an anonymous element
584     if (anonElement) {
585       nsAutoString anonclass;
586       nsresult rv =
587         aTarget->GetAttribute(NS_LITERAL_STRING("_moz_anonclass"), anonclass);
588       NS_ENSURE_SUCCESS(rv, rv);
589       if (anonclass.EqualsLiteral("mozResizer")) {
590         // and that element is a resizer, let's start resizing!
591         aEvent->PreventDefault();
592 
593         mOriginalX = aClientX;
594         mOriginalY = aClientY;
595         return StartResizing(aTarget);
596       }
597       if (anonclass.EqualsLiteral("mozGrabber")) {
598         // and that element is a grabber, let's start moving the element!
599         mOriginalX = aClientX;
600         mOriginalY = aClientY;
601         return GrabberClicked();
602       }
603     }
604   return NS_OK;
605 }
606 
607 NS_IMETHODIMP
MouseUp(int32_t aClientX,int32_t aClientY,nsIDOMElement * aTarget)608 HTMLEditor::MouseUp(int32_t aClientX,
609                     int32_t aClientY,
610                     nsIDOMElement* aTarget)
611 {
612   if (mIsResizing) {
613     // we are resizing and release the mouse button, so let's
614     // end the resizing process
615     mIsResizing = false;
616     HideShadowAndInfo();
617     SetFinalSize(aClientX, aClientY);
618   } else if (mIsMoving || mGrabberClicked) {
619     if (mIsMoving) {
620       mPositioningShadow->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
621                                   NS_LITERAL_STRING("hidden"), true);
622       SetFinalPosition(aClientX, aClientY);
623     }
624     if (mGrabberClicked) {
625       EndMoving();
626     }
627   }
628   return NS_OK;
629 }
630 
631 void
SetResizeIncrements(int32_t aX,int32_t aY,int32_t aW,int32_t aH,bool aPreserveRatio)632 HTMLEditor::SetResizeIncrements(int32_t aX,
633                                 int32_t aY,
634                                 int32_t aW,
635                                 int32_t aH,
636                                 bool aPreserveRatio)
637 {
638   mXIncrementFactor = aX;
639   mYIncrementFactor = aY;
640   mWidthIncrementFactor = aW;
641   mHeightIncrementFactor = aH;
642   mPreserveRatio = aPreserveRatio;
643 }
644 
645 nsresult
SetResizingInfoPosition(int32_t aX,int32_t aY,int32_t aW,int32_t aH)646 HTMLEditor::SetResizingInfoPosition(int32_t aX,
647                                     int32_t aY,
648                                     int32_t aW,
649                                     int32_t aH)
650 {
651   nsCOMPtr<nsIDOMDocument> domdoc = GetDOMDocument();
652 
653   // Determine the position of the resizing info box based upon the new
654   // position and size of the element (aX, aY, aW, aH), and which
655   // resizer is the "activated handle".  For example, place the resizing
656   // info box at the bottom-right corner of the new element, if the element
657   // is being resized by the bottom-right resizer.
658   int32_t infoXPosition;
659   int32_t infoYPosition;
660 
661   if (mActivatedHandle == mTopLeftHandle ||
662       mActivatedHandle == mLeftHandle ||
663       mActivatedHandle == mBottomLeftHandle) {
664     infoXPosition = aX;
665   } else if (mActivatedHandle == mTopHandle ||
666              mActivatedHandle == mBottomHandle) {
667     infoXPosition = aX + (aW / 2);
668   } else {
669     // should only occur when mActivatedHandle is one of the 3 right-side
670     // handles, but this is a reasonable default if it isn't any of them (?)
671     infoXPosition = aX + aW;
672   }
673 
674   if (mActivatedHandle == mTopLeftHandle ||
675       mActivatedHandle == mTopHandle ||
676       mActivatedHandle == mTopRightHandle) {
677     infoYPosition = aY;
678   } else if (mActivatedHandle == mLeftHandle ||
679              mActivatedHandle == mRightHandle) {
680     infoYPosition = aY + (aH / 2);
681   } else {
682     // should only occur when mActivatedHandle is one of the 3 bottom-side
683     // handles, but this is a reasonable default if it isn't any of them (?)
684     infoYPosition = aY + aH;
685   }
686 
687   // Offset info box by 20 so it's not directly under the mouse cursor.
688   const int mouseCursorOffset = 20;
689   mCSSEditUtils->SetCSSPropertyPixels(*mResizingInfo, *nsGkAtoms::left,
690                                       infoXPosition + mouseCursorOffset);
691   mCSSEditUtils->SetCSSPropertyPixels(*mResizingInfo, *nsGkAtoms::top,
692                                       infoYPosition + mouseCursorOffset);
693 
694   nsCOMPtr<nsIContent> textInfo = mResizingInfo->GetFirstChild();
695   ErrorResult erv;
696   if (textInfo) {
697     mResizingInfo->RemoveChild(*textInfo, erv);
698     NS_ENSURE_TRUE(!erv.Failed(), erv.StealNSResult());
699     textInfo = nullptr;
700   }
701 
702   nsAutoString widthStr, heightStr, diffWidthStr, diffHeightStr;
703   widthStr.AppendInt(aW);
704   heightStr.AppendInt(aH);
705   int32_t diffWidth  = aW - mResizedObjectWidth;
706   int32_t diffHeight = aH - mResizedObjectHeight;
707   if (diffWidth > 0) {
708     diffWidthStr.Assign('+');
709   }
710   if (diffHeight > 0) {
711     diffHeightStr.Assign('+');
712   }
713   diffWidthStr.AppendInt(diffWidth);
714   diffHeightStr.AppendInt(diffHeight);
715 
716   nsAutoString info(widthStr + NS_LITERAL_STRING(" x ") + heightStr +
717                     NS_LITERAL_STRING(" (") + diffWidthStr +
718                     NS_LITERAL_STRING(", ") + diffHeightStr +
719                     NS_LITERAL_STRING(")"));
720 
721   nsCOMPtr<nsIDOMText> nodeAsText;
722   nsresult rv = domdoc->CreateTextNode(info, getter_AddRefs(nodeAsText));
723   NS_ENSURE_SUCCESS(rv, rv);
724   textInfo = do_QueryInterface(nodeAsText);
725   mResizingInfo->AppendChild(*textInfo, erv);
726   NS_ENSURE_TRUE(!erv.Failed(), erv.StealNSResult());
727 
728   return mResizingInfo->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_class, true);
729 }
730 
731 nsresult
SetShadowPosition(Element * aShadow,Element * aOriginalObject,int32_t aOriginalObjectX,int32_t aOriginalObjectY)732 HTMLEditor::SetShadowPosition(Element* aShadow,
733                               Element* aOriginalObject,
734                               int32_t aOriginalObjectX,
735                               int32_t aOriginalObjectY)
736 {
737   SetAnonymousElementPosition(aOriginalObjectX, aOriginalObjectY, static_cast<nsIDOMElement*>(GetAsDOMNode(aShadow)));
738 
739   if (HTMLEditUtils::IsImage(aOriginalObject)) {
740     nsAutoString imageSource;
741     aOriginalObject->GetAttr(kNameSpaceID_None, nsGkAtoms::src, imageSource);
742     nsresult rv = aShadow->SetAttr(kNameSpaceID_None, nsGkAtoms::src,
743                                    imageSource, true);
744     NS_ENSURE_SUCCESS(rv, rv);
745   }
746   return NS_OK;
747 }
748 
749 int32_t
GetNewResizingIncrement(int32_t aX,int32_t aY,int32_t aID)750 HTMLEditor::GetNewResizingIncrement(int32_t aX,
751                                     int32_t aY,
752                                     int32_t aID)
753 {
754   int32_t result = 0;
755   if (!mPreserveRatio) {
756     switch (aID) {
757       case kX:
758       case kWidth:
759         result = aX - mOriginalX;
760         break;
761       case kY:
762       case kHeight:
763         result = aY - mOriginalY;
764         break;
765     }
766     return result;
767   }
768 
769   int32_t xi = (aX - mOriginalX) * mWidthIncrementFactor;
770   int32_t yi = (aY - mOriginalY) * mHeightIncrementFactor;
771   float objectSizeRatio =
772               ((float)mResizedObjectWidth) / ((float)mResizedObjectHeight);
773   result = (xi > yi) ? xi : yi;
774   switch (aID) {
775     case kX:
776     case kWidth:
777       if (result == yi)
778         result = (int32_t) (((float) result) * objectSizeRatio);
779       result = (int32_t) (((float) result) * mWidthIncrementFactor);
780       break;
781     case kY:
782     case kHeight:
783       if (result == xi)
784         result =  (int32_t) (((float) result) / objectSizeRatio);
785       result = (int32_t) (((float) result) * mHeightIncrementFactor);
786       break;
787   }
788   return result;
789 }
790 
791 int32_t
GetNewResizingX(int32_t aX,int32_t aY)792 HTMLEditor::GetNewResizingX(int32_t aX,
793                             int32_t aY)
794 {
795   int32_t resized = mResizedObjectX +
796                     GetNewResizingIncrement(aX, aY, kX) * mXIncrementFactor;
797   int32_t max =   mResizedObjectX + mResizedObjectWidth;
798   return std::min(resized, max);
799 }
800 
801 int32_t
GetNewResizingY(int32_t aX,int32_t aY)802 HTMLEditor::GetNewResizingY(int32_t aX,
803                             int32_t aY)
804 {
805   int32_t resized = mResizedObjectY +
806                     GetNewResizingIncrement(aX, aY, kY) * mYIncrementFactor;
807   int32_t max =   mResizedObjectY + mResizedObjectHeight;
808   return std::min(resized, max);
809 }
810 
811 int32_t
GetNewResizingWidth(int32_t aX,int32_t aY)812 HTMLEditor::GetNewResizingWidth(int32_t aX,
813                                 int32_t aY)
814 {
815   int32_t resized = mResizedObjectWidth +
816                      GetNewResizingIncrement(aX, aY, kWidth) *
817                          mWidthIncrementFactor;
818   return std::max(resized, 1);
819 }
820 
821 int32_t
GetNewResizingHeight(int32_t aX,int32_t aY)822 HTMLEditor::GetNewResizingHeight(int32_t aX,
823                                  int32_t aY)
824 {
825   int32_t resized = mResizedObjectHeight +
826                      GetNewResizingIncrement(aX, aY, kHeight) *
827                          mHeightIncrementFactor;
828   return std::max(resized, 1);
829 }
830 
831 NS_IMETHODIMP
MouseMove(nsIDOMEvent * aMouseEvent)832 HTMLEditor::MouseMove(nsIDOMEvent* aMouseEvent)
833 {
834   NS_NAMED_LITERAL_STRING(leftStr, "left");
835   NS_NAMED_LITERAL_STRING(topStr, "top");
836 
837   if (mIsResizing) {
838     // we are resizing and the mouse pointer's position has changed
839     // we have to resdisplay the shadow
840     nsCOMPtr<nsIDOMMouseEvent> mouseEvent ( do_QueryInterface(aMouseEvent) );
841     int32_t clientX, clientY;
842     mouseEvent->GetClientX(&clientX);
843     mouseEvent->GetClientY(&clientY);
844 
845     int32_t newX = GetNewResizingX(clientX, clientY);
846     int32_t newY = GetNewResizingY(clientX, clientY);
847     int32_t newWidth  = GetNewResizingWidth(clientX, clientY);
848     int32_t newHeight = GetNewResizingHeight(clientX, clientY);
849 
850     mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::left,
851                                         newX);
852     mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::top,
853                                         newY);
854     mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::width,
855                                         newWidth);
856     mCSSEditUtils->SetCSSPropertyPixels(*mResizingShadow, *nsGkAtoms::height,
857                                         newHeight);
858 
859     return SetResizingInfoPosition(newX, newY, newWidth, newHeight);
860   }
861 
862   if (mGrabberClicked) {
863     nsCOMPtr<nsIDOMMouseEvent> mouseEvent ( do_QueryInterface(aMouseEvent) );
864     int32_t clientX, clientY;
865     mouseEvent->GetClientX(&clientX);
866     mouseEvent->GetClientY(&clientY);
867 
868     int32_t xThreshold =
869       LookAndFeel::GetInt(LookAndFeel::eIntID_DragThresholdX, 1);
870     int32_t yThreshold =
871       LookAndFeel::GetInt(LookAndFeel::eIntID_DragThresholdY, 1);
872 
873     if (DeprecatedAbs(clientX - mOriginalX) * 2 >= xThreshold ||
874         DeprecatedAbs(clientY - mOriginalY) * 2 >= yThreshold) {
875       mGrabberClicked = false;
876       StartMoving(nullptr);
877     }
878   }
879   if (mIsMoving) {
880     nsCOMPtr<nsIDOMMouseEvent> mouseEvent ( do_QueryInterface(aMouseEvent) );
881     int32_t clientX, clientY;
882     mouseEvent->GetClientX(&clientX);
883     mouseEvent->GetClientY(&clientY);
884 
885     int32_t newX = mPositionedObjectX + clientX - mOriginalX;
886     int32_t newY = mPositionedObjectY + clientY - mOriginalY;
887 
888     SnapToGrid(newX, newY);
889 
890     mCSSEditUtils->SetCSSPropertyPixels(*mPositioningShadow, *nsGkAtoms::left,
891                                         newX);
892     mCSSEditUtils->SetCSSPropertyPixels(*mPositioningShadow, *nsGkAtoms::top,
893                                         newY);
894   }
895   return NS_OK;
896 }
897 
898 void
SetFinalSize(int32_t aX,int32_t aY)899 HTMLEditor::SetFinalSize(int32_t aX,
900                          int32_t aY)
901 {
902   if (!mResizedObject) {
903     // paranoia
904     return;
905   }
906 
907   if (mActivatedHandle) {
908     mActivatedHandle->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_moz_activated, true);
909     mActivatedHandle = nullptr;
910   }
911 
912   // we have now to set the new width and height of the resized object
913   // we don't set the x and y position because we don't control that in
914   // a normal HTML layout
915   int32_t left   = GetNewResizingX(aX, aY);
916   int32_t top    = GetNewResizingY(aX, aY);
917   int32_t width  = GetNewResizingWidth(aX, aY);
918   int32_t height = GetNewResizingHeight(aX, aY);
919   bool setWidth  = !mResizedObjectIsAbsolutelyPositioned || (width != mResizedObjectWidth);
920   bool setHeight = !mResizedObjectIsAbsolutelyPositioned || (height != mResizedObjectHeight);
921 
922   int32_t x, y;
923   x = left - ((mResizedObjectIsAbsolutelyPositioned) ? mResizedObjectBorderLeft+mResizedObjectMarginLeft : 0);
924   y = top - ((mResizedObjectIsAbsolutelyPositioned) ? mResizedObjectBorderTop+mResizedObjectMarginTop : 0);
925 
926   // we want one transaction only from a user's point of view
927   AutoEditBatch batchIt(this);
928 
929   NS_NAMED_LITERAL_STRING(widthStr,  "width");
930   NS_NAMED_LITERAL_STRING(heightStr, "height");
931 
932   nsCOMPtr<Element> resizedObject = do_QueryInterface(mResizedObject);
933   NS_ENSURE_TRUE(resizedObject, );
934   if (mResizedObjectIsAbsolutelyPositioned) {
935     if (setHeight) {
936       mCSSEditUtils->SetCSSPropertyPixels(*resizedObject, *nsGkAtoms::top, y);
937     }
938     if (setWidth) {
939       mCSSEditUtils->SetCSSPropertyPixels(*resizedObject, *nsGkAtoms::left, x);
940     }
941   }
942   if (IsCSSEnabled() || mResizedObjectIsAbsolutelyPositioned) {
943     if (setWidth && mResizedObject->HasAttr(kNameSpaceID_None, nsGkAtoms::width)) {
944       RemoveAttribute(static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)), widthStr);
945     }
946 
947     if (setHeight && mResizedObject->HasAttr(kNameSpaceID_None,
948                                              nsGkAtoms::height)) {
949       RemoveAttribute(static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)), heightStr);
950     }
951 
952     if (setWidth) {
953       mCSSEditUtils->SetCSSPropertyPixels(*resizedObject, *nsGkAtoms::width,
954                                           width);
955     }
956     if (setHeight) {
957       mCSSEditUtils->SetCSSPropertyPixels(*resizedObject, *nsGkAtoms::height,
958                                           height);
959     }
960   } else {
961     // we use HTML size and remove all equivalent CSS properties
962 
963     // we set the CSS width and height to remove it later,
964     // triggering an immediate reflow; otherwise, we have problems
965     // with asynchronous reflow
966     if (setWidth) {
967       mCSSEditUtils->SetCSSPropertyPixels(*resizedObject, *nsGkAtoms::width,
968                                           width);
969     }
970     if (setHeight) {
971       mCSSEditUtils->SetCSSPropertyPixels(*resizedObject, *nsGkAtoms::height,
972                                           height);
973     }
974     if (setWidth) {
975       nsAutoString w;
976       w.AppendInt(width);
977       SetAttribute(static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)), widthStr, w);
978     }
979     if (setHeight) {
980       nsAutoString h;
981       h.AppendInt(height);
982       SetAttribute(static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)), heightStr, h);
983     }
984 
985     if (setWidth) {
986       mCSSEditUtils->RemoveCSSProperty(*resizedObject, *nsGkAtoms::width,
987                                        EmptyString());
988     }
989     if (setHeight) {
990       mCSSEditUtils->RemoveCSSProperty(*resizedObject, *nsGkAtoms::height,
991                                        EmptyString());
992     }
993   }
994   // finally notify the listeners if any
995   for (auto& listener : mObjectResizeEventListeners) {
996     listener->OnEndResizing(static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject)),
997                             mResizedObjectWidth, mResizedObjectHeight, width,
998                             height);
999   }
1000 
1001   // keep track of that size
1002   mResizedObjectWidth  = width;
1003   mResizedObjectHeight = height;
1004 
1005   RefreshResizers();
1006 }
1007 
1008 NS_IMETHODIMP
GetResizedObject(nsIDOMElement ** aResizedObject)1009 HTMLEditor::GetResizedObject(nsIDOMElement** aResizedObject)
1010 {
1011   nsCOMPtr<nsIDOMElement> ret = static_cast<nsIDOMElement*>(GetAsDOMNode(mResizedObject));
1012   ret.forget(aResizedObject);
1013   return NS_OK;
1014 }
1015 
1016 NS_IMETHODIMP
GetObjectResizingEnabled(bool * aIsObjectResizingEnabled)1017 HTMLEditor::GetObjectResizingEnabled(bool* aIsObjectResizingEnabled)
1018 {
1019   *aIsObjectResizingEnabled = mIsObjectResizingEnabled;
1020   return NS_OK;
1021 }
1022 
1023 NS_IMETHODIMP
SetObjectResizingEnabled(bool aObjectResizingEnabled)1024 HTMLEditor::SetObjectResizingEnabled(bool aObjectResizingEnabled)
1025 {
1026   mIsObjectResizingEnabled = aObjectResizingEnabled;
1027   return NS_OK;
1028 }
1029 
1030 NS_IMETHODIMP
AddObjectResizeEventListener(nsIHTMLObjectResizeListener * aListener)1031 HTMLEditor::AddObjectResizeEventListener(nsIHTMLObjectResizeListener* aListener)
1032 {
1033   NS_ENSURE_ARG_POINTER(aListener);
1034   if (mObjectResizeEventListeners.Contains(aListener)) {
1035     /* listener already registered */
1036     NS_ASSERTION(false,
1037                  "trying to register an already registered object resize event listener");
1038     return NS_OK;
1039   }
1040   mObjectResizeEventListeners.AppendElement(*aListener);
1041   return NS_OK;
1042 }
1043 
1044 NS_IMETHODIMP
RemoveObjectResizeEventListener(nsIHTMLObjectResizeListener * aListener)1045 HTMLEditor::RemoveObjectResizeEventListener(
1046               nsIHTMLObjectResizeListener* aListener)
1047 {
1048   NS_ENSURE_ARG_POINTER(aListener);
1049   if (!mObjectResizeEventListeners.Contains(aListener)) {
1050     /* listener was not registered */
1051     NS_ASSERTION(false,
1052                  "trying to remove an object resize event listener that was not already registered");
1053     return NS_OK;
1054   }
1055   mObjectResizeEventListeners.RemoveElement(aListener);
1056   return NS_OK;
1057 }
1058 
1059 } // namespace mozilla
1060