Bug 357725, support minimum and maximum size constraints on windows and popups, r=mats,jmathies,karlt,smichaud,sr=neil
authorNeil Deakin <neil@mozilla.com>
Mon, 30 Jul 2012 20:43:29 -0400
changeset 100929 b8a0228a10faac7d965155e15bd66ff8bf3ddb82
parent 100928 de293780ba8aab62cfa995b1254b4e5601e85461
child 100930 28468ad2ffdc08b04de7498d246da1c72e38f428
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersmats, jmathies, karlt, smichaud, neil
bugs357725
milestone17.0a1
Bug 357725, support minimum and maximum size constraints on windows and popups, r=mats,jmathies,karlt,smichaud,sr=neil
layout/base/nsPresShell.cpp
layout/generic/nsContainerFrame.cpp
layout/generic/nsContainerFrame.h
layout/xul/base/src/nsMenuPopupFrame.cpp
layout/xul/base/src/nsResizerFrame.cpp
layout/xul/base/src/nsResizerFrame.h
widget/cocoa/nsCocoaWindow.h
widget/cocoa/nsCocoaWindow.mm
widget/gtk2/nsWindow.cpp
widget/gtk2/nsWindow.h
widget/nsIWidget.h
widget/windows/nsWindow.cpp
widget/windows/nsWindow.h
widget/xpwidgets/nsBaseWidget.cpp
widget/xpwidgets/nsBaseWidget.h
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -7483,17 +7483,17 @@ PresShell::DoReflow(nsIFrame* target, bo
   // Always use boundsRelativeToTarget here, not desiredSize.GetVisualOverflowArea(),
   // because for root frames (where they could be different, since root frames
   // are allowed to have overflow) the root view bounds need to match the
   // viewport bounds; the view manager "window dimensions" code depends on it.
   nsContainerFrame::SyncFrameViewAfterReflow(mPresContext, target,
                                              target->GetView(),
                                              boundsRelativeToTarget);
   nsContainerFrame::SyncWindowProperties(mPresContext, target,
-                                         target->GetView());
+                                         target->GetView(), rcx);
 
   target->DidReflow(mPresContext, nullptr, NS_FRAME_REFLOW_FINISHED);
   if (target == rootFrame && size.height == NS_UNCONSTRAINEDSIZE) {
     mPresContext->SetVisibleArea(boundsRelativeToTarget);
   }
 
 #ifdef DEBUG
   mCurrentReflowRoot = nullptr;
--- a/layout/generic/nsContainerFrame.cpp
+++ b/layout/generic/nsContainerFrame.cpp
@@ -29,16 +29,18 @@
 #include "nsTransform2D.h"
 #include "nsRegion.h"
 #include "nsLayoutErrors.h"
 #include "nsDisplayList.h"
 #include "nsContentErrors.h"
 #include "nsListControlFrame.h"
 #include "nsIBaseWindow.h"
 #include "nsThemeConstants.h"
+#include "nsBoxLayoutState.h"
+#include "nsRenderingContext.h"
 #include "nsCSSFrameConstructor.h"
 #include "mozilla/dom/Element.h"
 
 #ifdef DEBUG
 #undef NOISY
 #else
 #undef NOISY
 #endif
