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