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/. */
7 #include "mozilla/dom/XULResizerElement.h"
8 #include "mozilla/dom/XULResizerElementBinding.h"
10 #include "mozilla/EventDispatcher.h"
11 #include "mozilla/PresShell.h"
12 #include "mozilla/dom/Document.h"
13 #include "mozilla/dom/DocumentInlines.h"
14 #include "mozilla/MouseEvents.h"
15 #include "nsContentUtils.h"
16 #include "nsICSSDeclaration.h"
18 #include "nsLayoutUtils.h"
19 #include "nsPresContext.h"
20 #include "nsStyledElement.h"
22 namespace mozilla::dom
{
24 nsXULElement
* NS_NewXULResizerElement(
25 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
) {
26 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo(aNodeInfo
);
27 auto* nim
= nodeInfo
->NodeInfoManager();
28 return new (nim
) XULResizerElement(nodeInfo
.forget());
31 static bool GetEventPoint(const WidgetGUIEvent
* aEvent
,
32 LayoutDeviceIntPoint
& aPoint
) {
33 NS_ENSURE_TRUE(aEvent
, false);
35 const WidgetTouchEvent
* touchEvent
= aEvent
->AsTouchEvent();
37 // return false if there is more than one touch on the page, or if
38 // we can't find a touch point
39 if (touchEvent
->mTouches
.Length() != 1) {
43 const dom::Touch
* touch
= touchEvent
->mTouches
.SafeElementAt(0);
47 aPoint
= touch
->mRefPoint
;
49 aPoint
= aEvent
->mRefPoint
;
54 JSObject
* XULResizerElement::WrapNode(JSContext
* aCx
,
55 JS::Handle
<JSObject
*> aGivenProto
) {
56 return XULResizerElement_Binding::Wrap(aCx
, this, aGivenProto
);
59 XULResizerElement::Direction
XULResizerElement::GetDirection() {
60 static const mozilla::dom::Element::AttrValuesArray strings
[] = {
62 nsGkAtoms::topleft
, nsGkAtoms::top
, nsGkAtoms::topright
,
63 nsGkAtoms::left
, nsGkAtoms::right
,
64 nsGkAtoms::bottomleft
, nsGkAtoms::bottom
, nsGkAtoms::bottomright
,
65 nsGkAtoms::bottomstart
, nsGkAtoms::bottomend
,
70 static const Direction directions
[] = {
72 {-1, -1}, {0, -1}, {1, -1},
74 {-1, 1}, {0, 1}, {1, 1},
79 const auto* frame
= GetPrimaryFrame();
81 return directions
[0]; // default: topleft
85 FindAttrValueIn(kNameSpaceID_None
, nsGkAtoms::dir
, strings
, eCaseMatters
);
87 return directions
[0]; // default: topleft
91 // Directions 8 and higher are RTL-aware directions and should reverse the
92 // horizontal component if RTL.
93 auto wm
= frame
->GetWritingMode();
94 if (wm
.IsPhysicalRTL()) {
95 Direction direction
= directions
[index
];
96 direction
.mHorizontal
*= -1;
101 return directions
[index
];
104 nsresult
XULResizerElement::PostHandleEvent(EventChainPostVisitor
& aVisitor
) {
105 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
) {
106 PostHandleEventInternal(aVisitor
);
108 return nsXULElement::PostHandleEvent(aVisitor
);
111 Maybe
<nsSize
> XULResizerElement::GetCurrentSize() const {
112 nsIContent
* contentToResize
= GetContentToResize();
113 if (!contentToResize
) {
116 nsIFrame
* frame
= contentToResize
->GetPrimaryFrame();
120 return Some(frame
->StylePosition()->mBoxSizing
== StyleBoxSizing::Content
121 ? frame
->GetContentRect().Size()
122 : frame
->GetRect().Size());
125 void XULResizerElement::PostHandleEventInternal(
126 EventChainPostVisitor
& aVisitor
) {
127 bool doDefault
= true;
128 const WidgetEvent
& event
= *aVisitor
.mEvent
;
129 switch (event
.mMessage
) {
132 if (event
.mClass
== eTouchEventClass
||
133 (event
.mClass
== eMouseEventClass
&&
134 event
.AsMouseEvent()->mButton
== MouseButton::ePrimary
)) {
135 auto size
= GetCurrentSize();
137 break; // don't do anything if there's nothing to resize
139 // cache the content rectangle for the frame to resize
140 mMouseDownSize
= *size
;
142 // remember current mouse coordinates
143 auto* guiEvent
= event
.AsGUIEvent();
144 if (!GetEventPoint(guiEvent
, mMouseDownPoint
)) {
147 mTrackingMouseMove
= true;
148 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState
);
155 if (mTrackingMouseMove
) {
156 nsCOMPtr
<nsIContent
> contentToResize
= GetContentToResize();
157 if (!contentToResize
) {
158 break; // don't do anything if there's nothing to resize
160 nsIFrame
* frame
= contentToResize
->GetPrimaryFrame();
165 // both MouseMove and direction are negative when pointing to the
166 // top and left, and positive when pointing to the bottom and right
168 // retrieve the offset of the mousemove event relative to the mousedown.
169 // The difference is how much the resize needs to be
170 LayoutDeviceIntPoint refPoint
;
171 auto* guiEvent
= event
.AsGUIEvent();
172 if (!GetEventPoint(guiEvent
, refPoint
)) {
176 const nsPoint oldPos
= nsLayoutUtils::GetEventCoordinatesRelativeTo(
177 guiEvent
->mWidget
, mMouseDownPoint
, RelativeTo
{frame
});
178 const nsPoint newPos
= nsLayoutUtils::GetEventCoordinatesRelativeTo(
179 guiEvent
->mWidget
, refPoint
, RelativeTo
{frame
});
181 nsPoint
mouseMove(newPos
- oldPos
);
183 // Determine which direction to resize by checking the dir attribute.
184 // For windows and menus, ensure that it can be resized in that
186 Direction direction
= GetDirection();
188 const CSSIntSize newSize
= [&] {
189 nsSize newAuSize
= mMouseDownSize
;
190 // Check if there are any size constraints on this window.
191 newAuSize
.width
+= direction
.mHorizontal
* mouseMove
.x
;
192 newAuSize
.height
+= direction
.mVertical
* mouseMove
.y
;
193 if (newAuSize
.width
< AppUnitsPerCSSPixel() && mouseMove
.x
!= 0) {
194 newAuSize
.width
= AppUnitsPerCSSPixel();
196 if (newAuSize
.height
< AppUnitsPerCSSPixel() && mouseMove
.y
!= 0) {
197 newAuSize
.height
= AppUnitsPerCSSPixel();
200 // When changing the size in a direction, don't allow the new size to
201 // be less that the resizer's size. This ensures that content isn't
202 // resized too small as to make the resizer invisible.
203 if (auto* resizerFrame
= GetPrimaryFrame()) {
204 nsRect resizerRect
= resizerFrame
->GetRect();
205 if (newAuSize
.width
< resizerRect
.width
&& mouseMove
.x
!= 0) {
206 newAuSize
.width
= resizerRect
.width
;
208 if (newAuSize
.height
< resizerRect
.height
&& mouseMove
.y
!= 0) {
209 newAuSize
.height
= resizerRect
.height
;
213 // Convert the rectangle into css pixels.
214 return CSSIntSize::FromAppUnitsRounded(newAuSize
);
217 // Only resize in a given direction if the new size doesn't match the
219 if (auto currentSize
= GetCurrentSize()) {
220 auto newAuSize
= CSSIntSize::ToAppUnits(newSize
);
221 if (newAuSize
.width
== currentSize
->width
) {
222 direction
.mHorizontal
= 0;
224 if (newAuSize
.height
== currentSize
->height
) {
225 direction
.mVertical
= 0;
229 SizeInfo sizeInfo
, originalSizeInfo
;
230 sizeInfo
.width
.AppendInt(newSize
.width
);
231 sizeInfo
.height
.AppendInt(newSize
.height
);
232 ResizeContent(contentToResize
, direction
, sizeInfo
, &originalSizeInfo
);
233 MaybePersistOriginalSize(contentToResize
, originalSizeInfo
);
240 auto* mouseEvent
= event
.AsMouseEvent();
241 if (mouseEvent
->IsLeftClickEvent()) {
242 // Execute the oncommand event handler.
243 nsContentUtils::DispatchXULCommand(
244 this, false, nullptr, nullptr, mouseEvent
->IsControl(),
245 mouseEvent
->IsAlt(), mouseEvent
->IsShift(), mouseEvent
->IsMeta(),
246 mouseEvent
->mInputSource
, mouseEvent
->mButton
);
252 if (event
.mClass
== eTouchEventClass
||
253 (event
.mClass
== eMouseEventClass
&&
254 event
.AsMouseEvent()->mButton
== MouseButton::ePrimary
)) {
255 mTrackingMouseMove
= false;
256 PresShell::ReleaseCapturingContent();
261 case eMouseDoubleClick
: {
262 if (event
.AsMouseEvent()->mButton
== MouseButton::ePrimary
) {
263 if (nsIContent
* contentToResize
= GetContentToResize()) {
264 RestoreOriginalSize(contentToResize
);
274 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
278 nsIContent
* XULResizerElement::GetContentToResize() const {
279 if (!IsInComposedDoc()) {
282 // Return the parent, but skip over native anonymous content
283 nsIContent
* parent
= GetParent();
284 return parent
? parent
->FindFirstNonChromeOnlyAccessContent() : nullptr;
288 void XULResizerElement::ResizeContent(nsIContent
* aContent
,
289 const Direction
& aDirection
,
290 const SizeInfo
& aSizeInfo
,
291 SizeInfo
* aOriginalSizeInfo
) {
292 RefPtr inlineStyleContent
= nsStyledElement::FromNode(aContent
);
293 if (!inlineStyleContent
) {
296 nsCOMPtr
<nsICSSDeclaration
> decl
= inlineStyleContent
->Style();
297 if (aOriginalSizeInfo
) {
298 decl
->GetPropertyValue("width"_ns
, aOriginalSizeInfo
->width
);
299 decl
->GetPropertyValue("height"_ns
, aOriginalSizeInfo
->height
);
302 // only set the property if the element could have changed in that
304 if (aDirection
.mHorizontal
) {
305 nsAutoCString
widthstr(aSizeInfo
.width
);
306 if (!widthstr
.IsEmpty() && !StringEndsWith(widthstr
, "px"_ns
)) {
307 widthstr
.AppendLiteral("px");
309 decl
->SetProperty("width"_ns
, widthstr
, ""_ns
, IgnoreErrors());
312 if (aDirection
.mVertical
) {
313 nsAutoCString
heightstr(aSizeInfo
.height
);
314 if (!heightstr
.IsEmpty() && !StringEndsWith(heightstr
, "px"_ns
)) {
315 heightstr
.AppendLiteral("px");
317 decl
->SetProperty("height"_ns
, heightstr
, ""_ns
, IgnoreErrors());
322 void XULResizerElement::MaybePersistOriginalSize(nsIContent
* aContent
,
323 const SizeInfo
& aSizeInfo
) {
325 aContent
->GetProperty(nsGkAtoms::_moz_original_size
, &rv
);
326 if (rv
!= NS_PROPTABLE_PROP_NOT_THERE
) {
330 UniquePtr
<SizeInfo
> sizeInfo(new SizeInfo(aSizeInfo
));
331 rv
= aContent
->SetProperty(
332 nsGkAtoms::_moz_original_size
, sizeInfo
.get(),
333 nsINode::DeleteProperty
<XULResizerElement::SizeInfo
>);
334 if (NS_SUCCEEDED(rv
)) {
335 Unused
<< sizeInfo
.release();
340 void XULResizerElement::RestoreOriginalSize(nsIContent
* aContent
) {
342 SizeInfo
* sizeInfo
= static_cast<SizeInfo
*>(
343 aContent
->GetProperty(nsGkAtoms::_moz_original_size
, &rv
));
348 NS_ASSERTION(sizeInfo
, "We set a null sizeInfo!?");
349 Direction direction
= {1, 1};
350 ResizeContent(aContent
, direction
, *sizeInfo
, nullptr);
351 aContent
->RemoveProperty(nsGkAtoms::_moz_original_size
);
354 } // namespace mozilla::dom