@@ -627,17 +629,18 @@ IsTopLevelWidget(nsIWidget* aWidget)
          windowType == eWindowType_dialog ||
          windowType == eWindowType_sheet;
   // popups aren't toplevel so they're not handled here
 }
 
 void
 nsContainerFrame::SyncWindowProperties(nsPresContext*       aPresContext,
                                        nsIFrame*            aFrame,
-                                       nsIView*             aView)
+                                       nsIView*             aView,
+                                       nsRenderingContext*  aRC)
 {
 #ifdef MOZ_XUL
   if (!aView || !nsCSSRendering::IsCanvasFrame(aFrame) || !aView->HasWidget())
     return;
 
   nsIWidget* windowWidget = GetPresContextContainerWidget(aPresContext);
   if (!windowWidget || !IsTopLevelWidget(windowWidget))
     return;
@@ -671,19 +674,57 @@ nsContainerFrame::SyncWindowProperties(n
   nsIFrame *rootFrame = aPresContext->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
   if (!rootFrame)
     return;
 
   nsTransparencyMode mode = nsLayoutUtils::GetFrameTransparency(aFrame, rootFrame);
   nsIWidget* viewWidget = aView->GetWidget();
   viewWidget->SetTransparencyMode(mode);
   windowWidget->SetWindowShadowStyle(rootFrame->GetStyleUIReset()->mWindowShadow);
+
+  if (!aRC)
+    return;
+  
+  nsBoxLayoutState aState(aPresContext, aRC);
+  nsSize minSize = rootFrame->GetMinSize(aState);
+  nsSize maxSize = rootFrame->GetMaxSize(aState);
+
+  SetSizeConstraints(aPresContext, windowWidget, minSize, maxSize);
 #endif
 }
 
+void nsContainerFrame::SetSizeConstraints(nsPresContext* aPresContext,
+                                          nsIWidget* aWidget,
+                                          const nsSize& aMinSize,
+                                          const nsSize& aMaxSize)
+{
+  nsIntSize devMinSize(aPresContext->AppUnitsToDevPixels(aMinSize.width),
+                       aPresContext->AppUnitsToDevPixels(aMinSize.height));
+  nsIntSize devMaxSize(aMaxSize.width == NS_INTRINSICSIZE ? NS_MAXSIZE :
+                         aPresContext->AppUnitsToDevPixels(aMaxSize.width),
+                       aMaxSize.height == NS_INTRINSICSIZE ? NS_MAXSIZE :
+                         aPresContext->AppUnitsToDevPixels(aMaxSize.height));
+  widget::SizeConstraints constraints(devMinSize, devMaxSize);
+
+  // The sizes are in inner window sizes, so convert them into outer window sizes.
+  // Use a size of (200, 200) as only the difference between the inner and outer
+  // size is needed.
+  nsIntSize windowSize = aWidget->ClientToWindowSize(nsIntSize(200, 200));
+  if (constraints.mMinSize.width)
+    constraints.mMinSize.width += windowSize.width - 200;
+  if (constraints.mMinSize.height)
+    constraints.mMinSize.height += windowSize.height - 200;
+  if (constraints.mMaxSize.width != NS_MAXSIZE)
+    constraints.mMaxSize.width += windowSize.width - 200;
+  if (constraints.mMaxSize.height != NS_MAXSIZE)
+    constraints.mMaxSize.height += windowSize.height - 200;
+
+  aWidget->SetSizeConstraints(constraints);
+}
+
 void
 nsContainerFrame::SyncFrameViewAfterReflow(nsPresContext* aPresContext,
                                            nsIFrame*       aFrame,
                                            nsIView*        aView,
                                            const nsRect&   aVisualOverflowArea,
                                            PRUint32        aFlags)
 {
   if (!aView) {
--- a/layout/generic/nsContainerFrame.h
+++ b/layout/generic/nsContainerFrame.h
@@ -136,30 +136,45 @@ public:
                                        nsIView*        aView,
                                        const nsRect&   aVisualOverflowArea,
                                        PRUint32        aFlags = 0);
 
   // Syncs properties to the top level view and window, like transparency and
   // shadow.
   static void SyncWindowProperties(nsPresContext*       aPresContext,
                                    nsIFrame*            aFrame,
-                                   nsIView*             aView);
+                                   nsIView*             aView,
+                                   nsRenderingContext*  aRC = nullptr);
 
   // Sets the view's attributes from the frame style.
   // - visibility
   // - clip
   // Call this when one of these styles changes or when the view has just
   // been created.
   // @param aStyleContext can be null, in which case the frame's style context is used
   static void SyncFrameViewProperties(nsPresContext*  aPresContext,
                                       nsIFrame*        aFrame,
                                       nsStyleContext*  aStyleContext,
                                       nsIView*         aView,
                                       PRUint32         aFlags = 0);
 
+  /**
+   * Converts the minimum and maximum sizes given in inner window app units to
+   * outer window device pixel sizes and assigns these constraints to the widget.
+   *
+   * @param aPresContext pres context
+   * @param aWidget widget for this frame
+   * @param minimum size of the window in app units
+   * @param maxmimum size of the window in app units
+   */
+  static void SetSizeConstraints(nsPresContext* aPresContext,
+                                 nsIWidget* aWidget,
+                                 const nsSize& aMinSize,
+                                 const nsSize& aMaxSize);
+
   // Used by both nsInlineFrame and nsFirstLetterFrame.
   void DoInlineIntrinsicWidth(nsRenderingContext *aRenderingContext,
                               InlineIntrinsicWidthData *aData,
                               nsLayoutUtils::IntrinsicWidthType aType);
 
   /**
    * This is the CSS block concept of computing 'auto' widths, which most
    * classes derived from nsContainerFrame want.
--- a/layout/xul/base/src/nsMenuPopupFrame.cpp
+++ b/layout/xul/base/src/nsMenuPopupFrame.cpp
@@ -445,16 +445,24 @@ nsMenuPopupFrame::LayoutPopup(nsBoxLayou
 
   nsPresContext* pc = PresContext();
   if (isOpen) {
     nsIView* view = GetView();
     nsIViewManager* viewManager = view->GetViewManager();
     nsRect rect = GetRect();
     rect.x = rect.y = 0;
 
+    if (sizeChanged) {
+      // If the size of the popup changed, apply any size constraints.
+      nsIWidget* widget = view->GetWidget();
+      if (widget) {
+        SetSizeConstraints(pc, widget, minSize, maxSize);
+      }
+    }
+
     viewManager->ResizeView(view, rect);
 
     viewManager->SetViewVisibility(view, nsViewVisibility_kShow);
     mPopupState = ePopupOpenAndVisible;
     nsContainerFrame::SyncFrameViewProperties(pc, this, nullptr, view, 0);
   }
 
   // finally, if the popup just opened, send a popupshown event
--- a/layout/xul/base/src/nsResizerFrame.cpp
+++ b/layout/xul/base/src/nsResizerFrame.cpp
@@ -24,16 +24,17 @@
 #include "nsGUIEvent.h"
 #include "nsEventDispatcher.h"
 #include "nsContentUtils.h"
 #include "nsMenuPopupFrame.h"
 #include "nsIScreenManager.h"
 #include "mozilla/dom/Element.h"
 #include "nsContentErrors.h"
 
+using namespace mozilla;
 
 //
 // NS_NewResizerFrame
 //
 // Creates a new Resizer frame and returns it
 //
 nsIFrame*
 NS_NewResizerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
@@ -181,18 +182,29 @@ nsResizerFrame::HandleEvent(nsPresContex
             (direction.mVertical == -1) ? NS_SIDE_TOP : NS_SIDE_BOTTOM, mouseMove);
         }
       }
       else if (!contentToResize) {
         break; // don't do anything if there's nothing to resize
       }
 
       nsIntRect rect = mMouseDownRect;
-      AdjustDimensions(&rect.x, &rect.width, mouseMove.x, direction.mHorizontal);
-      AdjustDimensions(&rect.y, &rect.height, mouseMove.y, direction.mVertical);
+
+      // 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.
       if (window) {
         nsCOMPtr<nsIScreen> screen;
         nsCOMPtr<nsIScreenManager> sm(do_GetService("@mozilla.org/gfx/screenmanager;1"));
         if (sm) {
           nsIntRect frameRect = GetScreenRect();
@@ -244,16 +256,20 @@ nsResizerFrame::HandleEvent(nsPresContex
         }
 
         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)) {
           menuPopupFrame->MoveTo(rect.x, rect.y, true);
         }
       }
       else {
@@ -360,35 +376,35 @@ nsResizerFrame::GetContentToResize(nsIPr
     // return the parent, but skip over native anonymous content
     nsIContent* parent = mContent->GetParent();
     return parent ? parent->FindFirstNonNativeAnonymous() : nullptr;
   }
 
   return aPresShell->GetDocument()->GetElementById(elementid);
 }
 
-/* adjust the window position and size according to the mouse movement and
- * the resizer direction
- */
 void
 nsResizerFrame::AdjustDimensions(PRInt32* aPos, PRInt32* aSize,
+                                 PRInt32 aMinSize, PRInt32 aMaxSize,
                                  PRInt32 aMovement, PRInt8 aResizerDirection)
 {
-  switch(aResizerDirection)
-  {
-    case -1:
-      // only move the window when the direction is top and/or left
-      *aPos+= aMovement;
-      // falling through: the window is resized in both cases
-    case 1:
-      *aSize+= aResizerDirection*aMovement;
-      // use one as a minimum size or the element could disappear
-      if (*aSize < 1)
-        *aSize = 1;
-  }
+  PRInt32 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 = NS_MAX(aMinSize, NS_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)
 {
   // for XUL elements, just set the width and height attributes. For
   // other elements, set style.width and style.height
--- a/layout/xul/base/src/nsResizerFrame.h
+++ b/layout/xul/base/src/nsResizerFrame.h
@@ -30,18 +30,32 @@ public:
                                       nsEventStatus* aEventStatus);
 
   virtual void MouseClicked(nsPresContext* aPresContext, nsGUIEvent *aEvent);
 
 protected:
   nsIContent* GetContentToResize(nsIPresShell* 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(PRInt32* aPos, PRInt32* aSize,
-                        PRInt32 aMovement, PRInt8 aResizerDirection);
+                               PRInt32 aMinSize, PRInt32 aMaxSize,
+                               PRInt32 aMovement, PRInt8 aResizerDirection);
 
   struct SizeInfo {
     nsString width, height;
   };
   static void SizeInfoDtorFunc(void *aObject, nsIAtom *aPropertyName,
                                void *aPropertyValue, void *aData);
   static void ResizeContent(nsIContent* aContent, const Direction& aDirection,
                             const SizeInfo& aSizeInfo, SizeInfo* aOriginalSizeInfo);
--- a/widget/cocoa/nsCocoaWindow.h
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -219,16 +219,17 @@ public:
     virtual nsIntPoint WidgetToScreenOffset();
     virtual nsIntPoint GetClientOffset();
     virtual nsIntSize ClientToWindowSize(const nsIntSize& aClientSize);
 
     virtual void* GetNativeData(PRUint32 aDataType) ;
 
     NS_IMETHOD              ConstrainPosition(bool aAllowSlop,
                                               PRInt32 *aX, PRInt32 *aY);
+    virtual void            SetSizeConstraints(const SizeConstraints& aConstraints);
     NS_IMETHOD              Move(PRInt32 aX, PRInt32 aY);
     NS_IMETHOD              PlaceBehind(nsTopLevelWidgetZPlacement aPlacement,
                                         nsIWidget *aWidget, bool aActivate);
     NS_IMETHOD              SetSizeMode(PRInt32 aMode);
     NS_IMETHOD              HideWindowChrome(bool aShouldHide);
     void                    EnteredFullScreen(bool aFullScreen);
     NS_IMETHOD              MakeFullScreen(bool aFullScreen);
     NS_IMETHOD              Resize(PRInt32 aWidth,PRInt32 aHeight, bool aRepaint);
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -1067,16 +1067,42 @@ NS_IMETHODIMP nsCocoaWindow::ConstrainPo
     } else if (*aY >= screenBounds.y + screenBounds.height - mBounds.height) {
       *aY = screenBounds.y + screenBounds.height - mBounds.height;
     }
   }
 
   return NS_OK;
 }
 
