Bug 1590376 part 1 - Add a XULResizerElement and move all nsResizerFrame's event handling code there. r=ntim,smaug

I'm mostly moving the code verbatim, but I excluded a few bits
that handled resizers inside menu popup frames, e.g.
https://searchfox.org/mozilla-central/rev/7bb1cc6abf6634b2a20f71935e1e519e73402b63/layout/xul/nsResizerFrame.cpp#165-170
I don't think we need that functionallity anymore and it
simplifies the code to exclude it.

Differential Revision: https://phabricator.services.mozilla.com/D105926
This commit is contained in:
Mats Palmgren
2022-06-07 09:30:01 +00:00
parent 59361fecff
commit 7bcebb2935
15 changed files with 568 additions and 964 deletions

View File

@@ -71,6 +71,7 @@
#include "mozilla/dom/XULFrameElementBinding.h"
#include "mozilla/dom/XULMenuElementBinding.h"
#include "mozilla/dom/XULPopupElementBinding.h"
#include "mozilla/dom/XULResizerElementBinding.h"
#include "mozilla/dom/XULTextElementBinding.h"
#include "mozilla/dom/XULTreeElementBinding.h"
#include "mozilla/dom/Promise.h"
@@ -3838,6 +3839,8 @@ bool HTMLConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
if (definition->mLocalName == nsGkAtoms::description ||
definition->mLocalName == nsGkAtoms::label) {
cb = XULTextElement_Binding::GetConstructorObject;
} else if (definition->mLocalName == nsGkAtoms::resizer) {
cb = XULResizerElement_Binding::GetConstructorObject;
} else if (definition->mLocalName == nsGkAtoms::menupopup ||
definition->mLocalName == nsGkAtoms::popup ||
definition->mLocalName == nsGkAtoms::panel ||

View File

@@ -0,0 +1,10 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
[ChromeOnly, Exposed=Window]
interface XULResizerElement : XULElement
{
[HTMLConstructor] constructor();
};

View File

@@ -1012,6 +1012,7 @@ WEBIDL_FILES = [
"XULCommandEvent.webidl",
"XULElement.webidl",
"XULPopupElement.webidl",
"XULResizerElement.webidl",
]
if CONFIG["MOZ_DOM_STREAMS"]:

View File

@@ -0,0 +1,465 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/XULResizerElement.h"
#include "mozilla/dom/XULResizerElementBinding.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/MouseEvents.h"
#include "nsContentUtils.h"
#include "nsDocShell.h"
#include "nsIBaseWindow.h"
#include "nsICSSDeclaration.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIFrame.h"
#include "nsIScreenManager.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsStyledElement.h"
#ifdef MOZ_WAYLAND
# include "mozilla/WidgetUtilsGtk.h"
# define IS_WAYLAND_DISPLAY() mozilla::widget::GdkIsWaylandDisplay()
#else
# define IS_WAYLAND_DISPLAY() false
#endif
namespace mozilla::dom {
nsXULElement* NS_NewXULResizerElement(
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) {
RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
auto* nim = nodeInfo->NodeInfoManager();
return new (nim) XULResizerElement(nodeInfo.forget());
}
static bool GetEventPoint(const WidgetGUIEvent* aEvent,
LayoutDeviceIntPoint& aPoint) {
NS_ENSURE_TRUE(aEvent, false);
const WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
if (touchEvent) {
// return false if there is more than one touch on the page, or if
// we can't find a touch point
if (touchEvent->mTouches.Length() != 1) {
return false;
}
const dom::Touch* touch = touchEvent->mTouches.SafeElementAt(0);
if (!touch) {
return false;
}
aPoint = touch->mRefPoint;
} else {
aPoint = aEvent->mRefPoint;
}
return true;
}
JSObject* XULResizerElement::WrapNode(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return XULResizerElement_Binding::Wrap(aCx, this, aGivenProto);
}
XULResizerElement::Direction XULResizerElement::GetDirection() {
static const mozilla::dom::Element::AttrValuesArray strings[] = {
// clang-format off
nsGkAtoms::topleft, nsGkAtoms::top, nsGkAtoms::topright,
nsGkAtoms::left, nsGkAtoms::right,
nsGkAtoms::bottomleft, nsGkAtoms::bottom, nsGkAtoms::bottomright,
nsGkAtoms::bottomstart, nsGkAtoms::bottomend,
nullptr
// clang-format on
};
static const Direction directions[] = {
// clang-format off
{-1, -1}, {0, -1}, {1, -1},
{-1, 0}, {1, 0},
{-1, 1}, {0, 1}, {1, 1},
{-1, 1}, {1, 1}
// clang-format on
};
const auto* frame = GetPrimaryFrame();
if (!frame) {
return directions[0]; // default: topleft
}
int32_t index =
FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::dir, strings, eCaseMatters);
if (index < 0) {
return directions[0]; // default: topleft
}
if (index >= 8) {
// Directions 8 and higher are RTL-aware directions and should reverse the
// horizontal component if RTL.
auto wm = frame->GetWritingMode();
if (wm.IsPhysicalRTL()) {
Direction direction = directions[index];
direction.mHorizontal *= -1;
return direction;
}
}
return directions[index];
}
nsresult XULResizerElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
PostHandleEventInternal(aVisitor);
}
return nsXULElement::PostHandleEvent(aVisitor);
}
void XULResizerElement::PostHandleEventInternal(
EventChainPostVisitor& aVisitor) {
bool doDefault = true;
const WidgetEvent& event = *aVisitor.mEvent;
switch (event.mMessage) {
case eTouchStart:
case eMouseDown: {
if (event.mClass == eTouchEventClass ||
(event.mClass == eMouseEventClass &&
event.AsMouseEvent()->mButton == MouseButton::ePrimary)) {
nsCOMPtr<nsIBaseWindow> window;
nsIContent* contentToResize =
GetContentToResize(getter_AddRefs(window));
if (!window && !contentToResize) {
break; // don't do anything if there's nothing to resize
}
auto* guiEvent = event.AsGUIEvent();
if (contentToResize) {
nsIFrame* frame = contentToResize->GetPrimaryFrame();
if (!frame) {
break;
}
// cache the content rectangle for the frame to resize
// GetScreenRectInAppUnits returns the border box rectangle, so
// adjust to get the desired content rectangle.
nsRect rect = frame->GetScreenRectInAppUnits();
if (frame->StylePosition()->mBoxSizing == StyleBoxSizing::Content) {
rect.Deflate(frame->GetUsedBorderAndPadding());
}
mMouseDownRect = LayoutDeviceIntRect::FromAppUnitsToNearest(
rect, frame->PresContext()->AppUnitsPerDevPixel());
} else {
// ask the widget implementation to begin a resize drag if it can
Direction direction = GetDirection();
nsresult rv = guiEvent->mWidget->BeginResizeDrag(
const_cast<WidgetGUIEvent*>(guiEvent), direction.mHorizontal,
direction.mVertical);
// for native drags, don't set the fields below
if (rv != NS_ERROR_NOT_IMPLEMENTED) {
break;
}
// if there's no native resize support, we need to do window
// resizing ourselves
window->GetPositionAndSize(&mMouseDownRect.x, &mMouseDownRect.y,
&mMouseDownRect.width,
&mMouseDownRect.height);
}
// remember current mouse coordinates
LayoutDeviceIntPoint refPoint;
if (!GetEventPoint(guiEvent, refPoint)) {
break;
}
mMouseDownPoint = refPoint + guiEvent->mWidget->WidgetToScreenOffset();
mTrackingMouseMove = true;
PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
doDefault = false;
}
} break;
case eTouchMove:
case eMouseMove: {
if (mTrackingMouseMove) {
nsCOMPtr<nsIBaseWindow> window;
nsCOMPtr<nsIContent> contentToResize =
GetContentToResize(getter_AddRefs(window));
if (!window && !contentToResize) {
break; // don't do anything if there's nothing to resize
}
nsIFrame* frame = contentToResize ? contentToResize->GetPrimaryFrame()
: GetPrimaryFrame();
if (!frame) {
break;
}
// both MouseMove and direction are negative when pointing to the
// top and left, and positive when pointing to the bottom and right
// retrieve the offset of the mousemove event relative to the mousedown.
// The difference is how much the resize needs to be
LayoutDeviceIntPoint refPoint;
auto* guiEvent = event.AsGUIEvent();
if (!GetEventPoint(guiEvent, refPoint)) {
break;
}
LayoutDeviceIntPoint screenPoint =
refPoint + guiEvent->mWidget->WidgetToScreenOffset();
LayoutDeviceIntPoint mouseMove(screenPoint - mMouseDownPoint);
// Determine which direction to resize by checking the dir attribute.
// For windows and menus, ensure that it can be resized in that
// direction.
Direction direction = GetDirection();
LayoutDeviceIntRect rect = mMouseDownRect;
// Check if there are any size constraints on this window.
widget::SizeConstraints sizeConstraints;
if (window) {
nsCOMPtr<nsIWidget> widget;
window->GetMainWidget(getter_AddRefs(widget));
sizeConstraints = widget->GetSizeConstraints();
}
AdjustDimensions(&rect.x, &rect.width, sizeConstraints.mMinSize.width,
sizeConstraints.mMaxSize.width, mouseMove.x,
direction.mHorizontal);
AdjustDimensions(&rect.y, &rect.height, sizeConstraints.mMinSize.height,
sizeConstraints.mMaxSize.height, mouseMove.y,
direction.mVertical);
// Don't allow resizing a window or a popup past the edge of the screen,
// so adjust the rectangle to fit within the available screen area.
// Don't check it on Wayland as we can't get absolute window position
// there.
if (window && !IS_WAYLAND_DISPLAY()) {
nsCOMPtr<nsIScreen> screen;
nsCOMPtr<nsIScreenManager> sm(
do_GetService("@mozilla.org/gfx/screenmanager;1"));
if (sm) {
CSSIntRect frameRect = frame->GetScreenRect();
// ScreenForRect requires display pixels, so scale from device pix
double scale;
window->GetUnscaledDevicePixelsPerCSSPixel(&scale);
sm->ScreenForRect(NSToIntRound(frameRect.x / scale),
NSToIntRound(frameRect.y / scale), 1, 1,
getter_AddRefs(screen));
if (screen) {
LayoutDeviceIntRect screenRect;
screen->GetRect(&screenRect.x, &screenRect.y, &screenRect.width,
&screenRect.height);
rect.IntersectRect(rect, screenRect);
}
}
}
if (contentToResize) {
// convert the rectangle into css pixels. When changing the size in a
// direction, don't allow the new size to be less that the resizer's
// size. This ensures that content isn't resized too small as to make
// the resizer invisible.
nsRect appUnitsRect =
ToAppUnits(rect.ToUnknownRect(),
frame->PresContext()->AppUnitsPerDevPixel());
if (auto* resizerFrame = GetPrimaryFrame()) {
nsRect frameRect = resizerFrame->GetRect();
if (appUnitsRect.width < frameRect.width && mouseMove.x)
appUnitsRect.width = frameRect.width;
if (appUnitsRect.height < frameRect.height && mouseMove.y)
appUnitsRect.height = frameRect.height;
}
nsIntRect cssRect =
appUnitsRect.ToInsidePixels(AppUnitsPerCSSPixel());
SizeInfo sizeInfo, originalSizeInfo;
sizeInfo.width.AppendInt(cssRect.width);
sizeInfo.height.AppendInt(cssRect.height);
ResizeContent(contentToResize, direction, sizeInfo,
&originalSizeInfo);
MaybePersistOriginalSize(contentToResize, originalSizeInfo);
} else {
window->SetPositionAndSize(
rect.x, rect.y, rect.width, rect.height,
nsIBaseWindow::eRepaint); // do the repaint.
}
doDefault = false;
}
} break;
case eMouseClick: {
auto* mouseEvent = event.AsMouseEvent();
if (mouseEvent->IsLeftClickEvent()) {
// Execute the oncommand event handler.
nsContentUtils::DispatchXULCommand(
this, false, nullptr, nullptr, mouseEvent->IsControl(),
mouseEvent->IsAlt(), mouseEvent->IsShift(), mouseEvent->IsMeta(),
mouseEvent->mInputSource, mouseEvent->mButton);
}
} break;
case eTouchEnd:
case eMouseUp: {
if (event.mClass == eTouchEventClass ||
(event.mClass == eMouseEventClass &&
event.AsMouseEvent()->mButton == MouseButton::ePrimary)) {
mTrackingMouseMove = false;
PresShell::ReleaseCapturingContent();
doDefault = false;
}
} break;
case eMouseDoubleClick: {
if (event.AsMouseEvent()->mButton == MouseButton::ePrimary) {
nsCOMPtr<nsIBaseWindow> window;
nsIContent* contentToResize =
GetContentToResize(getter_AddRefs(window));
if (contentToResize) {
RestoreOriginalSize(contentToResize);
}
}
} break;
default:
break;
}
if (!doDefault) {
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
}
}
nsIContent* XULResizerElement::GetContentToResize(nsIBaseWindow** aWindow) {
*aWindow = nullptr;
Document* doc = GetComposedDoc();
if (!doc) {
return nullptr;
}
nsAutoString elementid;
GetAttr(kNameSpaceID_None, nsGkAtoms::element, elementid);
if (elementid.IsEmpty()) {
// don't allow resizing windows in content shells
if (nsPresContext* presContext = doc->GetPresContext();
presContext && !presContext->IsChrome()) {
// don't allow resizers in content shells, except for the viewport
// scrollbar which doesn't have a parent
nsIContent* nonNativeAnon = FindFirstNonChromeOnlyAccessContent();
if (!nonNativeAnon || nonNativeAnon->GetParent()) {
return nullptr;
}
}
// get the document and the window - should this be cached?
nsPIDOMWindowOuter* win = OwnerDoc()->GetWindow();
nsCOMPtr<nsIDocShell> docShell = win ? win->GetDocShell() : nullptr;
if (docShell) {
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
docShell->GetTreeOwner(getter_AddRefs(treeOwner));
if (treeOwner) {
CallQueryInterface(treeOwner, aWindow);
}
}
return nullptr;
}
if (elementid.EqualsLiteral("_parent")) {
// return the parent, but skip over native anonymous content
nsIContent* parent = GetParent();
return parent ? parent->FindFirstNonChromeOnlyAccessContent() : nullptr;
}
return doc->GetElementById(elementid);
}
/* static */
void XULResizerElement::AdjustDimensions(int32_t* aPos, int32_t* aSize,
int32_t aMinSize, int32_t aMaxSize,
int32_t aMovement,
int8_t aResizerDirection) {
int32_t oldSize = *aSize;
*aSize += aResizerDirection * aMovement;
// use one as a minimum size or the element could disappear
if (*aSize < 1) *aSize = 1;
// Constrain the size within the minimum and maximum size.
*aSize = std::max(aMinSize, std::min(aMaxSize, *aSize));
// For left and top resizers, the window must be moved left by the same
// amount that the window was resized.
if (aResizerDirection == -1) *aPos += oldSize - *aSize;
}
/* static */
void XULResizerElement::ResizeContent(nsIContent* aContent,
const Direction& aDirection,
const SizeInfo& aSizeInfo,
SizeInfo* aOriginalSizeInfo) {
if (RefPtr<nsStyledElement> inlineStyleContent =
nsStyledElement::FromNode(aContent)) {
nsICSSDeclaration* decl = inlineStyleContent->Style();
if (aOriginalSizeInfo) {
decl->GetPropertyValue("width"_ns, aOriginalSizeInfo->width);
decl->GetPropertyValue("height"_ns, aOriginalSizeInfo->height);
}
// only set the property if the element could have changed in that
// direction
if (aDirection.mHorizontal) {
nsAutoCString widthstr(aSizeInfo.width);
if (!widthstr.IsEmpty() &&
!Substring(widthstr, widthstr.Length() - 2, 2).EqualsLiteral("px"))
widthstr.AppendLiteral("px");
decl->SetProperty("width"_ns, widthstr, ""_ns, IgnoreErrors());
}
if (aDirection.mVertical) {
nsAutoCString heightstr(aSizeInfo.height);
if (!heightstr.IsEmpty() &&
!Substring(heightstr, heightstr.Length() - 2, 2).EqualsLiteral("px"))
heightstr.AppendLiteral("px");
decl->SetProperty("height"_ns, heightstr, ""_ns, IgnoreErrors());
}
}
}
/* static */
void XULResizerElement::MaybePersistOriginalSize(nsIContent* aContent,
const SizeInfo& aSizeInfo) {
nsresult rv;
aContent->GetProperty(nsGkAtoms::_moz_original_size, &rv);
if (rv != NS_PROPTABLE_PROP_NOT_THERE) {
return;
}
UniquePtr<SizeInfo> sizeInfo(new SizeInfo(aSizeInfo));
rv = aContent->SetProperty(
nsGkAtoms::_moz_original_size, sizeInfo.get(),
nsINode::DeleteProperty<XULResizerElement::SizeInfo>);
if (NS_SUCCEEDED(rv)) {
Unused << sizeInfo.release();
}
}
/* static */
void XULResizerElement::RestoreOriginalSize(nsIContent* aContent) {
nsresult rv;
SizeInfo* sizeInfo = static_cast<SizeInfo*>(
aContent->GetProperty(nsGkAtoms::_moz_original_size, &rv));
if (NS_FAILED(rv)) {
return;
}
NS_ASSERTION(sizeInfo, "We set a null sizeInfo!?");
Direction direction = {1, 1};
ResizeContent(aContent, direction, *sizeInfo, nullptr);
aContent->RemoveProperty(nsGkAtoms::_moz_original_size);
}
} // namespace mozilla::dom

