Bug 1591736 - Fix AddonManagerWebAPI::IsAPIEnabled in out-of-process iframes r=mixedpuppy
[gecko.git] / layout / xul / nsResizerFrame.cpp
blob2262f3a27c2ad425d47a0a2cda7da01f2b35fb2b
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 "nsAutoPtr.h"
8 #include "nsCOMPtr.h"
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"
29 #include "nsError.h"
30 #include "nsICSSDeclaration.h"
31 #include "nsStyledElement.h"
32 #include <algorithm>
34 using namespace mozilla;
37 // NS_NewResizerFrame
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) {
56 return NS_OK;
59 AutoWeakFrame weakFrame(this);
60 bool doDefault = true;
62 switch (aEvent->mMessage) {
63 case eTouchStart:
64 case eMouseDown: {
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());
87 doDefault = false;
88 } else {
89 // If there is no window, then resizing isn't allowed.
90 if (!window) break;
92 doDefault = false;
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();
113 // we're tracking
114 mTrackingMouseMove = true;
116 PresShell::SetCapturingContent(GetContent(),
117 CaptureFlags::IgnoreAllowedState);
119 } break;
121 case eTouchEnd:
122 case eMouseUp: {
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();
131 doDefault = false;
133 } break;
135 case eTouchMove:
136 case eMouseMove: {
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
162 // direction.
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,
169 mouseMove);
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;
179 if (window) {
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.
194 if (window) {
195 nsCOMPtr<nsIScreen> screen;
196 nsCOMPtr<nsIScreenManager> sm(
197 do_GetService("@mozilla.org/gfx/screenmanager;1"));
198 if (sm) {
199 CSSIntRect frameRect = GetScreenRect();
200 // ScreenForRect requires display pixels, so scale from device pix
201 double scale;
202 window->GetUnscaledDevicePixelsPerCSSPixel(&scale);
203 sm->ScreenForRect(NSToIntRound(frameRect.x / scale),
204 NSToIntRound(frameRect.y / scale), 1, 1,
205 getter_AddRefs(screen));
206 if (screen) {
207 LayoutDeviceIntRect screenRect;
208 screen->GetRect(&screenRect.x, &screenRect.y, &screenRect.width,
209 &screenRect.height);
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
225 // resizing.
226 LayoutDeviceIntRect::FromAppUnitsToInside(rootScreenRect,
227 appPerDev),
228 popupLevel);
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;
243 nsIntRect cssRect =
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,
262 &originalSizeInfo);
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
268 // right place.
269 if (weakFrame.IsAlive() &&
270 (oldRect.x != rect.x || oldRect.y != rect.y) &&
271 (!menuPopupFrame->IsAnchored() ||
272 menuPopupFrame->PopupLevel() != ePopupLevelParent)) {
273 CSSPoint cssPos =
274 rect.TopLeft() / aPresContext->CSSToDevPixelScale();
275 menuPopupFrame->MoveTo(RoundedToInt(cssPos), true);
277 } else {
278 window->SetPositionAndSize(
279 rect.x, rect.y, rect.width, rect.height,
280 nsIBaseWindow::eRepaint); // do the repaint.
283 doDefault = false;
285 } break;
287 case eMouseClick: {
288 WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
289 if (mouseEvent->IsLeftClickEvent()) {
290 MouseClicked(mouseEvent);
292 break;
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());
303 if (menuPopupFrame)
304 break; // Don't restore original sizing for menupopup frames until
305 // we handle screen constraints here. (Bug 357725)
307 RestoreOriginalSize(contentToResize);
310 break;
312 default:
313 break;
316 if (!doDefault) *aEventStatus = nsEventStatus_eConsumeNoDefault;
318 if (doDefault && weakFrame.IsAlive())
319 return nsTitleBarFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
321 return NS_OK;
324 nsIContent* nsResizerFrame::GetContentToResize(mozilla::PresShell* aPresShell,
325 nsIBaseWindow** aWindow) {
326 *aWindow = nullptr;
328 nsAutoString elementid;
329 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::element,
330 elementid);
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();
335 while (popup) {
336 nsMenuPopupFrame* popupFrame = do_QueryFrame(popup);
337 if (popupFrame) {
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()) {
352 return nullptr;
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();
360 if (docShell) {
361 nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
362 docShell->GetTreeOwner(getter_AddRefs(treeOwner));
363 if (treeOwner) {
364 CallQueryInterface(treeOwner, aWindow);
369 return nullptr;
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,
383 int32_t aMovement,
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;
399 /* static */
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);
422 } else {
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
435 // direction
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,
450 EmptyString());
456 /* static */
457 void nsResizerFrame::MaybePersistOriginalSize(nsIContent* aContent,
458 const SizeInfo& aSizeInfo) {
459 nsresult rv;
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();
470 /* static */
471 void nsResizerFrame::RestoreOriginalSize(nsIContent* aContent) {
472 nsresult rv;
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[] = {
487 // clang-format off
488 nsGkAtoms::topleft, nsGkAtoms::top, nsGkAtoms::topright,
489 nsGkAtoms::left, nsGkAtoms::right,
490 nsGkAtoms::bottomleft, nsGkAtoms::bottom, nsGkAtoms::bottomright,
491 nsGkAtoms::bottomstart, nsGkAtoms::bottomend,
492 nullptr
493 // clang-format on
496 static const Direction directions[] = {
497 // clang-format off
498 {-1, -1}, {0, -1}, {1, -1},
499 {-1, 0}, {1, 0},
500 {-1, 1}, {0, 1}, {1, 1},
501 {-1, 1}, {1, 1}
502 // clang-format on
505 if (!GetContent()) {
506 return directions[0]; // default: topleft
509 int32_t index = mContent->AsElement()->FindAttrValueIn(
510 kNameSpaceID_None, nsGkAtoms::dir, strings, eCaseMatters);
511 if (index < 0) {
512 return directions[0]; // default: topleft
515 if (index >= 8) {
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;
522 return direction;
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);