+void nsCocoaWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
+
+  // Popups can be smaller than (60, 60)
+  NSRect rect =
+    (mWindowType == eWindowType_popup) ? NSZeroRect : NSMakeRect(0.0, 0.0, 60, 60);
+  rect = [mWindow frameRectForContentRect:rect];
+
+  SizeConstraints c = aConstraints;
+  c.mMinSize.width = NS_MAX(PRInt32(rect.size.width), c.mMinSize.width);
+  c.mMinSize.height = NS_MAX(PRInt32(rect.size.height), c.mMinSize.height);
+
+  NSSize minSize = { static_cast<CGFloat>(c.mMinSize.width),
+                     static_cast<CGFloat>(c.mMinSize.height) };
+  [mWindow setMinSize:minSize];
+
+  NSSize maxSize = { c.mMaxSize.width == NS_MAXSIZE ? FLT_MAX : c.mMaxSize.width,
+                     c.mMaxSize.height == NS_MAXSIZE ? FLT_MAX : c.mMaxSize.height };
+  [mWindow setMaxSize:maxSize];
+
+  nsBaseWidget::SetSizeConstraints(c);
+
+  NS_OBJC_END_TRY_ABORT_BLOCK;
+}
+
 NS_IMETHODIMP nsCocoaWindow::Move(PRInt32 aX, PRInt32 aY)
 {
   if (!mWindow || (mBounds.x == aX && mBounds.y == aY))
     return NS_OK;
 
   // The point we have is in Gecko coordinates (origin top-left). Convert
   // it to Cocoa ones (origin bottom-left).
   NSPoint coord = {static_cast<CGFloat>(aX), nsCocoaUtils::FlippedScreenY(aY)};
@@ -1245,16 +1271,18 @@ NS_METHOD nsCocoaWindow::MakeFullScreen(
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 
 NS_IMETHODIMP nsCocoaWindow::Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, bool aRepaint)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
 
+  ConstrainSize(&aWidth, &aHeight);
+
   nsIntRect newBounds = nsIntRect(aX, aY, aWidth, aHeight);
   FitRectToVisibleAreaForScreen(newBounds, [mWindow screen]);
 
   BOOL isMoving = (mBounds.x != newBounds.x || mBounds.y != newBounds.y);
   BOOL isResizing = (mBounds.width != newBounds.width || mBounds.height != newBounds.height);
 
   if (!mWindow || (!isMoving && !isResizing))
     return NS_OK;
@@ -1612,21 +1640,16 @@ nsIntPoint nsCocoaWindow::GetClientOffse
 
 nsIntSize nsCocoaWindow::ClientToWindowSize(const nsIntSize& aClientSize)
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   if (!mWindow)
     return nsIntSize(0, 0);
 
-  // this is only called for popups currently. If needed, expand this to support
-  // other types of windows
-  if (!IsPopupWithTitleBar())
-    return aClientSize;
-
   NSRect rect(NSMakeRect(0.0, 0.0, aClientSize.width, aClientSize.height));
 
   NSRect inflatedRect = [mWindow frameRectForContentRect:rect];
   return nsCocoaUtils::CocoaRectToGeckoRect(inflatedRect).Size();
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nsIntSize(0,0));
 }
 
