1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
9 #include "nsIServiceManager.h"
10 #include "nsResizerFrame.h"
11 #include "nsIContent.h"
12 #include "mozilla/PresShell.h"
13 #include "mozilla/dom/Document.h"
14 #include "nsGkAtoms.h"
15 #include "nsNameSpaceManager.h"
17 #include "nsPresContext.h"
18 #include "nsFrameManager.h"
19 #include "nsIDocShell.h"
20 #include "nsIDocShellTreeOwner.h"
21 #include "nsIBaseWindow.h"
22 #include "nsPIDOMWindow.h"
23 #include "mozilla/MouseEvents.h"
24 #include "nsContentUtils.h"
25 #include "nsMenuPopupFrame.h"
26 #include "nsIScreenManager.h"
27 #include "mozilla/dom/Element.h"
28 #include "mozilla/dom/MouseEventBinding.h"
30 #include "nsICSSDeclaration.h"
31 #include "nsStyledElement.h"
34 using namespace mozilla
;
39 // Creates a new Resizer frame and returns it
41 nsIFrame
* NS_NewResizerFrame(PresShell
* aPresShell
, ComputedStyle
* aStyle
) {
42 return new (aPresShell
) nsResizerFrame(aStyle
, aPresShell
->GetPresContext());
45 NS_IMPL_FRAMEARENA_HELPERS(nsResizerFrame
)
47 nsResizerFrame::nsResizerFrame(ComputedStyle
* aStyle
,
48 nsPresContext
* aPresContext
)
49 : nsTitleBarFrame(aStyle
, aPresContext
, kClassID
) {}
51 nsresult
nsResizerFrame::HandleEvent(nsPresContext
* aPresContext
,
52 WidgetGUIEvent
* aEvent
,
53 nsEventStatus
* aEventStatus
) {
54 NS_ENSURE_ARG_POINTER(aEventStatus
);
55 if (nsEventStatus_eConsumeNoDefault
== *aEventStatus
) {
59 AutoWeakFrame
weakFrame(this);
60 bool doDefault
= true;
62 switch (aEvent
->mMessage
) {
65 if (aEvent
->mClass
== eTouchEventClass
||
66 (aEvent
->mClass
== eMouseEventClass
&&
67 aEvent
->AsMouseEvent()->mButton
== MouseButton::eLeft
)) {
68 nsCOMPtr
<nsIBaseWindow
> window
;
69 mozilla::PresShell
* presShell
= aPresContext
->GetPresShell();
70 nsIContent
* contentToResize
=
71 GetContentToResize(presShell
, getter_AddRefs(window
));
72 if (contentToResize
) {
73 nsIFrame
* frameToResize
= contentToResize
->GetPrimaryFrame();
74 if (!frameToResize
) break;
76 // cache the content rectangle for the frame to resize
77 // GetScreenRectInAppUnits returns the border box rectangle, so
78 // adjust to get the desired content rectangle.
79 nsRect rect
= frameToResize
->GetScreenRectInAppUnits();
80 if (frameToResize
->StylePosition()->mBoxSizing
==
81 StyleBoxSizing::Content
) {
82 rect
.Deflate(frameToResize
->GetUsedBorderAndPadding());
85 mMouseDownRect
= LayoutDeviceIntRect::FromAppUnitsToNearest(
86 rect
, aPresContext
->AppUnitsPerDevPixel());
89 // If there is no window, then resizing isn't allowed.
94 // ask the widget implementation to begin a resize drag if it can
95 Direction direction
= GetDirection();
96 nsresult rv
= aEvent
->mWidget
->BeginResizeDrag(
97 aEvent
, direction
.mHorizontal
, direction
.mVertical
);
98 // for native drags, don't set the fields below
99 if (rv
!= NS_ERROR_NOT_IMPLEMENTED
) break;
101 // if there's no native resize support, we need to do window
102 // resizing ourselves
103 window
->GetPositionAndSize(&mMouseDownRect
.x
, &mMouseDownRect
.y
,
104 &mMouseDownRect
.width
,
105 &mMouseDownRect
.height
);
108 // remember current mouse coordinates
109 LayoutDeviceIntPoint refPoint
;
110 if (!GetEventPoint(aEvent
, refPoint
)) return NS_OK
;
111 mMouseDownPoint
= refPoint
+ aEvent
->mWidget
->WidgetToScreenOffset();
114 mTrackingMouseMove
= true;
116 PresShell::SetCapturingContent(GetContent(),
117 CaptureFlags::IgnoreAllowedState
);
123 if (aEvent
->mClass
== eTouchEventClass
||
124 (aEvent
->mClass
== eMouseEventClass
&&
125 aEvent
->AsMouseEvent()->mButton
== MouseButton::eLeft
)) {
126 // we're done tracking.
127 mTrackingMouseMove
= false;
129 PresShell::ReleaseCapturingContent();
137 if (mTrackingMouseMove
) {
138 nsCOMPtr
<nsIBaseWindow
> window
;
139 mozilla::PresShell
* presShell
= aPresContext
->GetPresShell();
140 nsCOMPtr
<nsIContent
> contentToResize
=
141 GetContentToResize(presShell
, getter_AddRefs(window
));
143 // check if the returned content really is a menupopup
144 nsMenuPopupFrame
* menuPopupFrame
= nullptr;
145 if (contentToResize
) {
146 menuPopupFrame
= do_QueryFrame(contentToResize
->GetPrimaryFrame());
149 // both MouseMove and direction are negative when pointing to the
150 // top and left, and positive when pointing to the bottom and right
152 // retrieve the offset of the mousemove event relative to the mousedown.
153 // The difference is how much the resize needs to be
154 LayoutDeviceIntPoint refPoint
;
155 if (!GetEventPoint(aEvent
, refPoint
)) return NS_OK
;
156 LayoutDeviceIntPoint screenPoint
=
157 refPoint
+ aEvent
->mWidget
->WidgetToScreenOffset();
158 LayoutDeviceIntPoint
mouseMove(screenPoint
- mMouseDownPoint
);
160 // Determine which direction to resize by checking the dir attribute.
161 // For windows and menus, ensure that it can be resized in that
163 Direction direction
= GetDirection();
164 if (window
|| menuPopupFrame
) {
165 if (menuPopupFrame
) {
166 menuPopupFrame
->CanAdjustEdges(
167 (direction
.mHorizontal
== -1) ? eSideLeft
: eSideRight
,
168 (direction
.mVertical
== -1) ? eSideTop
: eSideBottom
,
171 } else if (!contentToResize
) {
172 break; // don't do anything if there's nothing to resize
175 LayoutDeviceIntRect rect
= mMouseDownRect
;
177 // Check if there are any size constraints on this window.
178 widget::SizeConstraints sizeConstraints
;
180 nsCOMPtr
<nsIWidget
> widget
;
181 window
->GetMainWidget(getter_AddRefs(widget
));
182 sizeConstraints
= widget
->GetSizeConstraints();
185 AdjustDimensions(&rect
.x
, &rect
.width
, sizeConstraints
.mMinSize
.width
,
186 sizeConstraints
.mMaxSize
.width
, mouseMove
.x
,
187 direction
.mHorizontal
);
188 AdjustDimensions(&rect
.y
, &rect
.height
, sizeConstraints
.mMinSize
.height
,
189 sizeConstraints
.mMaxSize
.height
, mouseMove
.y
,
190 direction
.mVertical
);
192 // Don't allow resizing a window or a popup past the edge of the screen,
193 // so adjust the rectangle to fit within the available screen area.
195 nsCOMPtr
<nsIScreen
> screen
;
196 nsCOMPtr
<nsIScreenManager
> sm(
197 do_GetService("@mozilla.org/gfx/screenmanager;1"));
199 CSSIntRect frameRect
= GetScreenRect();
200 // ScreenForRect requires display pixels, so scale from device pix
202 window
->GetUnscaledDevicePixelsPerCSSPixel(&scale
);
203 sm
->ScreenForRect(NSToIntRound(frameRect
.x
/ scale
),
204 NSToIntRound(frameRect
.y
/ scale
), 1, 1,
205 getter_AddRefs(screen
));
207 LayoutDeviceIntRect screenRect
;
208 screen
->GetRect(&screenRect
.x
, &screenRect
.y
, &screenRect
.width
,
210 rect
.IntersectRect(rect
, screenRect
);
213 } else if (menuPopupFrame
) {
214 nsRect frameRect
= menuPopupFrame
->GetScreenRectInAppUnits();
215 nsIFrame
* rootFrame
= aPresContext
->PresShell()->GetRootFrame();
216 nsRect rootScreenRect
= rootFrame
->GetScreenRectInAppUnits();
218 nsPopupLevel popupLevel
= menuPopupFrame
->PopupLevel();
219 int32_t appPerDev
= aPresContext
->AppUnitsPerDevPixel();
220 LayoutDeviceIntRect screenRect
= menuPopupFrame
->GetConstraintRect(
221 LayoutDeviceIntRect::FromAppUnitsToNearest(frameRect
, appPerDev
),
222 // round using ...ToInside as it's better to be a pixel too small
223 // than be too large. If the popup is too large it could get
224 // flipped to the opposite side of the anchor point while
226 LayoutDeviceIntRect::FromAppUnitsToInside(rootScreenRect
,
229 rect
.IntersectRect(rect
, screenRect
);
232 if (contentToResize
) {
233 // convert the rectangle into css pixels. When changing the size in a
234 // direction, don't allow the new size to be less that the resizer's
235 // size. This ensures that content isn't resized too small as to make
236 // the resizer invisible.
237 nsRect appUnitsRect
= ToAppUnits(rect
.ToUnknownRect(),
238 aPresContext
->AppUnitsPerDevPixel());
239 if (appUnitsRect
.width
< mRect
.width
&& mouseMove
.x
)
240 appUnitsRect
.width
= mRect
.width
;
241 if (appUnitsRect
.height
< mRect
.height
&& mouseMove
.y
)
242 appUnitsRect
.height
= mRect
.height
;
244 appUnitsRect
.ToInsidePixels(AppUnitsPerCSSPixel());
246 LayoutDeviceIntRect oldRect
;
247 AutoWeakFrame
weakFrame(menuPopupFrame
);
248 if (menuPopupFrame
) {
249 nsCOMPtr
<nsIWidget
> widget
= menuPopupFrame
->GetWidget();
250 if (widget
) oldRect
= widget
->GetScreenBounds();
252 // convert the new rectangle into outer window coordinates
253 LayoutDeviceIntPoint clientOffset
= widget
->GetClientOffset();
254 rect
.x
-= clientOffset
.x
;
255 rect
.y
-= clientOffset
.y
;
258 SizeInfo sizeInfo
, originalSizeInfo
;
259 sizeInfo
.width
.AppendInt(cssRect
.width
);
260 sizeInfo
.height
.AppendInt(cssRect
.height
);
261 ResizeContent(contentToResize
, direction
, sizeInfo
,
263 MaybePersistOriginalSize(contentToResize
, originalSizeInfo
);
265 // Move the popup to the new location unless it is anchored, since
266 // the position shouldn't change. nsMenuPopupFrame::SetPopupPosition
267 // will instead ensure that the popup's position is anchored at the
269 if (weakFrame
.IsAlive() &&
270 (oldRect
.x
!= rect
.x
|| oldRect
.y
!= rect
.y
) &&
271 (!menuPopupFrame
->IsAnchored() ||
272 menuPopupFrame
->PopupLevel() != ePopupLevelParent
)) {
274 rect
.TopLeft() / aPresContext
->CSSToDevPixelScale();
275 menuPopupFrame
->MoveTo(RoundedToInt(cssPos
), true);
278 window
->SetPositionAndSize(
279 rect
.x
, rect
.y
, rect
.width
, rect
.height
,
280 nsIBaseWindow::eRepaint
); // do the repaint.
288 WidgetMouseEvent
* mouseEvent
= aEvent
->AsMouseEvent();
289 if (mouseEvent
->IsLeftClickEvent()) {
290 MouseClicked(mouseEvent
);
294 case eMouseDoubleClick
:
295 if (aEvent
->AsMouseEvent()->mButton
== MouseButton::eLeft
) {
296 nsCOMPtr
<nsIBaseWindow
> window
;
297 mozilla::PresShell
* presShell
= aPresContext
->GetPresShell();
298 nsIContent
* contentToResize
=
299 GetContentToResize(presShell
, getter_AddRefs(window
));
300 if (contentToResize
) {
301 nsMenuPopupFrame
* menuPopupFrame
=
302 do_QueryFrame(contentToResize
->GetPrimaryFrame());
304 break; // Don't restore original sizing for menupopup frames until
305 // we handle screen constraints here. (Bug 357725)
307 RestoreOriginalSize(contentToResize
);
316 if (!doDefault
) *aEventStatus
= nsEventStatus_eConsumeNoDefault
;
318 if (doDefault
&& weakFrame
.IsAlive())
319 return nsTitleBarFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
324 nsIContent
* nsResizerFrame::GetContentToResize(mozilla::PresShell
* aPresShell
,
325 nsIBaseWindow
** aWindow
) {
328 nsAutoString elementid
;
329 mContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::element
,
331 if (elementid
.IsEmpty()) {
332 // If the resizer is in a popup, resize the popup's widget, otherwise
333 // resize the widget associated with the window.
334 nsIFrame
* popup
= GetParent();
336 nsMenuPopupFrame
* popupFrame
= do_QueryFrame(popup
);
338 return popupFrame
->GetContent();
340 popup
= popup
->GetParent();
343 // don't allow resizing windows in content shells
344 nsCOMPtr
<nsIDocShellTreeItem
> dsti
=
345 aPresShell
->GetPresContext()->GetDocShell();
346 if (!dsti
|| dsti
->ItemType() != nsIDocShellTreeItem::typeChrome
) {
347 // don't allow resizers in content shells, except for the viewport
348 // scrollbar which doesn't have a parent
349 nsIContent
* nonNativeAnon
=
350 mContent
->FindFirstNonChromeOnlyAccessContent();
351 if (!nonNativeAnon
|| nonNativeAnon
->GetParent()) {
356 // get the document and the window - should this be cached?
357 if (nsPIDOMWindowOuter
* domWindow
=
358 aPresShell
->GetDocument()->GetWindow()) {
359 nsCOMPtr
<nsIDocShell
> docShell
= domWindow
->GetDocShell();
361 nsCOMPtr
<nsIDocShellTreeOwner
> treeOwner
;
362 docShell
->GetTreeOwner(getter_AddRefs(treeOwner
));
364 CallQueryInterface(treeOwner
, aWindow
);
372 if (elementid
.EqualsLiteral("_parent")) {
373 // return the parent, but skip over native anonymous content
374 nsIContent
* parent
= mContent
->GetParent();
375 return parent
? parent
->FindFirstNonChromeOnlyAccessContent() : nullptr;
378 return aPresShell
->GetDocument()->GetElementById(elementid
);
381 void nsResizerFrame::AdjustDimensions(int32_t* aPos
, int32_t* aSize
,
382 int32_t aMinSize
, int32_t aMaxSize
,
384 int8_t aResizerDirection
) {
385 int32_t oldSize
= *aSize
;
387 *aSize
+= aResizerDirection
* aMovement
;
388 // use one as a minimum size or the element could disappear
389 if (*aSize
< 1) *aSize
= 1;
391 // Constrain the size within the minimum and maximum size.
392 *aSize
= std::max(aMinSize
, std::min(aMaxSize
, *aSize
));
394 // For left and top resizers, the window must be moved left by the same
395 // amount that the window was resized.
396 if (aResizerDirection
== -1) *aPos
+= oldSize
- *aSize
;
400 void nsResizerFrame::ResizeContent(nsIContent
* aContent
,
401 const Direction
& aDirection
,
402 const SizeInfo
& aSizeInfo
,
403 SizeInfo
* aOriginalSizeInfo
) {
404 // for XUL elements, just set the width and height attributes. For
405 // other elements, set style.width and style.height
406 if (aContent
->IsXULElement()) {
407 if (aOriginalSizeInfo
) {
408 aContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::width
,
409 aOriginalSizeInfo
->width
);
410 aContent
->AsElement()->GetAttr(kNameSpaceID_None
, nsGkAtoms::height
,
411 aOriginalSizeInfo
->height
);
413 // only set the property if the element could have changed in that direction
414 if (aDirection
.mHorizontal
) {
415 aContent
->AsElement()->SetAttr(kNameSpaceID_None
, nsGkAtoms::width
,
416 aSizeInfo
.width
, true);
418 if (aDirection
.mVertical
) {
419 aContent
->AsElement()->SetAttr(kNameSpaceID_None
, nsGkAtoms::height
,
420 aSizeInfo
.height
, true);
423 nsCOMPtr
<nsStyledElement
> inlineStyleContent
= do_QueryInterface(aContent
);
424 if (inlineStyleContent
) {
425 nsICSSDeclaration
* decl
= inlineStyleContent
->Style();
427 if (aOriginalSizeInfo
) {
428 decl
->GetPropertyValue(NS_LITERAL_STRING("width"),
429 aOriginalSizeInfo
->width
);
430 decl
->GetPropertyValue(NS_LITERAL_STRING("height"),
431 aOriginalSizeInfo
->height
);
434 // only set the property if the element could have changed in that
436 if (aDirection
.mHorizontal
) {
437 nsAutoString
widthstr(aSizeInfo
.width
);
438 if (!widthstr
.IsEmpty() &&
439 !Substring(widthstr
, widthstr
.Length() - 2, 2).EqualsLiteral("px"))
440 widthstr
.AppendLiteral("px");
441 decl
->SetProperty(NS_LITERAL_STRING("width"), widthstr
, EmptyString());
443 if (aDirection
.mVertical
) {
444 nsAutoString
heightstr(aSizeInfo
.height
);
445 if (!heightstr
.IsEmpty() &&
446 !Substring(heightstr
, heightstr
.Length() - 2, 2)
447 .EqualsLiteral("px"))
448 heightstr
.AppendLiteral("px");
449 decl
->SetProperty(NS_LITERAL_STRING("height"), heightstr
,
457 void nsResizerFrame::MaybePersistOriginalSize(nsIContent
* aContent
,
458 const SizeInfo
& aSizeInfo
) {
461 aContent
->GetProperty(nsGkAtoms::_moz_original_size
, &rv
);
462 if (rv
!= NS_PROPTABLE_PROP_NOT_THERE
) return;
464 nsAutoPtr
<SizeInfo
> sizeInfo(new SizeInfo(aSizeInfo
));
465 rv
= aContent
->SetProperty(nsGkAtoms::_moz_original_size
, sizeInfo
.get(),
466 nsINode::DeleteProperty
<nsResizerFrame::SizeInfo
>);
467 if (NS_SUCCEEDED(rv
)) sizeInfo
.forget();
471 void nsResizerFrame::RestoreOriginalSize(nsIContent
* aContent
) {
473 SizeInfo
* sizeInfo
= static_cast<SizeInfo
*>(
474 aContent
->GetProperty(nsGkAtoms::_moz_original_size
, &rv
));
475 if (NS_FAILED(rv
)) return;
477 NS_ASSERTION(sizeInfo
, "We set a null sizeInfo!?");
478 Direction direction
= {1, 1};
479 ResizeContent(aContent
, direction
, *sizeInfo
, nullptr);
480 aContent
->DeleteProperty(nsGkAtoms::_moz_original_size
);
483 /* returns a Direction struct containing the horizontal and vertical direction
485 nsResizerFrame::Direction
nsResizerFrame::GetDirection() {
486 static const Element::AttrValuesArray strings
[] = {
488 nsGkAtoms::topleft
, nsGkAtoms::top
, nsGkAtoms::topright
,
489 nsGkAtoms::left
, nsGkAtoms::right
,
490 nsGkAtoms::bottomleft
, nsGkAtoms::bottom
, nsGkAtoms::bottomright
,
491 nsGkAtoms::bottomstart
, nsGkAtoms::bottomend
,
496 static const Direction directions
[] = {
498 {-1, -1}, {0, -1}, {1, -1},
500 {-1, 1}, {0, 1}, {1, 1},
506 return directions
[0]; // default: topleft
509 int32_t index
= mContent
->AsElement()->FindAttrValueIn(
510 kNameSpaceID_None
, nsGkAtoms::dir
, strings
, eCaseMatters
);
512 return directions
[0]; // default: topleft
516 // Directions 8 and higher are RTL-aware directions and should reverse the
517 // horizontal component if RTL.
518 WritingMode wm
= GetWritingMode();
519 if (wm
.IsPhysicalRTL()) {
520 Direction direction
= directions
[index
];
521 direction
.mHorizontal
*= -1;
526 return directions
[index
];
529 void nsResizerFrame::MouseClicked(WidgetMouseEvent
* aEvent
) {
530 // Execute the oncommand event handler.
531 nsCOMPtr
<nsIContent
> content
= mContent
;
532 nsContentUtils::DispatchXULCommand(
533 content
, false, nullptr, nullptr, aEvent
->IsControl(), aEvent
->IsAlt(),
534 aEvent
->IsShift(), aEvent
->IsMeta(), aEvent
->mInputSource
);