View File

@@ -0,0 +1,80 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_XULResizerElement_h
#define mozilla_dom_XULResizerElement_h
#include "nsXULElement.h"
#include "Units.h"
class nsIBaseWindow;
namespace mozilla {
namespace dom {
nsXULElement* NS_NewXULResizerElement(
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo);
class XULResizerElement final : public nsXULElement {
public:
explicit XULResizerElement(
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
: nsXULElement(std::move(aNodeInfo)) {}
MOZ_CAN_RUN_SCRIPT
nsresult PostHandleEvent(mozilla::EventChainPostVisitor&) override;
private:
virtual ~XULResizerElement() = default;
JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
MOZ_CAN_RUN_SCRIPT
void PostHandleEventInternal(mozilla::EventChainPostVisitor&);
struct Direction {
int8_t mHorizontal;
int8_t mVertical;
};
Direction GetDirection();
nsIContent* GetContentToResize(nsIBaseWindow** aWindow);
/**
* Adjust the window position and size in a direction according to the mouse
* movement and the resizer direction. The minimum and maximum size is used
* to constrain the size.
*
* @param aPos left or top position
* @param aSize width or height
* @param aMinSize minimum width or height
* @param aMacSize maximum width or height
* @param aMovement the amount the mouse was moved
* @param aResizerDirection resizer direction returned by GetDirection
*/
static void AdjustDimensions(int32_t* aPos, int32_t* aSize, int32_t aMinSize,
int32_t aMaxSize, int32_t aMovement,
int8_t aResizerDirection);
struct SizeInfo {
nsCString width, height;
};
static void SizeInfoDtorFunc(void* aObject, nsAtom* aPropertyName,
void* aPropertyValue, void* aData);
static void ResizeContent(nsIContent* aContent, const Direction& aDirection,
const SizeInfo& aSizeInfo,
SizeInfo* aOriginalSizeInfo);
static void MaybePersistOriginalSize(nsIContent* aContent,
const SizeInfo& aSizeInfo);
static void RestoreOriginalSize(nsIContent* aContent);
LayoutDeviceIntRect mMouseDownRect;
LayoutDeviceIntPoint mMouseDownPoint;
bool mTrackingMouseMove = false;
};
} // namespace dom
} // namespace mozilla
#endif // XULResizerElement_h

View File

@@ -26,6 +26,7 @@ EXPORTS.mozilla.dom += [
"XULMenuElement.h",
"XULPersist.h",
"XULPopupElement.h",
"XULResizerElement.h",
"XULTextElement.h",
"XULTooltipElement.h",
"XULTreeElement.h",
@@ -45,6 +46,7 @@ UNIFIED_SOURCES += [
"XULMenuElement.cpp",
"XULPersist.cpp",
"XULPopupElement.cpp",
"XULResizerElement.cpp",
"XULTextElement.cpp",
"XULTooltipElement.cpp",
"XULTreeElement.cpp",

View File

@@ -14,6 +14,7 @@
#include "XULFrameElement.h"
#include "XULMenuElement.h"
#include "XULPopupElement.h"
#include "XULResizerElement.h"
#include "XULTextElement.h"
#include "XULTooltipElement.h"
#include "XULTreeElement.h"
@@ -155,6 +156,10 @@ nsXULElement* nsXULElement::Construct(
// them into account, otherwise you'll start getting "Illegal constructor"
// exceptions in chrome code.
RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;
if (nodeInfo->Equals(nsGkAtoms::resizer)) {
return NS_NewXULResizerElement(nodeInfo.forget());
}
if (nodeInfo->Equals(nsGkAtoms::label) ||
nodeInfo->Equals(nsGkAtoms::description)) {
auto* nim = nodeInfo->NodeInfoManager();

View File

@@ -244,8 +244,6 @@ nsIFrame* NS_NewTreeBodyFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewTitleBarFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsIFrame* NS_NewResizerFrame(PresShell* aPresShell, ComputedStyle* aStyle);
nsHTMLScrollFrame* NS_NewHTMLScrollFrame(PresShell* aPresShell,
ComputedStyle* aStyle, bool aIsRoot);
@@ -4127,7 +4125,6 @@ nsCSSFrameConstructor::FindXULTagData(const Element& aElement,
SCROLLABLE_XUL_CREATE(checkbox, NS_NewButtonBoxFrame),
SCROLLABLE_XUL_CREATE(radio, NS_NewButtonBoxFrame),
SCROLLABLE_XUL_CREATE(titlebar, NS_NewTitleBarFrame),
SCROLLABLE_XUL_CREATE(resizer, NS_NewResizerFrame),
SCROLLABLE_XUL_CREATE(toolbarpaletteitem, NS_NewBoxFrame),
SCROLLABLE_XUL_CREATE(treecolpicker, NS_NewButtonBoxFrame),
SIMPLE_XUL_CREATE(image, NS_NewImageBoxFrame),

View File

@@ -33,13 +33,6 @@
using namespace mozilla;
#ifdef MOZ_WAYLAND
# include "mozilla/WidgetUtilsGtk.h"
# define IS_WAYLAND_DISPLAY() mozilla::widget::GdkIsWaylandDisplay()
#else
# define IS_WAYLAND_DISPLAY() false
#endif
//
// NS_NewResizerFrame
//
@@ -55,467 +48,3 @@ nsResizerFrame::nsResizerFrame(ComputedStyle* aStyle,
nsPresContext* aPresContext)
: nsTitleBarFrame(aStyle, aPresContext, kClassID) {}
nsresult nsResizerFrame::HandleEvent(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) {
NS_ENSURE_ARG_POINTER(aEventStatus);
if (nsEventStatus_eConsumeNoDefault == *aEventStatus) {
return NS_OK;
}
AutoWeakFrame weakFrame(this);
bool doDefault = true;
switch (aEvent->mMessage) {
case eTouchStart:
case eMouseDown: {
if (aEvent->mClass == eTouchEventClass ||
(aEvent->mClass == eMouseEventClass &&
aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary)) {
nsCOMPtr<nsIBaseWindow> window;
mozilla::PresShell* presShell = aPresContext->GetPresShell();
nsIContent* contentToResize =
GetContentToResize(presShell, getter_AddRefs(window));
if (contentToResize) {
nsIFrame* frameToResize = contentToResize->GetPrimaryFrame();
if (!frameToResize) break;
// cache the content rectangle for the frame to resize
// GetScreenRectInAppUnits returns the border box rectangle, so
// adjust to get the desired content rectangle.
nsRect rect = frameToResize->GetScreenRectInAppUnits();
if (frameToResize->StylePosition()->mBoxSizing ==
StyleBoxSizing::Content) {
rect.Deflate(frameToResize->GetUsedBorderAndPadding());
}
mMouseDownRect = LayoutDeviceIntRect::FromAppUnitsToNearest(
rect, aPresContext->AppUnitsPerDevPixel());
doDefault = false;
} else {
// If there is no window, then resizing isn't allowed.
if (!window) break;
doDefault = false;
// ask the widget implementation to begin a resize drag if it can
Direction direction = GetDirection();
nsresult rv = aEvent->mWidget->BeginResizeDrag(
aEvent, direction.mHorizontal, direction.mVertical);
// for native drags, don't set the fields below
if (rv != NS_ERROR_NOT_IMPLEMENTED) break;
// if there's no native resize support, we need to do window
// resizing ourselves
window->GetPositionAndSize(&mMouseDownRect.x, &mMouseDownRect.y,
&mMouseDownRect.width,
&mMouseDownRect.height);
}
// remember current mouse coordinates
LayoutDeviceIntPoint refPoint;
if (!GetEventPoint(aEvent, refPoint)) return NS_OK;
mMouseDownPoint = refPoint + aEvent->mWidget->WidgetToScreenOffset();
// we're tracking
mTrackingMouseMove = true;
PresShell::SetCapturingContent(GetContent(),
CaptureFlags::IgnoreAllowedState);
}
} break;
case eTouchEnd:
case eMouseUp: {
if (aEvent->mClass == eTouchEventClass ||
(aEvent->mClass == eMouseEventClass &&
aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary)) {
// we're done tracking.
mTrackingMouseMove = false;
PresShell::ReleaseCapturingContent();
doDefault = false;
}
} break;
case eTouchMove:
case eMouseMove: {
if (mTrackingMouseMove) {
nsCOMPtr<nsIBaseWindow> window;
mozilla::PresShell* presShell = aPresContext->GetPresShell();
nsCOMPtr<nsIContent> contentToResize =
GetContentToResize(presShell, getter_AddRefs(window));
// check if the returned content really is a menupopup
nsMenuPopupFrame* menuPopupFrame = nullptr;
if (contentToResize) {
menuPopupFrame = do_QueryFrame(contentToResize->GetPrimaryFrame());
}
// both MouseMove and direction are negative when pointing to the
// top and left, and positive when pointing to the bottom and right
// retrieve the offset of the mousemove event relative to the mousedown.
// The difference is how much the resize needs to be
LayoutDeviceIntPoint refPoint;
if (!GetEventPoint(aEvent, refPoint)) return NS_OK;
LayoutDeviceIntPoint screenPoint =
refPoint + aEvent->mWidget->WidgetToScreenOffset();
LayoutDeviceIntPoint mouseMove(screenPoint - mMouseDownPoint);
// Determine which direction to resize by checking the dir attribute.
// For windows and menus, ensure that it can be resized in that
// direction.
Direction direction = GetDirection();
if (window || menuPopupFrame) {
if (menuPopupFrame) {
menuPopupFrame->CanAdjustEdges(
(direction.mHorizontal == -1) ? eSideLeft : eSideRight,
(direction.mVertical == -1) ? eSideTop : eSideBottom,
mouseMove);
}
} else if (!contentToResize) {
break; // don't do anything if there's nothing to resize
}
LayoutDeviceIntRect rect = mMouseDownRect;
// Check if there are any size constraints on this window.
widget::SizeConstraints sizeConstraints;
if (window) {
nsCOMPtr<nsIWidget> widget;
window->GetMainWidget(getter_AddRefs(widget));
sizeConstraints = widget->GetSizeConstraints();
}
AdjustDimensions(&rect.x, &rect.width, sizeConstraints.mMinSize.width,
sizeConstraints.mMaxSize.width, mouseMove.x,
direction.mHorizontal);
AdjustDimensions(&rect.y, &rect.height, sizeConstraints.mMinSize.height,
sizeConstraints.mMaxSize.height, mouseMove.y,
direction.mVertical);
// Don't allow resizing a window or a popup past the edge of the screen,
// so adjust the rectangle to fit within the available screen area.
// Don't check it on Wayland as we can't get absolute window position
// there.
if (window && !IS_WAYLAND_DISPLAY()) {
nsCOMPtr<nsIScreen> screen;
nsCOMPtr<nsIScreenManager> sm(
do_GetService("@mozilla.org/gfx/screenmanager;1"));
if (sm) {
CSSIntRect frameRect = GetScreenRect();
// ScreenForRect requires display pixels, so scale from device pix
double scale;
window->GetUnscaledDevicePixelsPerCSSPixel(&scale);
sm->ScreenForRect(NSToIntRound(frameRect.x / scale),
NSToIntRound(frameRect.y / scale), 1, 1,
getter_AddRefs(screen));
if (screen) {
LayoutDeviceIntRect screenRect;
screen->GetRect(&screenRect.x, &screenRect.y, &screenRect.width,
&screenRect.height);
rect.IntersectRect(rect, screenRect);
}
}
} else if (menuPopupFrame && !IS_WAYLAND_DISPLAY()) {
nsRect frameRect = menuPopupFrame->GetScreenRectInAppUnits();
nsIFrame* rootFrame = aPresContext->PresShell()->GetRootFrame();
nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits();
nsPopupLevel popupLevel = menuPopupFrame->PopupLevel();
int32_t appPerDev = aPresContext->AppUnitsPerDevPixel();
LayoutDeviceIntRect screenRect = menuPopupFrame->GetConstraintRect(
LayoutDeviceIntRect::FromAppUnitsToNearest(frameRect, appPerDev),
// round using ...ToInside as it's better to be a pixel too small
// than be too large. If the popup is too large it could get
// flipped to the opposite side of the anchor point while
// resizing.
LayoutDeviceIntRect::FromAppUnitsToInside(rootScreenRect,
appPerDev),
popupLevel);
rect.IntersectRect(rect, screenRect);
}
if (contentToResize) {
// convert the rectangle into css pixels. When changing the size in a
// direction, don't allow the new size to be less that the resizer's
// size. This ensures that content isn't resized too small as to make
// the resizer invisible.
nsRect appUnitsRect = ToAppUnits(rect.ToUnknownRect(),
aPresContext->AppUnitsPerDevPixel());
if (appUnitsRect.width < mRect.width && mouseMove.x)
appUnitsRect.width = mRect.width;
if (appUnitsRect.height < mRect.height && mouseMove.y)
appUnitsRect.height = mRect.height;
nsIntRect cssRect =
appUnitsRect.ToInsidePixels(AppUnitsPerCSSPixel());
LayoutDeviceIntRect oldRect;
AutoWeakFrame weakFrame(menuPopupFrame);
if (menuPopupFrame) {
nsCOMPtr<nsIWidget> widget = menuPopupFrame->GetWidget();
if (widget) oldRect = widget->GetScreenBounds();
// convert the new rectangle into outer window coordinates
LayoutDeviceIntPoint clientOffset = widget->GetClientOffset();
rect.x -= clientOffset.x;
rect.y -= clientOffset.y;
}
SizeInfo sizeInfo, originalSizeInfo;
sizeInfo.width.AppendInt(cssRect.width);
sizeInfo.height.AppendInt(cssRect.height);
ResizeContent(contentToResize, direction, sizeInfo,
&originalSizeInfo);
MaybePersistOriginalSize(contentToResize, originalSizeInfo);
// Move the popup to the new location unless it is anchored, since
// the position shouldn't change. nsMenuPopupFrame::SetPopupPosition
// will instead ensure that the popup's position is anchored at the
// right place.
if (weakFrame.IsAlive() &&
(oldRect.x != rect.x || oldRect.y != rect.y) &&
(!menuPopupFrame->IsAnchored() ||
menuPopupFrame->PopupLevel() != ePopupLevelParent)) {
CSSPoint cssPos =
rect.TopLeft() / aPresContext->CSSToDevPixelScale();
menuPopupFrame->MoveTo(RoundedToInt(cssPos), true);
}
} else {
window->SetPositionAndSize(
rect.x, rect.y, rect.width, rect.height,
nsIBaseWindow::eRepaint); // do the repaint.
}
doDefault = false;
}
} break;
case eMouseClick: {
WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
if (mouseEvent->IsLeftClickEvent()) {
MouseClicked(mouseEvent);
}
break;
}
case eMouseDoubleClick:
if (aEvent->AsMouseEvent()->mButton == MouseButton::ePrimary) {
nsCOMPtr<nsIBaseWindow> window;
mozilla::PresShell* presShell = aPresContext->GetPresShell();
nsIContent* contentToResize =
GetContentToResize(presShell, getter_AddRefs(window));
if (contentToResize) {
nsMenuPopupFrame* menuPopupFrame =
do_QueryFrame(contentToResize->GetPrimaryFrame());
if (menuPopupFrame)
break; // Don't restore original sizing for menupopup frames until
// we handle screen constraints here. (Bug 357725)
RestoreOriginalSize(contentToResize);
}
}
break;
default:
break;
}
if (!doDefault) *aEventStatus = nsEventStatus_eConsumeNoDefault;
if (doDefault && weakFrame.IsAlive())
return nsTitleBarFrame::HandleEvent(aPresContext, aEvent, aEventStatus);
return NS_OK;
}
nsIContent* nsResizerFrame::GetContentToResize(mozilla::PresShell* aPresShell,
nsIBaseWindow** aWindow) {
*aWindow = nullptr;
nsAutoString elementid;
mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::element,
elementid);
if (elementid.IsEmpty()) {
// If the resizer is in a popup, resize the popup's widget, otherwise
// resize the widget associated with the window.
nsIFrame* popup = GetParent();
while (popup) {
nsMenuPopupFrame* popupFrame = do_QueryFrame(popup);
if (popupFrame) {
return popupFrame->GetContent();
}
popup = popup->GetParent();
}
// don't allow resizing windows in content shells
if (!aPresShell->GetPresContext()->IsChrome()) {
// don't allow resizers in content shells, except for the viewport
// scrollbar which doesn't have a parent
nsIContent* nonNativeAnon =
mContent->FindFirstNonChromeOnlyAccessContent();
if (!nonNativeAnon || nonNativeAnon->GetParent()) {
return nullptr;
}
}
// get the document and the window - should this be cached?
if (nsPIDOMWindowOuter* domWindow =
aPresShell->GetDocument()->GetWindow()) {
nsCOMPtr<nsIDocShell> docShell = domWindow->GetDocShell();
if (docShell) {
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
docShell->GetTreeOwner(getter_AddRefs(treeOwner));
if (treeOwner) {
CallQueryInterface(treeOwner, aWindow);
}
}
}
return nullptr;
}
if (elementid.EqualsLiteral("_parent")) {
// return the parent, but skip over native anonymous content
nsIContent* parent = mContent->GetParent();
return parent ? parent->FindFirstNonChromeOnlyAccessContent() : nullptr;
}
return aPresShell->GetDocument()->GetElementById(elementid);
}
void nsResizerFrame::AdjustDimensions(int32_t* aPos, int32_t* aSize,
int32_t aMinSize, int32_t aMaxSize,
int32_t aMovement,
int8_t aResizerDirection) {
int32_t oldSize = *aSize;
*aSize += aResizerDirection * aMovement;
// use one as a minimum size or the element could disappear
if (*aSize < 1) *aSize = 1;
// Constrain the size within the minimum and maximum size.
*aSize = std::max(aMinSize, std::min(aMaxSize, *aSize));
// For left and top resizers, the window must be moved left by the same
// amount that the window was resized.
if (aResizerDirection == -1) *aPos += oldSize - *aSize;
}
/* static */
void nsResizerFrame::ResizeContent(nsIContent* aContent,
const Direction& aDirection,
const SizeInfo& aSizeInfo,
SizeInfo* aOriginalSizeInfo) {
if (RefPtr<nsStyledElement> inlineStyleContent =
nsStyledElement::FromNode(aContent)) {
nsICSSDeclaration* decl = inlineStyleContent->Style();
if (aOriginalSizeInfo) {
decl->GetPropertyValue("width"_ns, aOriginalSizeInfo->width);
decl->GetPropertyValue("height"_ns, aOriginalSizeInfo->height);
}
// only set the property if the element could have changed in that
// direction
if (aDirection.mHorizontal) {
nsAutoCString widthstr(aSizeInfo.width);
if (!widthstr.IsEmpty() &&
!Substring(widthstr, widthstr.Length() - 2, 2).EqualsLiteral("px"))
widthstr.AppendLiteral("px");
decl->SetProperty("width"_ns, widthstr, ""_ns, IgnoreErrors());
}
if (aDirection.mVertical) {
nsAutoCString heightstr(aSizeInfo.height);
if (!heightstr.IsEmpty() &&
!Substring(heightstr, heightstr.Length() - 2, 2).EqualsLiteral("px"))
heightstr.AppendLiteral("px");
decl->SetProperty("height"_ns, heightstr, ""_ns, IgnoreErrors());
}
}
}
/* static */
void nsResizerFrame::MaybePersistOriginalSize(nsIContent* aContent,
const SizeInfo& aSizeInfo) {
nsresult rv;
aContent->GetProperty(nsGkAtoms::_moz_original_size, &rv);
if (rv != NS_PROPTABLE_PROP_NOT_THERE) return;
UniquePtr<SizeInfo> sizeInfo(new SizeInfo(aSizeInfo));
rv = aContent->SetProperty(nsGkAtoms::_moz_original_size, sizeInfo.get(),
nsINode::DeleteProperty<nsResizerFrame::SizeInfo>);
if (NS_SUCCEEDED(rv)) {
Unused << sizeInfo.release();
}
}
/* static */
void nsResizerFrame::RestoreOriginalSize(nsIContent* aContent) {
nsresult rv;
SizeInfo* sizeInfo = static_cast<SizeInfo*>(
aContent->GetProperty(nsGkAtoms::_moz_original_size, &rv));
if (NS_FAILED(rv)) return;
NS_ASSERTION(sizeInfo, "We set a null sizeInfo!?");
Direction direction = {1, 1};
ResizeContent(aContent, direction, *sizeInfo, nullptr);
aContent->RemoveProperty(nsGkAtoms::_moz_original_size);
}
/* returns a Direction struct containing the horizontal and vertical direction
*/
nsResizerFrame::Direction nsResizerFrame::GetDirection() {
static const mozilla::dom::Element::AttrValuesArray strings[] = {
// clang-format off
nsGkAtoms::topleft, nsGkAtoms::top, nsGkAtoms::topright,
nsGkAtoms::left, nsGkAtoms::right,
nsGkAtoms::bottomleft, nsGkAtoms::bottom, nsGkAtoms::bottomright,
nsGkAtoms::bottomstart, nsGkAtoms::bottomend,
nullptr
// clang-format on
};
static const Direction directions[] = {
// clang-format off
{-1, -1}, {0, -1}, {1, -1},
{-1, 0}, {1, 0},
{-1, 1}, {0, 1}, {1, 1},
{-1, 1}, {1, 1}
// clang-format on
};
if (!GetContent()) {
return directions[0]; // default: topleft
}
int32_t index = mContent->AsElement()->FindAttrValueIn(
kNameSpaceID_None, nsGkAtoms::dir, strings, eCaseMatters);
if (index < 0) {
return directions[0]; // default: topleft
}
if (index >= 8) {
// Directions 8 and higher are RTL-aware directions and should reverse the
// horizontal component if RTL.
WritingMode wm = GetWritingMode();
if (wm.IsPhysicalRTL()) {
Direction direction = directions[index];
direction.mHorizontal *= -1;
return direction;
}
}
return directions[index];
}
void nsResizerFrame::MouseClicked(WidgetMouseEvent* aEvent) {
// Execute the oncommand event handler.
nsCOMPtr<nsIContent> content = mContent;
nsContentUtils::DispatchXULCommand(content, false, nullptr, nullptr,
aEvent->IsControl(), aEvent->IsAlt(),
aEvent->IsShift(), aEvent->IsMeta(),
aEvent->mInputSource, aEvent->mButton);
}

View File

@@ -6,78 +6,21 @@
#ifndef nsResizerFrame_h___
#define nsResizerFrame_h___
#include "mozilla/Attributes.h"
#include "mozilla/EventForwards.h"
#include "nsTitleBarFrame.h"
class nsIBaseWindow;
namespace mozilla {
class PresShell;
} // namespace mozilla
class nsResizerFrame final : public nsTitleBarFrame {
protected:
typedef mozilla::LayoutDeviceIntPoint LayoutDeviceIntPoint;
typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
struct Direction {
int8_t mHorizontal;
int8_t mVertical;
};
public:
NS_DECL_FRAMEARENA_HELPERS(nsResizerFrame)
friend nsIFrame* NS_NewResizerFrame(mozilla::PresShell* aPresShell,
ComputedStyle* aStyle);
explicit nsResizerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext);
nsResizerFrame(ComputedStyle* aStyle, nsPresContext* aPresContext);
virtual nsresult HandleEvent(nsPresContext* aPresContext,
mozilla::WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) override;
MOZ_CAN_RUN_SCRIPT_BOUNDARY
virtual void MouseClicked(mozilla::WidgetMouseEvent* aEvent) override;
protected:
nsIContent* GetContentToResize(mozilla::PresShell* aPresShell,
nsIBaseWindow** aWindow);
Direction GetDirection();
/**
* Adjust the window position and size in a direction according to the mouse
* movement and the resizer direction. The minimum and maximum size is used
* to constrain the size.
*
* @param aPos left or top position
* @param aSize width or height
* @param aMinSize minimum width or height
* @param aMacSize maximum width or height
* @param aMovement the amount the mouse was moved
* @param aResizerDirection resizer direction returned by GetDirection
*/
static void AdjustDimensions(int32_t* aPos, int32_t* aSize, int32_t aMinSize,
int32_t aMaxSize, int32_t aMovement,
int8_t aResizerDirection);
struct SizeInfo {
nsCString width, height;
};
static void SizeInfoDtorFunc(void* aObject, nsAtom* aPropertyName,
void* aPropertyValue, void* aData);
static void ResizeContent(nsIContent* aContent, const Direction& aDirection,
const SizeInfo& aSizeInfo,
SizeInfo* aOriginalSizeInfo);
static void MaybePersistOriginalSize(nsIContent* aContent,
const SizeInfo& aSizeInfo);
static void RestoreOriginalSize(nsIContent* aContent);
protected:
LayoutDeviceIntRect mMouseDownRect;
LayoutDeviceIntPoint mMouseDownPoint;
}; // class nsResizerFrame
#endif /* nsResizerFrame_h___ */

View File

@@ -1,8 +1,6 @@
[DEFAULT]
skip-if = os == 'android'
support-files =
window_resizer.xhtml
window_resizer_element.xhtml
windowminmaxsize1.xhtml
windowminmaxsize2.xhtml
windowminmaxsize3.xhtml
@@ -29,8 +27,6 @@ skip-if = os == 'linux' # No native mousedown event on Linux
[test_popupReflowPos.xhtml]
[test_popupSizeTo.xhtml]
[test_popupZoom.xhtml]
[test_resizer.xhtml]
skip-if = (verify && (os == 'win'))
[test_submenuClose.xhtml]
[test_windowminmaxsize.xhtml]
[test_resizer_ctrl_click.xhtml]

View File

@@ -1,97 +0,0 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
<!--
XUL <resizer> tests
-->
<window title="XUL resizer tests"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<!-- test results are displayed in the html:body -->
<body xmlns="http://www.w3.org/1999/xhtml">
</body>
<!-- test code goes here -->
<script type="application/javascript"><![CDATA[
SimpleTest.waitForExplicitFinish();
SimpleTest.ignoreAllUncaughtExceptions();
function openPopup()
{
document.getElementById("panel").
openPopupAtScreen(Math.round(window.mozInnerScreenX) + window.innerWidth - 130,
Math.round(window.mozInnerScreenY) + window.innerHeight - 130);
}
var step = 0;
function popupShown(event)
{
let panel = document.getElementById("panel");
if (step == 0) {
// check to make sure that the popup cannot be resized past the edges of
// the content area
var resizerrect = document.getElementById("resizer").getBoundingClientRect();
synthesizeMouse(document.documentElement, resizerrect.left + 5, resizerrect.top + 5, { type:"mousedown" });
synthesizeMouse(document.documentElement, resizerrect.left + 2000, resizerrect.top + 2000, { type:"mousemove" });
// allow a one pixel variance as rounding is always done to the inside
// of a rectangle.
var popuprect = panel.getBoundingClientRect();
ok(Math.round(popuprect.right) == window.innerWidth ||
Math.round(popuprect.right) == window.innerWidth - 1,
"resized to content edge width");
ok(Math.round(popuprect.bottom) == window.innerHeight ||
Math.round(popuprect.bottom) == window.innerHeight - 1,
"resized to content edge height");
resizerrect = document.getElementById("resizer").getBoundingClientRect();
synthesizeMouse(document.documentElement, resizerrect.left + 5, resizerrect.top + 5, { type:"mouseup" });
}
else {
// the popup is opened twice. Make sure that for the second time, the
// resized popup opens in the same direction as there should still be
// room for it
var popuprect = panel.getBoundingClientRect();
var marginLeft = parseFloat(getComputedStyle(panel).marginLeft);
var marginTop = parseFloat(getComputedStyle(panel).marginTop);
is(Math.round(popuprect.left - marginLeft), window.innerWidth - 130, "reopen popup left");
is(Math.round(popuprect.top - marginTop), window.innerHeight - 130, "reopen popup top");
}
event.target.hidePopup();
}
function doResizerWindowTests() {
step++;
if (step == 1) {
openPopup();
return;
}
if (/Mac/.test(navigator.platform)) {
window.openDialog("window_resizer.xhtml", "_blank", "left=200,top=200,outerWidth=300,outerHeight=300,chrome,noopener", window);
}
else {
// Skip window_resizer.xhtml tests.
todo(false, "We can't test GTK and Windows native drag resizing implementations.");
// Run window_resizer_element.xhtml test only.
lastResizerTest();
}
}
function lastResizerTest()
{
window.openDialog("window_resizer_element.xhtml", "_blank", "left=200,top=200,outerWidth=300,outerHeight=300,chrome,noopener", window);
}
SimpleTest.waitForFocus(openPopup);
]]></script>
<panel id="panel" onpopupshown="popupShown(event)" onpopuphidden="doResizerWindowTests()">
<resizer id="resizer" dir="bottomend" width="16" height="16"/>
<hbox width="50" height="50" flex="1"/>
</panel>
</window>

View File

@@ -22,7 +22,7 @@
<panel id="panel" onpopupshown="doPanelTest(this)" onpopuphidden="nextPopupTest(this)"
orient="vertical"
align="start" pack="start" style="appearance: none; margin: 0; border: 0; padding: 0;">
<resizer id="popupresizer" dir="bottomright" flex="1" width="60" height="60"
<hbox id="popupresizer" dir="bottomright" flex="1" width="60" height="60"
style="appearance: none; margin: 0; border: 0; padding: 0;"/>
</panel>
@@ -80,10 +80,6 @@ var popupTests = [
{ testname: "popup with maximum size",
maxwidth: 50, maxheight: 45,
width: 50, height: 45,
},
{ testname: "popup with minimum and size",
minwidth: 80, minheight: 70, maxwidth: 250, maxheight: 220,
width: 80, height: 70, last: true
}
];
@@ -155,24 +151,6 @@ function doPanelTest(panel)
is(rect.width, popupTests[gTestId].width, popupTests[gTestId].testname + " width");
is(rect.height, popupTests[gTestId].height, popupTests[gTestId].testname + " height");
if ('last' in popupTests[gTestId]) {
var resizer = document.getElementById("popupresizer");
synthesizeMouse(resizer, 4, 4, { type:"mousedown" });
synthesizeMouse(resizer, 800, 800, { type:"mousemove" });
rect = panel.getBoundingClientRect();
is(rect.width, 250, "Popup width after maximum resize");
is(rect.height, 220, "Popup height after maximum resize");
synthesizeMouse(resizer, -100, -100, { type:"mousemove" });
rect = panel.getBoundingClientRect();
is(rect.width, 80, "Popup width after minimum resize");
is(rect.height, 70, "Popup height after minimum resize");
synthesizeMouse(resizer, 4, 4, { type:"mouseup" });
}
panel.hidePopup();
}
@@ -196,11 +174,6 @@ function nextPopupTest(panel)
setattr("maxwidth");
setattr("maxheight");
// Remove the flexibility as it causes the resizer to not shrink down
// when resizing.
if ("last" in popupTests[gTestId])
document.getElementById("popupresizer").removeAttribute("flex");
// Prevent event loop starvation as a result of popup events being
// synchronous. See bug 1131576.
SimpleTest.executeSoon(() => {

View File

@@ -1,113 +0,0 @@
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
screenX="200" screenY="200" width="300" height="300"
onload="setTimeout(doTest, 0)">
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script><![CDATA[
var is = window.arguments[0].SimpleTest.is;
function doTest() {
// from test_resizer.xhtml
var expectX = 200;
var expectY = 200;
var expectXMost = 500;
var expectYMost = 500;
var screenScale = expectX/window.screenX;
var root = document.documentElement;
var oldScreenX = window.screenX;
var oldScreenY = window.screenY;
var oldWidth = window.outerWidth;
var oldHeight = window.outerHeight;
function testResizer(dx, dy) {
var offset = 20;
var scale = 5;
// target the centre of the resizer
var offsetX = window.innerWidth/2 + (window.innerWidth/3)*dx;
var offsetY = window.innerHeight/2 + (window.innerHeight/3)*dy;
for (var mouseX = -1; mouseX <= 1; ++mouseX) {
for (var mouseY = -1; mouseY <= 1; ++mouseY) {
var newExpectX = expectX;
var newExpectXMost = expectXMost;
var newExpectY = expectY;
var newExpectYMost = expectYMost;
if (dx < 0) {
newExpectX += mouseX*scale;
} else if (dx > 0) {
newExpectXMost += mouseX*scale;
}
if (dy < 0) {
newExpectY += mouseY*scale;
} else if (dy > 0) {
newExpectYMost += mouseY*scale;
}
synthesizeMouse(root, offsetX, offsetY, { type:"mousedown" });
synthesizeMouse(root, offsetX + mouseX*scale, offsetY + mouseY*scale, { type:"mousemove" });
is(window.screenX*screenScale, newExpectX,
"Bad x for " + dx + "," + dy + " moving " + mouseX + "," + mouseY);
is(window.screenY*screenScale, newExpectY,
"Bad y for " + dx + "," + dy + " moving " + mouseX + "," + mouseY);
is(window.outerWidth, newExpectXMost - newExpectX,
"Bad width for " + dx + "," + dy + " moving " + mouseX + "," + mouseY);
is(window.outerHeight, newExpectYMost - newExpectY,
"Bad height for " + dx + "," + dy + " moving " + mouseX + "," + mouseY);
// move it back before we release! Adjust for any window movement
synthesizeMouse(root, offsetX - (newExpectX - expectX),
offsetY - (newExpectY - expectY), { type:"mousemove" });
synthesizeMouse(root, offsetX, offsetY, { type:"mouseup" });
}
}
}
testResizer(-1, -1);
testResizer(-1, 0);
testResizer(-1, 1);
testResizer(0, -1);
testResizer(0, 1);
testResizer(1, -1);
testResizer(1, 0);
testResizer(1, 1);
var resizers = document.getElementsByTagName("resizer");
Array.prototype.forEach.call(resizers, function (element) {
is(getComputedStyle(element, "").cursor,
element.getAttribute("expectedcursor"),
"cursor for " + element.getAttribute("dir"));
});
// now check the cursors in rtl. The bottomend resizer
// should be reversed
document.documentElement.setAttribute("localedir", "rtl");
Array.prototype.forEach.call(resizers, function (element) {
is(getComputedStyle(element, "").cursor,
element.getAttribute("dir") == "bottomend" ? "sw-resize" :
element.getAttribute("expectedcursor"),
"cursor for " + element.getAttribute("dir"));
});
window.close();
window.arguments[0].lastResizerTest();
}
]]></script>
<hbox id="container" flex="1">
<vbox flex="1">
<resizer dir="topleft" expectedcursor="nw-resize" flex="1"/>
<resizer dir="left" expectedcursor="ew-resize" flex="1"/>
<resizer dir="bottomleft" expectedcursor="sw-resize" flex="1"/>
</vbox>
<vbox flex="1">
<resizer dir="top" expectedcursor="ns-resize" flex="1"/>
<resizer id="bottomend" dir="bottomend" expectedcursor="se-resize" flex="1"/>
<resizer dir="bottom" expectedcursor="ns-resize" flex="1"/>
</vbox>
<vbox flex="1">
<resizer dir="topright" expectedcursor="ne-resize" flex="1"/>
<resizer dir="right" expectedcursor="ew-resize" flex="1"/>
<resizer dir="bottomright" expectedcursor="se-resize" flex="1"/>
</vbox>
</hbox>
</window>

View File

@@ -1,190 +0,0 @@
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
align="start">
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script><![CDATA[
var is = window.arguments[0].SimpleTest.is;
window.onerror = window.arguments[0].onerror;
const anchorPositions =
[ "before_start", "before_end", "after_start", "after_end",
"start_before", "start_after", "end_before", "end_after", "overlap", "screen"];
var currentPosition;
function testResizer(resizerid, noShrink, hResize, vResize, testid)
{
var rect = document.getElementById(resizerid + "-container").getBoundingClientRect();
var resizer = document.getElementById(resizerid);
var resizerrect = resizer.getBoundingClientRect();
var originalX = resizerrect.left;
var originalY = resizerrect.top;
const scale = 20;
for (var mouseX = -1; mouseX <= 1; ++mouseX) {
for (var mouseY = -1; mouseY <= 1; ++mouseY) {
var expectedWidth = rect.width + hResize * mouseX * scale;
var expectedHeight = rect.height + vResize * mouseY * scale;
if (noShrink) {
if (mouseX == -1)
expectedWidth = rect.width;
if (mouseY == -1)
expectedHeight = rect.height;
}
synthesizeMouse(document.documentElement, originalX + 5, originalY + 5, { type:"mousedown" });
synthesizeMouse(document.documentElement, originalX + 5 + mouseX * scale,
originalY + 5 + mouseY * scale, { type:"mousemove" });
var newrect = document.getElementById(resizerid + "-container").getBoundingClientRect();
is(Math.round(newrect.width), Math.round(expectedWidth), "resize element " + resizerid +
" " + testid + " width moving " + mouseX + "," + mouseY + ",,," + hResize);
is(Math.round(newrect.height), Math.round(expectedHeight), "resize element " + resizerid +
" " + testid + " height moving " + mouseX + "," + mouseY);
// release
synthesizeMouse(document.documentElement, originalX + 5 + mouseX * scale,
originalY + 5 + mouseY * scale, { type:"mouseup" });
// return to the original size
synthesizeMouse(document.documentElement, originalX + 5 + mouseX * scale,
originalY + 5 + mouseY * scale, { type:"dblclick" });
var newrect = document.getElementById(resizerid + "-container").getBoundingClientRect();
is(Math.round(newrect.width), Math.round(rect.width), "resize element " + resizerid +
" " + testid + " doubleclicking to restore original size");
is(Math.round(newrect.height), Math.round(rect.height), "resize element " + resizerid +
" " + testid + " doubleclicking to restore original size");
}
}
}
function doTest() {
// first, check if a resizer with a element attribute set to an element that
// does not exist does not cause a problem
var resizer = document.getElementById("notfound");
synthesizeMouse(resizer, 5, 5, { type:"mousedown" });
synthesizeMouse(resizer, 10, 10, { type:"mousemove" });
synthesizeMouse(resizer, 5, 5, { type:"mouseup" });
testResizer("outside", true, 1, 1, "");
testResizer("html", true, 1, 1, "");
testResizer("inside", true, 1, 1, "");
testResizer("inside-large", false, 1, 1, "");
testResizer("inside-with-border", true, 1, 1, "");
document.getElementById("inside-popup-container").
openPopupAtScreen(Math.ceil(window.mozInnerScreenX) + 100, Math.ceil(window.mozInnerScreenY) + 100);
}
function popupShown(event)
{
testResizer("inside-popup", false, 1, 1, "");
document.getElementById("inside-popup-container").id = "outside-popup-container";
testResizer("outside-popup", false, 1, 1, "");
var resizerrect = document.getElementById("inside-popup").getBoundingClientRect();
synthesizeMouse(document.documentElement, resizerrect.left + 5, resizerrect.top + 5, { type:"mousedown" });
synthesizeMouse(document.documentElement, resizerrect.left + 2000, resizerrect.top + 2000, { type:"mousemove" });
var isMac = (navigator.platform.includes("Mac"));
var popuprect = document.getElementById("outside-popup-container").getBoundingClientRect();
// subtract 3 due to space left for panel dropshadow
is(Math.ceil(window.mozInnerScreenX) + popuprect.right,
(isMac ? screen.availLeft + screen.availWidth : screen.left + screen.width) - 3, "resized to edge width");
is(Math.ceil(window.mozInnerScreenY) + popuprect.bottom,
(isMac ? screen.availTop + screen.availHeight : screen.top + screen.height) - 3, "resized to edge height");
resizerrect = document.getElementById("inside-popup").getBoundingClientRect();
synthesizeMouse(document.documentElement, resizerrect.left + 5, resizerrect.top + 5, { type:"mouseup" });
event.target.hidePopup();
}
function popupHidden()
{
if (anchorPositions.length == 0) {
window.close();
window.arguments[0].SimpleTest.finish();
return;
}
currentPosition = anchorPositions.shift();
var anchor = document.getElementById("anchor");
var popup = document.getElementById("anchored-panel-container");
if (currentPosition == "screen")
popup.openPopupAtScreen(window.screenX + 100, window.screenY + 100);
else
popup.openPopup(anchor, currentPosition);
}
function anchoredPopupShown(event)
{
var leftAllowed = (!currentPosition.includes("end_") && !currentPosition.includes("_start"));
var rightAllowed = (!currentPosition.includes("start_") && !currentPosition.includes("_end"));
var topAllowed = (!currentPosition.includes("after_") && !currentPosition.includes("_before"));
var bottomAllowed = (!currentPosition.includes("before_") && !currentPosition.includes("_after"));
if (currentPosition == "overlap") {
leftAllowed = topAllowed = false;
rightAllowed = bottomAllowed = true;
}
var resizerTypes = [ "topleft", "top", "topright", "left", "right",
"bottomleft", "bottom", "bottomright", "bottomend" ];
for (var r = 0; r < resizerTypes.length; r++) {
var resizerType = resizerTypes[r];
var horiz = 0, vert = 0;
if (leftAllowed && resizerType.includes("left")) horiz = -1;
else if (rightAllowed && (resizerType.includes("right") || resizerType == "bottomend")) horiz = 1;
if (topAllowed && resizerType.includes("top")) vert = -1;
else if (bottomAllowed && resizerType.includes("bottom")) vert = 1;
document.getElementById("anchored-panel").dir = resizerType;
testResizer("anchored-panel", false, horiz, vert, currentPosition + " " + resizerType);
}
event.target.hidePopup();
}
SimpleTest.executeSoon(() => {
window.arguments[0].SimpleTest.waitForFocus(doTest, window);
});
]]></script>
<resizer id="outside" dir="bottomend" element="outside-container"/>
<resizer id="notfound" dir="bottomend" element="nothing"/>
<hbox id="outside-container">
<hbox minwidth="46" minheight="39"/>
</hbox>
<html:div id="html-container" xmlns:html="http://www.w3.org/1999/xhtml">
<html:button>One</html:button><html:br/>
<resizer id="html" dir="bottomend" element="_parent"/>
</html:div>
<hbox id="anchor" align="start" style="margin-left: 100px;">
<hbox id="inside-container" align="start">
<hbox minwidth="45" minheight="41"/>
<resizer id="inside" dir="bottomend" element="_parent"/>
</hbox>
<hbox id="inside-large-container" width="70" height="70" align="start">
<resizer id="inside-large" dir="bottomend" element="_parent"/>
</hbox>
<hbox id="inside-with-border-container" style="border: 5px solid red; padding: 2px; margin: 2px;" align="start">
<hbox minwidth="35" minheight="30"/>
<resizer id="inside-with-border" dir="bottomend" element="_parent"/>
</hbox>
</hbox>
<panel id="inside-popup-container" align="start" onpopupshown="popupShown(event)" onpopuphidden="popupHidden()">
<resizer id="inside-popup" dir="bottomend"/>
<hbox width="50" height="50" flex="1"/>
</panel>
<resizer id="outside-popup" dir="bottomend" element="outside-popup-container"/>
<panel id="anchored-panel-container" align="start" onpopupshown="anchoredPopupShown(event)"
onpopuphidden="popupHidden()">
<hbox width="50" height="50" flex="1"/>
<resizer id="anchored-panel" width="20" height="20"/>
</panel>
</window>