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/. */
7 #include "nsIServiceManager.h"
8 #include "nsResizerFrame.h"
9 #include "nsIContent.h"
10 #include "nsIDocument.h"
11 #include "nsIDOMNodeList.h"
12 #include "nsGkAtoms.h"
13 #include "nsNameSpaceManager.h"
14 #include "nsIDOMElementCSSInlineStyle.h"
15 #include "nsIDOMCSSStyleDeclaration.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"
31 using namespace mozilla
;
36 // Creates a new Resizer frame and returns it
39 NS_NewResizerFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
41 return new (aPresShell
) nsResizerFrame(aPresShell
, aContext
);
44 NS_IMPL_FRAMEARENA_HELPERS(nsResizerFrame
)
46 nsResizerFrame::nsResizerFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
47 :nsTitleBarFrame(aPresShell
, aContext
)
52 nsResizerFrame::HandleEvent(nsPresContext
* aPresContext
,
53 WidgetGUIEvent
* aEvent
,
54 nsEventStatus
* aEventStatus
)
56 NS_ENSURE_ARG_POINTER(aEventStatus
);
57 if (nsEventStatus_eConsumeNoDefault
== *aEventStatus
) {
61 nsWeakFrame
weakFrame(this);
62 bool doDefault
= true;
64 switch (aEvent
->message
) {
66 case NS_MOUSE_BUTTON_DOWN
: {
67 if (aEvent
->mClass
== eTouchEventClass
||
68 (aEvent
->mClass
== eMouseEventClass
&&
69 aEvent
->AsMouseEvent()->button
== WidgetMouseEvent::eLeftButton
)) {
70 nsCOMPtr
<nsIBaseWindow
> window
;
71 nsIPresShell
* presShell
= aPresContext
->GetPresShell();
72 nsIContent
* contentToResize
=
73 GetContentToResize(presShell
, getter_AddRefs(window
));
74 if (contentToResize
) {
75 nsIFrame
* frameToResize
= contentToResize
->GetPrimaryFrame();
79 // cache the content rectangle for the frame to resize
80 // GetScreenRectInAppUnits returns the border box rectangle, so
81 // adjust to get the desired content rectangle.
82 nsRect rect
= frameToResize
->GetScreenRectInAppUnits();
83 switch (frameToResize
->StylePosition()->mBoxSizing
) {
84 case NS_STYLE_BOX_SIZING_CONTENT
:
85 rect
.Deflate(frameToResize
->GetUsedPadding());
86 case NS_STYLE_BOX_SIZING_PADDING
:
87 rect
.Deflate(frameToResize
->GetUsedBorder());
92 mMouseDownRect
= rect
.ToNearestPixels(aPresContext
->AppUnitsPerDevPixel());
96 // If there is no window, then resizing isn't allowed.
102 // ask the widget implementation to begin a resize drag if it can
103 Direction direction
= GetDirection();
104 nsresult rv
= aEvent
->widget
->BeginResizeDrag(aEvent
,
105 direction
.mHorizontal
, direction
.mVertical
);
106 // for native drags, don't set the fields below
107 if (rv
!= NS_ERROR_NOT_IMPLEMENTED
)
110 // if there's no native resize support, we need to do window
111 // resizing ourselves
112 window
->GetPositionAndSize(&mMouseDownRect
.x
, &mMouseDownRect
.y
,
113 &mMouseDownRect
.width
, &mMouseDownRect
.height
);
116 // remember current mouse coordinates
118 if (!GetEventPoint(aEvent
, refPoint
))
120 mMouseDownPoint
= refPoint
+ aEvent
->widget
->WidgetToScreenOffset();
123 mTrackingMouseMove
= true;
125 nsIPresShell::SetCapturingContent(GetContent(), CAPTURE_IGNOREALLOWED
);
131 case NS_MOUSE_BUTTON_UP
: {
132 if (aEvent
->mClass
== eTouchEventClass
||
133 (aEvent
->mClass
== eMouseEventClass
&&
134 aEvent
->AsMouseEvent()->button
== WidgetMouseEvent::eLeftButton
)) {
135 // we're done tracking.
136 mTrackingMouseMove
= false;
138 nsIPresShell::SetCapturingContent(nullptr, 0);
146 case NS_MOUSE_MOVE
: {
147 if (mTrackingMouseMove
)
149 nsCOMPtr
<nsIBaseWindow
> window
;
150 nsIPresShell
* presShell
= aPresContext
->GetPresShell();
151 nsCOMPtr
<nsIContent
> contentToResize
=
152 GetContentToResize(presShell
, getter_AddRefs(window
));
154 // check if the returned content really is a menupopup
155 nsMenuPopupFrame
* menuPopupFrame
= nullptr;
156 if (contentToResize
) {
157 menuPopupFrame
= do_QueryFrame(contentToResize
->GetPrimaryFrame());
160 // both MouseMove and direction are negative when pointing to the
161 // top and left, and positive when pointing to the bottom and right
163 // retrieve the offset of the mousemove event relative to the mousedown.
164 // The difference is how much the resize needs to be
166 if (!GetEventPoint(aEvent
, refPoint
))
168 nsIntPoint
screenPoint(refPoint
+ aEvent
->widget
->WidgetToScreenOffset());
169 nsIntPoint
mouseMove(screenPoint
- mMouseDownPoint
);
171 // Determine which direction to resize by checking the dir attribute.
172 // For windows and menus, ensure that it can be resized in that direction.
173 Direction direction
= GetDirection();
174 if (window
|| menuPopupFrame
) {
175 if (menuPopupFrame
) {
176 menuPopupFrame
->CanAdjustEdges(
177 (direction
.mHorizontal
== -1) ? NS_SIDE_LEFT
: NS_SIDE_RIGHT
,
178 (direction
.mVertical
== -1) ? NS_SIDE_TOP
: NS_SIDE_BOTTOM
, mouseMove
);
181 else if (!contentToResize
) {
182 break; // don't do anything if there's nothing to resize
185 nsIntRect rect
= mMouseDownRect
;
187 // Check if there are any size constraints on this window.
188 widget::SizeConstraints sizeConstraints
;
190 nsCOMPtr
<nsIWidget
> widget
;
191 window
->GetMainWidget(getter_AddRefs(widget
));
192 sizeConstraints
= widget
->GetSizeConstraints();
195 AdjustDimensions(&rect
.x
, &rect
.width
, sizeConstraints
.mMinSize
.width
,
196 sizeConstraints
.mMaxSize
.width
, mouseMove
.x
, direction
.mHorizontal
);
197 AdjustDimensions(&rect
.y
, &rect
.height
, sizeConstraints
.mMinSize
.height
,
198 sizeConstraints
.mMaxSize
.height
, mouseMove
.y
, direction
.mVertical
);
200 // Don't allow resizing a window or a popup past the edge of the screen,
201 // so adjust the rectangle to fit within the available screen area.
203 nsCOMPtr
<nsIScreen
> screen
;
204 nsCOMPtr
<nsIScreenManager
> sm(do_GetService("@mozilla.org/gfx/screenmanager;1"));
206 nsIntRect frameRect
= GetScreenRect();
207 // ScreenForRect requires display pixels, so scale from device pix
209 window
->GetUnscaledDevicePixelsPerCSSPixel(&scale
);
210 sm
->ScreenForRect(NSToIntRound(frameRect
.x
/ scale
),
211 NSToIntRound(frameRect
.y
/ scale
), 1, 1,
212 getter_AddRefs(screen
));
214 nsIntRect screenRect
;
215 screen
->GetRect(&screenRect
.x
, &screenRect
.y
,
216 &screenRect
.width
, &screenRect
.height
);
217 rect
.IntersectRect(rect
, screenRect
);
221 else if (menuPopupFrame
) {
222 nsRect frameRect
= menuPopupFrame
->GetScreenRectInAppUnits();
223 nsIFrame
* rootFrame
= aPresContext
->PresShell()->FrameManager()->GetRootFrame();
224 nsRect rootScreenRect
= rootFrame
->GetScreenRectInAppUnits();
226 nsPopupLevel popupLevel
= menuPopupFrame
->PopupLevel();
227 nsRect screenRect
= menuPopupFrame
->GetConstraintRect(frameRect
, rootScreenRect
, popupLevel
);
228 // round using ToInsidePixels as it's better to be a pixel too small
229 // than be too large. If the popup is too large it could get flipped
230 // to the opposite side of the anchor point while resizing.
231 nsIntRect screenRectPixels
= screenRect
.ToInsidePixels(aPresContext
->AppUnitsPerDevPixel());
232 rect
.IntersectRect(rect
, screenRectPixels
);
235 if (contentToResize
) {
236 // convert the rectangle into css pixels. When changing the size in a
237 // direction, don't allow the new size to be less that the resizer's
238 // size. This ensures that content isn't resized too small as to make
239 // the resizer invisible.
240 nsRect appUnitsRect
= rect
.ToAppUnits(aPresContext
->AppUnitsPerDevPixel());
241 if (appUnitsRect
.width
< mRect
.width
&& mouseMove
.x
)
242 appUnitsRect
.width
= mRect
.width
;
243 if (appUnitsRect
.height
< mRect
.height
&& mouseMove
.y
)
244 appUnitsRect
.height
= mRect
.height
;
245 nsIntRect cssRect
= appUnitsRect
.ToInsidePixels(nsPresContext::AppUnitsPerCSSPixel());
248 nsWeakFrame
weakFrame(menuPopupFrame
);
249 if (menuPopupFrame
) {
250 nsCOMPtr
<nsIWidget
> widget
= menuPopupFrame
->GetWidget();
252 widget
->GetScreenBounds(oldRect
);
254 // convert the new rectangle into outer window coordinates
255 nsIntPoint clientOffset
= widget
->GetClientOffset();
256 rect
.x
-= clientOffset
.x
;
257 rect
.y
-= clientOffset
.y
;
260 SizeInfo sizeInfo
, originalSizeInfo
;
261 sizeInfo
.width
.AppendInt(cssRect
.width
);
262 sizeInfo
.height
.AppendInt(cssRect
.height
);
263 ResizeContent(contentToResize
, direction
, sizeInfo
, &originalSizeInfo
);
264 MaybePersistOriginalSize(contentToResize
, originalSizeInfo
);
266 // Move the popup to the new location unless it is anchored, since
267 // the position shouldn't change. nsMenuPopupFrame::SetPopupPosition
268 // will instead ensure that the popup's position is anchored at the
270 if (weakFrame
.IsAlive() &&
271 (oldRect
.x
!= rect
.x
|| oldRect
.y
!= rect
.y
) &&
272 (!menuPopupFrame
->IsAnchored() ||
273 menuPopupFrame
->PopupLevel() != ePopupLevelParent
)) {
275 rect
.x
= aPresContext
->DevPixelsToIntCSSPixels(rect
.x
);
276 rect
.y
= aPresContext
->DevPixelsToIntCSSPixels(rect
.y
);
277 menuPopupFrame
->MoveTo(rect
.x
, rect
.y
, true);
281 window
->SetPositionAndSize(rect
.x
, rect
.y
, rect
.width
, rect
.height
, true); // do the repaint.
289 case NS_MOUSE_CLICK
: {
290 WidgetMouseEvent
* mouseEvent
= aEvent
->AsMouseEvent();
291 if (mouseEvent
->IsLeftClickEvent()) {
292 MouseClicked(aPresContext
, mouseEvent
);
296 case NS_MOUSE_DOUBLECLICK
:
297 if (aEvent
->AsMouseEvent()->button
== WidgetMouseEvent::eLeftButton
) {
298 nsCOMPtr
<nsIBaseWindow
> window
;
299 nsIPresShell
* presShell
= aPresContext
->GetPresShell();
300 nsIContent
* contentToResize
=
301 GetContentToResize(presShell
, getter_AddRefs(window
));
302 if (contentToResize
) {
303 nsMenuPopupFrame
* menuPopupFrame
= do_QueryFrame(contentToResize
->GetPrimaryFrame());
305 break; // Don't restore original sizing for menupopup frames until
306 // we handle screen constraints here. (Bug 357725)
308 RestoreOriginalSize(contentToResize
);
315 *aEventStatus
= nsEventStatus_eConsumeNoDefault
;
317 if (doDefault
&& weakFrame
.IsAlive())
318 return nsTitleBarFrame::HandleEvent(aPresContext
, aEvent
, aEventStatus
);
324 nsResizerFrame::GetContentToResize(nsIPresShell
* aPresShell
, nsIBaseWindow
** aWindow
)
328 nsAutoString elementid
;
329 mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::element
, elementid
);
330 if (elementid
.IsEmpty()) {
331 // If the resizer is in a popup, resize the popup's widget, otherwise
332 // resize the widget associated with the window.
333 nsIFrame
* popup
= GetParent();
335 nsMenuPopupFrame
* popupFrame
= do_QueryFrame(popup
);
337 return popupFrame
->GetContent();
339 popup
= popup
->GetParent();
342 // don't allow resizing windows in content shells
343 nsCOMPtr
<nsIDocShellTreeItem
> dsti
= aPresShell
->GetPresContext()->GetDocShell();
344 if (!dsti
|| dsti
->ItemType() != nsIDocShellTreeItem::typeChrome
) {
345 // don't allow resizers in content shells, except for the viewport
346 // scrollbar which doesn't have a parent
347 nsIContent
* nonNativeAnon
= mContent
->FindFirstNonChromeOnlyAccessContent();
348 if (!nonNativeAnon
|| nonNativeAnon
->GetParent()) {
353 // get the document and the window - should this be cached?
354 nsPIDOMWindow
*domWindow
= aPresShell
->GetDocument()->GetWindow();
356 nsCOMPtr
<nsIDocShell
> docShell
= domWindow
->GetDocShell();
358 nsCOMPtr
<nsIDocShellTreeOwner
> treeOwner
;
359 docShell
->GetTreeOwner(getter_AddRefs(treeOwner
));
361 CallQueryInterface(treeOwner
, aWindow
);
369 if (elementid
.EqualsLiteral("_parent")) {
370 // return the parent, but skip over native anonymous content
371 nsIContent
* parent
= mContent
->GetParent();
372 return parent
? parent
->FindFirstNonChromeOnlyAccessContent() : nullptr;
375 return aPresShell
->GetDocument()->GetElementById(elementid
);
379 nsResizerFrame::AdjustDimensions(int32_t* aPos
, int32_t* aSize
,
380 int32_t aMinSize
, int32_t aMaxSize
,
381 int32_t aMovement
, int8_t aResizerDirection
)
383 int32_t oldSize
= *aSize
;
385 *aSize
+= aResizerDirection
* aMovement
;
386 // use one as a minimum size or the element could disappear
390 // Constrain the size within the minimum and maximum size.
391 *aSize
= std::max(aMinSize
, std::min(aMaxSize
, *aSize
));
393 // For left and top resizers, the window must be moved left by the same
394 // amount that the window was resized.
395 if (aResizerDirection
== -1)
396 *aPos
+= oldSize
- *aSize
;
400 nsResizerFrame::ResizeContent(nsIContent
* aContent
, const Direction
& aDirection
,
401 const SizeInfo
& aSizeInfo
, SizeInfo
* aOriginalSizeInfo
)
403 // for XUL elements, just set the width and height attributes. For
404 // other elements, set style.width and style.height
405 if (aContent
->IsXUL()) {
406 if (aOriginalSizeInfo
) {
407 aContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::width
,
408 aOriginalSizeInfo
->width
);
409 aContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::height
,
410 aOriginalSizeInfo
->height
);
412 // only set the property if the element could have changed in that direction
413 if (aDirection
.mHorizontal
) {
414 aContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::width
, aSizeInfo
.width
, true);
416 if (aDirection
.mVertical
) {
417 aContent
->SetAttr(kNameSpaceID_None
, nsGkAtoms::height
, aSizeInfo
.height
, true);
421 nsCOMPtr
<nsIDOMElementCSSInlineStyle
> inlineStyleContent
=
422 do_QueryInterface(aContent
);
423 if (inlineStyleContent
) {
424 nsCOMPtr
<nsIDOMCSSStyleDeclaration
> decl
;
425 inlineStyleContent
->GetStyle(getter_AddRefs(decl
));
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 direction
435 if (aDirection
.mHorizontal
) {
436 nsAutoString
widthstr(aSizeInfo
.width
);
437 if (!widthstr
.IsEmpty() &&
438 !Substring(widthstr
, widthstr
.Length() - 2, 2).EqualsLiteral("px"))
439 widthstr
.AppendLiteral("px");
440 decl
->SetProperty(NS_LITERAL_STRING("width"), widthstr
, EmptyString());
442 if (aDirection
.mVertical
) {
443 nsAutoString
heightstr(aSizeInfo
.height
);
444 if (!heightstr
.IsEmpty() &&
445 !Substring(heightstr
, heightstr
.Length() - 2, 2).EqualsLiteral("px"))
446 heightstr
.AppendLiteral("px");
447 decl
->SetProperty(NS_LITERAL_STRING("height"), heightstr
, EmptyString());
454 nsResizerFrame::MaybePersistOriginalSize(nsIContent
* aContent
,
455 const SizeInfo
& aSizeInfo
)
459 aContent
->GetProperty(nsGkAtoms::_moz_original_size
, &rv
);
460 if (rv
!= NS_PROPTABLE_PROP_NOT_THERE
)
463 nsAutoPtr
<SizeInfo
> sizeInfo(new SizeInfo(aSizeInfo
));
464 rv
= aContent
->SetProperty(nsGkAtoms::_moz_original_size
, sizeInfo
.get(),
465 nsINode::DeleteProperty
<nsResizerFrame::SizeInfo
>);
466 if (NS_SUCCEEDED(rv
))
471 nsResizerFrame::RestoreOriginalSize(nsIContent
* aContent
)
475 static_cast<SizeInfo
*>(aContent
->GetProperty(nsGkAtoms::_moz_original_size
,
480 NS_ASSERTION(sizeInfo
, "We set a null sizeInfo!?");
481 Direction direction
= {1, 1};
482 ResizeContent(aContent
, direction
, *sizeInfo
, nullptr);
483 aContent
->DeleteProperty(nsGkAtoms::_moz_original_size
);
486 /* returns a Direction struct containing the horizontal and vertical direction
488 nsResizerFrame::Direction
489 nsResizerFrame::GetDirection()
491 static const nsIContent::AttrValuesArray strings
[] =
492 {&nsGkAtoms::topleft
, &nsGkAtoms::top
, &nsGkAtoms::topright
,
493 &nsGkAtoms::left
, &nsGkAtoms::right
,
494 &nsGkAtoms::bottomleft
, &nsGkAtoms::bottom
, &nsGkAtoms::bottomright
,
495 &nsGkAtoms::bottomstart
, &nsGkAtoms::bottomend
,
498 static const Direction directions
[] =
499 {{-1, -1}, {0, -1}, {1, -1},
501 {-1, 1}, {0, 1}, {1, 1},
506 return directions
[0]; // default: topleft
508 int32_t index
= GetContent()->FindAttrValueIn(kNameSpaceID_None
,
510 strings
, eCaseMatters
);
512 return directions
[0]; // default: topleft
513 else if (index
>= 8 && StyleVisibility()->mDirection
== NS_STYLE_DIRECTION_RTL
) {
514 // Directions 8 and higher are RTL-aware directions and should reverse the
515 // horizontal component if RTL.
516 Direction direction
= directions
[index
];
517 direction
.mHorizontal
*= -1;
520 return directions
[index
];
524 nsResizerFrame::MouseClicked(nsPresContext
* aPresContext
,
525 WidgetMouseEvent
* aEvent
)
527 // Execute the oncommand event handler.
528 nsContentUtils::DispatchXULCommand(mContent
,
529 aEvent
&& aEvent
->mFlags
.mIsTrusted
);