--- a/widget/gtk2/nsWindow.cpp
+++ b/widget/gtk2/nsWindow.cpp
@@ -939,16 +939,33 @@ nsWindow::ConstrainPosition(bool aAllowS
                 *aY = 0;
             if (*aY > (screenHeight - mBounds.height))
                 *aY = screenHeight - mBounds.height;
         }
     }
     return NS_OK;
 }
 
+void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
+{
+  if (mShell) {
+    GdkGeometry geometry;
+    geometry.min_width = aConstraints.mMinSize.width;
+    geometry.min_height = aConstraints.mMinSize.height;
+    geometry.max_width = aConstraints.mMaxSize.width;
+    geometry.max_height = aConstraints.mMaxSize.height;
+
+    PRUint32 hints = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE;
+    gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr,
+                                  &geometry, GdkWindowHints(hints));
+  }
+
+  nsBaseWidget::SetSizeConstraints(aConstraints);
+}
+
 NS_IMETHODIMP
 nsWindow::Show(bool aState)
 {
     if (aState == mIsShown)
         return NS_OK;
 
     // Clear our cached resources when the window is hidden.
     if (mIsShown && !aState) {
@@ -997,16 +1014,18 @@ nsWindow::Show(bool aState)
     NativeShow(aState);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::Resize(PRInt32 aWidth, PRInt32 aHeight, bool aRepaint)
 {
+    ConstrainSize(&aWidth, &aHeight);
+
     // For top-level windows, aWidth and aHeight should possibly be
     // interpreted as frame bounds, but NativeResize treats these as window
     // bounds (Bug 581866).
 
     mBounds.SizeTo(GetSafeWindowSize(nsIntSize(aWidth, aHeight)));
 
     if (!mCreated)
         return NS_OK;
@@ -1074,16 +1093,18 @@ nsWindow::Resize(PRInt32 aWidth, PRInt32
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight,
                        bool aRepaint)
 {
+    ConstrainSize(&aWidth, &aHeight);
+
     mBounds.x = aX;
     mBounds.y = aY;
     mBounds.SizeTo(GetSafeWindowSize(nsIntSize(aWidth, aHeight)));
 
     mNeedsMove = true;
 
     if (!mCreated)
         return NS_OK;
--- a/widget/gtk2/nsWindow.h
+++ b/widget/gtk2/nsWindow.h
@@ -106,16 +106,17 @@ public:
     virtual nsIWidget *GetParent();
     virtual float      GetDPI();
     virtual nsresult   SetParent(nsIWidget* aNewParent);
     NS_IMETHOD         SetModal(bool aModal);
     virtual bool       IsVisible() const;
     NS_IMETHOD         ConstrainPosition(bool aAllowSlop,
                                          PRInt32 *aX,
                                          PRInt32 *aY);
+    virtual void       SetSizeConstraints(const SizeConstraints& aConstraints);
     NS_IMETHOD         Move(PRInt32 aX,
                             PRInt32 aY);
     NS_IMETHOD         Show             (bool aState);
     NS_IMETHOD         Resize           (PRInt32 aWidth,
                                          PRInt32 aHeight,
                                          bool    aRepaint);
     NS_IMETHOD         Resize           (PRInt32 aX,
                                          PRInt32 aY,
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -158,17 +158,16 @@ enum nsCursor {   ///(normal cursor,    
                 }; 
 
 enum nsTopLevelWidgetZPlacement { // for PlaceBehind()
   eZPlacementBottom = 0,  // bottom of the window stack
   eZPlacementBelow,       // just below another widget
   eZPlacementTop          // top of the window stack
 };
 
-
 /**
  * Preference for receiving IME updates
  *
  * If mWantUpdates is true, PuppetWidget will forward
  * nsIWidget::OnIMETextChange and nsIWidget::OnIMESelectionChange to the chrome
  * process. This incurs overhead from observers and IPDL. If the IME
  * implementation on a particular platform doesn't care about OnIMETextChange
  * and OnIMESelectionChange from content processes, they should set
@@ -346,16 +345,37 @@ struct InputContextAction {
 
   InputContextAction(Cause aCause,
                      FocusChange aFocusChange = FOCUS_NOT_CHANGED) :
     mCause(aCause), mFocusChange(aFocusChange)
   {
   }
 };
 
+/**
+ * Size constraints for setting the minimum and maximum size of a widget.
+ * Values are in device pixels.
+ */
+struct SizeConstraints {
+  SizeConstraints()
+    : mMaxSize(NS_MAXSIZE, NS_MAXSIZE)
+  {
+  }
+
+  SizeConstraints(nsIntSize aMinSize,
+                  nsIntSize aMaxSize)
+  : mMinSize(aMinSize),
+    mMaxSize(aMaxSize)
+  {
+  }
+
+  nsIntSize mMinSize;
+  nsIntSize mMaxSize;
+};
+
 } // namespace widget
 } // namespace mozilla
 
 /**
  * The base class for all the widgets. It provides the interface for
  * all basic and necessary functionality.
  */
 class nsIWidget : public nsISupports {
@@ -364,16 +384,17 @@ class nsIWidget : public nsISupports {
 
   public:
     typedef mozilla::layers::LayerManager LayerManager;
     typedef mozilla::layers::LayersBackend LayersBackend;
     typedef mozilla::layers::PLayersChild PLayersChild;
     typedef mozilla::widget::IMEState IMEState;
     typedef mozilla::widget::InputContext InputContext;
     typedef mozilla::widget::InputContextAction InputContextAction;
+    typedef mozilla::widget::SizeConstraints SizeConstraints;
 
     // Used in UpdateThemeGeometries.
     struct ThemeGeometry {
       // The -moz-appearance value for the themed widget
       PRUint8 mWidgetType;
       // The device-pixel rect within the window for the themed widget
       nsIntRect mRect;
 
@@ -653,29 +674,31 @@ class nsIWidget : public nsISupports {
      *                 offset from the origin of the client area of the parent
      *                 widget (for root widgets and popup widgets it is in
      *                 screen coordinates)
      *
      **/
     NS_IMETHOD MoveClient(PRInt32 aX, PRInt32 aY) = 0;
 
     /**
-     * Resize this widget. 
+     * Resize this widget. Any size constraints set for the window by a
+     * previous call to SetSizeConstraints will be applied.
      *
      * @param aWidth  the new width expressed in the parent's coordinate system
      * @param aHeight the new height expressed in the parent's coordinate system
      * @param aRepaint whether the widget should be repainted
      *
      */
     NS_IMETHOD Resize(PRInt32 aWidth,
                       PRInt32 aHeight,
                       bool     aRepaint) = 0;
 
     /**
-     * Move or resize this widget.
+     * Move or resize this widget. Any size constraints set for the window by
+     * a previous call to SetSizeConstraints will be applied.
      *
      * @param aX       the new x position expressed in the parent's coordinate system
      * @param aY       the new y position expressed in the parent's coordinate system
      * @param aWidth   the new width expressed in the parent's coordinate system
      * @param aHeight  the new height expressed in the parent's coordinate system
      * @param aRepaint whether the widget should be repainted if the size changes
      *
      */
@@ -1583,16 +1606,36 @@ class nsIWidget : public nsISupports {
      * probably shouldn't call this method.
      */
     virtual nsIntRect GetNaturalBounds() {
         nsIntRect bounds;
         GetBounds(bounds);
         return bounds;
     }
 
+    /**
+     * Set size constraints on the window size such that it is never less than
+     * the specified minimum size and never larger than the specified maximum
+     * size. The size constraints are sizes of the outer rectangle including
+     * the window frame and title bar. Use 0 for an unconstrained minimum size
+     * and NS_MAXSIZE for an unconstrained maximum size. Note that this method
+     * does not necessarily change the size of a window to conform to this size,
+     * thus Resize should be called afterwards.
+     *
+     * @param aConstraints: the size constraints in device pixels
+     */
+    virtual void SetSizeConstraints(const SizeConstraints& aConstraints) = 0;
+
+    /**
+     * Return the size constraints currently observed by the widget.
+     *
+     * @return the constraints in device pixels
+     */
+    virtual const SizeConstraints& GetSizeConstraints() const = 0;
+
 protected:
 
     // keep the list of children.  We also keep track of our siblings.
     // The ownership model is as follows: parent holds a strong ref to
     // the first element of the list, and each element holds a strong
     // ref to the next element in the list.  The prevsibling and
     // lastchild pointers are weak, which is fine as long as they are
     // maintained properly.
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -1281,16 +1281,28 @@ BOOL CALLBACK nsWindow::UnregisterTouchF
  *
  * SECTION: nsIWidget::Move, nsIWidget::Resize,
  * nsIWidget::Size, nsIWidget::BeginResizeDrag
  *
  * Repositioning and sizing a window.
  *
  **************************************************************/
 
+void
+nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints)
+{
+  SizeConstraints c = aConstraints;
+  if (mWindowType != eWindowType_popup) {
+    c.mMinSize.width = NS_MAX(PRInt32(::GetSystemMetrics(SM_CXMINTRACK)), c.mMinSize.width);
+    c.mMinSize.height = NS_MAX(PRInt32(::GetSystemMetrics(SM_CYMINTRACK)), c.mMinSize.height);
+  }
+
+  nsBaseWidget::SetSizeConstraints(c);
+}
+
 // Move this component
 NS_METHOD nsWindow::Move(PRInt32 aX, PRInt32 aY)
 {
   if (mWindowType == eWindowType_toplevel ||
       mWindowType == eWindowType_dialog) {
     SetSizeMode(nsSizeMode_Normal);
   }
   // Check to see if window needs to be moved first
@@ -1351,16 +1363,17 @@ NS_METHOD nsWindow::Move(PRInt32 aX, PRI
   return NS_OK;
 }
 
 // Resize this component
 NS_METHOD nsWindow::Resize(PRInt32 aWidth, PRInt32 aHeight, bool aRepaint)
 {
   NS_ASSERTION((aWidth >=0 ) , "Negative width passed to nsWindow::Resize");
   NS_ASSERTION((aHeight >=0 ), "Negative height passed to nsWindow::Resize");
+  ConstrainSize(&aWidth, &aHeight);
 
   // Avoid unnecessary resizing calls
   if (mBounds.width == aWidth && mBounds.height == aHeight && !aRepaint)
     return NS_OK;
 
 #ifdef MOZ_XUL
   if (eTransparencyTransparent == mTransparencyMode)
     ResizeTranslucentWindow(aWidth, aHeight);
@@ -1389,16 +1402,17 @@ NS_METHOD nsWindow::Resize(PRInt32 aWidt
   return NS_OK;
 }
 
 // Resize this component
 NS_METHOD nsWindow::Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, bool aRepaint)
 {
   NS_ASSERTION((aWidth >=0 ),  "Negative width passed to nsWindow::Resize");
   NS_ASSERTION((aHeight >=0 ), "Negative height passed to nsWindow::Resize");
+  ConstrainSize(&aWidth, &aHeight);
 
   // Avoid unnecessary resizing calls
   if (mBounds.x == aX && mBounds.y == aY &&
       mBounds.width == aWidth && mBounds.height == aHeight && !aRepaint)
     return NS_OK;
 
 #ifdef MOZ_XUL
   if (eTransparencyTransparent == mTransparencyMode)
@@ -2878,17 +2892,17 @@ nsIntPoint nsWindow::WidgetToScreenOffse
   point.x = 0;
   point.y = 0;
   ::ClientToScreen(mWnd, &point);
   return nsIntPoint(point.x, point.y);
 }
 
 nsIntSize nsWindow::ClientToWindowSize(const nsIntSize& aClientSize)
 {
-  if (!IsPopupWithTitleBar())
+  if (mWindowType == eWindowType_popup && !IsPopupWithTitleBar())
     return aClientSize;
 
   // just use (200, 200) as the position
   RECT r;
   r.left = 200;
   r.top = 200;
   r.right = 200 + aClientSize.width;
   r.bottom = 200 + aClientSize.height;
@@ -5058,16 +5072,32 @@ bool nsWindow::ProcessMessage(UINT msg, 
 
     case WM_WINDOWPOSCHANGING:
     {
       LPWINDOWPOS info = (LPWINDOWPOS) lParam;
       OnWindowPosChanging(info);
     }
     break;
 
+    case WM_GETMINMAXINFO:
+    {
+      MINMAXINFO* mmi = (MINMAXINFO*)lParam;
+      // Set the constraints. The minimum size should also be constrained to the
+      // default window maximum size so that it fits on screen.
+      mmi->ptMinTrackSize.x =
+        NS_MIN((PRInt32)mmi->ptMaxTrackSize.x,
+               NS_MAX((PRInt32)mmi->ptMinTrackSize.x, mSizeConstraints.mMinSize.width));
+      mmi->ptMinTrackSize.y =
+        NS_MIN((PRInt32)mmi->ptMaxTrackSize.y,
+        NS_MAX((PRInt32)mmi->ptMinTrackSize.y, mSizeConstraints.mMinSize.height));
+      mmi->ptMaxTrackSize.x = NS_MIN((PRInt32)mmi->ptMaxTrackSize.x, mSizeConstraints.mMaxSize.width);
+      mmi->ptMaxTrackSize.y = NS_MIN((PRInt32)mmi->ptMaxTrackSize.y, mSizeConstraints.mMaxSize.height);
+    }
+    break;
+
     case WM_SETFOCUS:
       // If previous focused window isn't ours, it must have received the
       // redirected message.  So, we should forget it.
       if (!WinUtils::IsOurProcessWindow(HWND(wParam))) {
         ForgetRedirectedKeyDownMessage();
       }
       if (sJustGotActivate) {
         result = DispatchFocusToTopLevelWindow(NS_ACTIVATE);
--- a/widget/windows/nsWindow.h
+++ b/widget/windows/nsWindow.h
@@ -89,16 +89,17 @@ public:
                                  nsWidgetInitData *aInitData = nullptr);
   NS_IMETHOD              Destroy();
   NS_IMETHOD              SetParent(nsIWidget *aNewParent);
   virtual nsIWidget*      GetParent(void);
   virtual float           GetDPI();
   NS_IMETHOD              Show(bool bState);
   virtual bool            IsVisible() const;
   NS_IMETHOD              ConstrainPosition(bool aAllowSlop, PRInt32 *aX, PRInt32 *aY);
+  virtual void            SetSizeConstraints(const SizeConstraints& aConstraints);
   NS_IMETHOD              Move(PRInt32 aX, PRInt32 aY);
   NS_IMETHOD              Resize(PRInt32 aWidth, PRInt32 aHeight, bool aRepaint);
   NS_IMETHOD              Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, bool aRepaint);
   NS_IMETHOD              BeginResizeDrag(nsGUIEvent* aEvent, PRInt32 aHorizontal, PRInt32 aVertical);
   NS_IMETHOD              PlaceBehind(nsTopLevelWidgetZPlacement aPlacement, nsIWidget *aWidget, bool aActivate);
   NS_IMETHOD              SetSizeMode(PRInt32 aMode);
   NS_IMETHOD              Enable(bool aState);
   virtual bool            IsEnabled() const;
--- a/widget/xpwidgets/nsBaseWidget.cpp
+++ b/widget/xpwidgets/nsBaseWidget.cpp
@@ -1318,16 +1318,28 @@ nsBaseWidget::GetGLFrameBufferFormat()
       mLayerManager->GetBackendType() == LAYERS_OPENGL) {
     // Assume that the default framebuffer has RGBA format.  Specific
     // backends that know differently will override this method.
     return LOCAL_GL_RGBA;
   }
   return LOCAL_GL_NONE;
 }
 
+void nsBaseWidget::SetSizeConstraints(const SizeConstraints& aConstraints)
+{
+  mSizeConstraints = aConstraints;
+  // We can't ensure that the size is honored at this point because we're
+  // probably in the middle of a reflow.
+}
+
+const widget::SizeConstraints& nsBaseWidget::GetSizeConstraints() const
+{
+  return mSizeConstraints;
+}
+
 #ifdef DEBUG
 //////////////////////////////////////////////////////////////
 //
 // Convert a GUI event message code to a string.
 // Makes it a lot easier to debug events.
 //
 // See gtk/nsWidget.cpp and windows/nsWindow.cpp
 // for a DebugPrintEvent() function that uses
--- a/widget/xpwidgets/nsBaseWidget.h
+++ b/widget/xpwidgets/nsBaseWidget.h
@@ -174,16 +174,19 @@ public:
             mBorderStyle != eBorderStyle_default &&
             mBorderStyle & eBorderStyle_title);
   }
 
   NS_IMETHOD              ReparentNativeWidget(nsIWidget* aNewParent) = 0;
 
   virtual PRUint32 GetGLFrameBufferFormat() MOZ_OVERRIDE;
 
+  virtual const SizeConstraints& GetSizeConstraints() const;
+  virtual void SetSizeConstraints(const SizeConstraints& aConstraints);
+
   /**
    * Use this when GetLayerManager() returns a BasicLayerManager
    * (nsBaseWidget::GetLayerManager() does). This sets up the widget's
    * layer manager to temporarily render into aTarget.
    *
    * |aNaturalWidgetBounds| is the un-rotated bounds of |aWidget|.
    * |aRotation| is the "virtual rotation" to apply when rendering to
    * the target.  When |aRotation| is ROTATION_0,
@@ -278,16 +281,30 @@ protected:
 
   void NotifyRollupGeometryChange(nsIRollupListener* aRollupListener)
   {
     if (aRollupListener) {
       aRollupListener->NotifyGeometryChange();
     }
   }
 
+  /**
+   * Apply the current size constraints to the given size.
+   *
+   * @param aWidth width to constrain
+   * @param aHeight height to constrain
+   */
+  void ConstrainSize(PRInt32* aWidth, PRInt32* aHeight) const
+  {
+    *aWidth = NS_MAX(mSizeConstraints.mMinSize.width,
+                     NS_MIN(mSizeConstraints.mMaxSize.width, *aWidth));
+    *aHeight = NS_MAX(mSizeConstraints.mMinSize.height,
+                      NS_MIN(mSizeConstraints.mMaxSize.height, *aHeight));
+  }
+
 protected:
   /**
    * Starts the OMTC compositor destruction sequence.
    *
    * When this function returns, the compositor should not be 
    * able to access the opengl context anymore.
    * It is safe to call it several times if platform implementations
    * require the compositor to be destroyed before ~nsBaseWidget is
@@ -317,16 +334,17 @@ protected:
   nsIntRect*        mOriginalBounds;
   // When this pointer is null, the widget is not clipped
   nsAutoArrayPtr<nsIntRect> mClipRects;
   PRUint32          mClipRectCount;
   PRInt32           mZIndex;
   nsSizeMode        mSizeMode;
   nsPopupLevel      mPopupLevel;
   nsPopupType       mPopupType;
+  SizeConstraints   mSizeConstraints;
 
   // the last rolled up popup. Only set this when an nsAutoRollup is in scope,
   // so it can be cleared automatically.
   static nsIContent* mLastRollup;
 
 #ifdef DEBUG
 protected:
   static nsAutoString debug_GuiEventToString(nsGUIEvent * aGuiEvent);