Bug 1825212 [wpt PR 39266] - [@scope] Propagate proximity from SubResult, a=testonly
[gecko.git] / dom / xul / XULResizerElement.cpp
bloba0dbb485d52dc93c41824287988b4ec9f4c27b32
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"
17 #include "nsIFrame.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();
36 if (touchEvent) {
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) {
40 return false;
43 const dom::Touch* touch = touchEvent->mTouches.SafeElementAt(0);
44 if (!touch) {
45 return false;
47 aPoint = touch->mRefPoint;
48 } else {
49 aPoint = aEvent->mRefPoint;
51 return true;
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[] = {
61 // clang-format off
62 nsGkAtoms::topleft, nsGkAtoms::top, nsGkAtoms::topright,
63 nsGkAtoms::left, nsGkAtoms::right,
64 nsGkAtoms::bottomleft, nsGkAtoms::bottom, nsGkAtoms::bottomright,
65 nsGkAtoms::bottomstart, nsGkAtoms::bottomend,
66 nullptr
67 // clang-format on
70 static const Direction directions[] = {
71 // clang-format off
72 {-1, -1}, {0, -1}, {1, -1},
73 {-1, 0}, {1, 0},
74 {-1, 1}, {0, 1}, {1, 1},
75 {-1, 1}, {1, 1}
76 // clang-format on
79 const auto* frame = GetPrimaryFrame();
80 if (!frame) {
81 return directions[0]; // default: topleft
84 int32_t index =
85 FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::dir, strings, eCaseMatters);
86 if (index < 0) {
87 return directions[0]; // default: topleft
90 if (index >= 8) {
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;
97 return direction;
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) {
114 return Nothing();
116 nsIFrame* frame = contentToResize->GetPrimaryFrame();
117 if (!frame) {
118 return Nothing();
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) {
130 case eTouchStart:
131 case eMouseDown: {
132 if (event.mClass == eTouchEventClass ||
133 (event.mClass == eMouseEventClass &&
134 event.AsMouseEvent()->mButton == MouseButton::ePrimary)) {
135 auto size = GetCurrentSize();
136 if (!size) {
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)) {
145 break;
147 mTrackingMouseMove = true;
148 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
149 doDefault = false;
151 } break;
153 case eTouchMove:
154 case eMouseMove: {
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();
161 if (!frame) {
162 break;
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)) {
173 break;
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
185 // direction.
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);
215 }();
217 // Only resize in a given direction if the new size doesn't match the
218 // current size.
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);
235 doDefault = false;
237 } break;
239 case eMouseClick: {
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);
248 } break;
250 case eTouchEnd:
251 case eMouseUp: {
252 if (event.mClass == eTouchEventClass ||
253 (event.mClass == eMouseEventClass &&
254 event.AsMouseEvent()->mButton == MouseButton::ePrimary)) {
255 mTrackingMouseMove = false;
256 PresShell::ReleaseCapturingContent();
257 doDefault = false;
259 } break;
261 case eMouseDoubleClick: {
262 if (event.AsMouseEvent()->mButton == MouseButton::ePrimary) {
263 if (nsIContent* contentToResize = GetContentToResize()) {
264 RestoreOriginalSize(contentToResize);
267 } break;
269 default:
270 break;
273 if (!doDefault) {
274 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
278 nsIContent* XULResizerElement::GetContentToResize() const {
279 if (!IsInComposedDoc()) {
280 return nullptr;
282 // Return the parent, but skip over native anonymous content
283 nsIContent* parent = GetParent();
284 return parent ? parent->FindFirstNonChromeOnlyAccessContent() : nullptr;
287 /* static */
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) {
294 return;
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
303 // direction
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());
321 /* static */
322 void XULResizerElement::MaybePersistOriginalSize(nsIContent* aContent,
323 const SizeInfo& aSizeInfo) {
324 nsresult rv;
325 aContent->GetProperty(nsGkAtoms::_moz_original_size, &rv);
326 if (rv != NS_PROPTABLE_PROP_NOT_THERE) {
327 return;
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();
339 /* static */
340 void XULResizerElement::RestoreOriginalSize(nsIContent* aContent) {
341 nsresult rv;
342 SizeInfo* sizeInfo = static_cast<SizeInfo*>(
343 aContent->GetProperty(nsGkAtoms::_moz_original_size, &rv));
344 if (NS_FAILED(rv)) {
345 return;
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