diff options
author | tmdiep@chromium.org <tmdiep@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-11 04:12:46 +0000 |
---|---|---|
committer | tmdiep@chromium.org <tmdiep@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-11 04:12:46 +0000 |
commit | 1dd77182636791cba0143c41c025a3f669524976 (patch) | |
tree | 3813636a5c3807f170b05984a954088a9a4db1a8 | |
parent | fd0acadd8ef82eae537c8de7333f5e9bb80907c2 (diff) | |
download | chromium_src-1dd77182636791cba0143c41c025a3f669524976.zip chromium_src-1dd77182636791cba0143c41c025a3f669524976.tar.gz chromium_src-1dd77182636791cba0143c41c025a3f669524976.tar.bz2 |
Create windows for new app window bounds API
Added create options "innerBounds" and "outerBounds" for
the new app window bounds API.
BUG=315471, 349397
TEST=browser_tests (AppWindowAPITest.*)
Review URL: https://codereview.chromium.org/186343002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@256125 0039d316-1c4b-4281-b951-d872f2087c98
20 files changed, 1168 insertions, 303 deletions
diff --git a/apps/app_window.cc b/apps/app_window.cc index 8bb2b7e..e6f5a5e 100644 --- a/apps/app_window.cc +++ b/apps/app_window.cc @@ -4,6 +4,8 @@ #include "apps/app_window.h" +#include <algorithm> + #include "apps/app_window_geometry_cache.h" #include "apps/app_window_registry.h" #include "apps/apps_client.h" @@ -95,14 +97,62 @@ void SetBoundsProperties(const gfx::Rect& bounds, window_properties->Set(bounds_name, bounds_properties.release()); } +// Combines the constraints of the content and window, and returns constraints +// for the window. +gfx::Size GetCombinedWindowConstraints(const gfx::Size& window_constraints, + const gfx::Size& content_constraints, + const gfx::Insets& frame_insets) { + gfx::Size combined_constraints(window_constraints); + if (content_constraints.width() > 0) { + combined_constraints.set_width( + content_constraints.width() + frame_insets.width()); + } + if (content_constraints.height() > 0) { + combined_constraints.set_height( + content_constraints.height() + frame_insets.height()); + } + return combined_constraints; +} + +// Combines the constraints of the content and window, and returns constraints +// for the content. +gfx::Size GetCombinedContentConstraints(const gfx::Size& window_constraints, + const gfx::Size& content_constraints, + const gfx::Insets& frame_insets) { + gfx::Size combined_constraints(content_constraints); + if (window_constraints.width() > 0) { + combined_constraints.set_width( + std::max(0, window_constraints.width() - frame_insets.width())); + } + if (window_constraints.height() > 0) { + combined_constraints.set_height( + std::max(0, window_constraints.height() - frame_insets.height())); + } + return combined_constraints; +} + } // namespace +// AppWindow::BoundsSpecification + +const int AppWindow::BoundsSpecification::kUnspecifiedPosition = INT_MIN; + +AppWindow::BoundsSpecification::BoundsSpecification() + : bounds(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0) {} + +AppWindow::BoundsSpecification::~BoundsSpecification() {} + +void AppWindow::BoundsSpecification::ResetBounds() { + bounds.SetRect(kUnspecifiedPosition, kUnspecifiedPosition, 0, 0); +} + +// AppWindow::CreateParams + AppWindow::CreateParams::CreateParams() : window_type(AppWindow::WINDOW_TYPE_DEFAULT), frame(AppWindow::FRAME_CHROME), has_frame_color(false), transparent_background(false), - bounds(INT_MIN, INT_MIN, 0, 0), creator_process_id(0), state(ui::SHOW_STATE_DEFAULT), hidden(false), @@ -112,8 +162,68 @@ AppWindow::CreateParams::CreateParams() AppWindow::CreateParams::~CreateParams() {} +gfx::Rect AppWindow::CreateParams::GetInitialWindowBounds( + const gfx::Insets& frame_insets) const { + // Combine into a single window bounds. + gfx::Rect combined_bounds(window_spec.bounds); + if (content_spec.bounds.x() != BoundsSpecification::kUnspecifiedPosition) + combined_bounds.set_x(content_spec.bounds.x() - frame_insets.left()); + if (content_spec.bounds.y() != BoundsSpecification::kUnspecifiedPosition) + combined_bounds.set_y(content_spec.bounds.y() - frame_insets.top()); + if (content_spec.bounds.width() > 0) { + combined_bounds.set_width( + content_spec.bounds.width() + frame_insets.width()); + } + if (content_spec.bounds.height() > 0) { + combined_bounds.set_height( + content_spec.bounds.height() + frame_insets.height()); + } + + // Constrain the bounds. + SizeConstraints constraints( + GetCombinedWindowConstraints( + window_spec.minimum_size, content_spec.minimum_size, frame_insets), + GetCombinedWindowConstraints( + window_spec.maximum_size, content_spec.maximum_size, frame_insets)); + combined_bounds.set_size(constraints.ClampSize(combined_bounds.size())); + + return combined_bounds; +} + +gfx::Size AppWindow::CreateParams::GetContentMinimumSize( + const gfx::Insets& frame_insets) const { + return GetCombinedContentConstraints(window_spec.minimum_size, + content_spec.minimum_size, + frame_insets); +} + +gfx::Size AppWindow::CreateParams::GetContentMaximumSize( + const gfx::Insets& frame_insets) const { + return GetCombinedContentConstraints(window_spec.maximum_size, + content_spec.maximum_size, + frame_insets); +} + +gfx::Size AppWindow::CreateParams::GetWindowMinimumSize( + const gfx::Insets& frame_insets) const { + return GetCombinedWindowConstraints(window_spec.minimum_size, + content_spec.minimum_size, + frame_insets); +} + +gfx::Size AppWindow::CreateParams::GetWindowMaximumSize( + const gfx::Insets& frame_insets) const { + return GetCombinedWindowConstraints(window_spec.maximum_size, + content_spec.maximum_size, + frame_insets); +} + +// AppWindow::Delegate + AppWindow::Delegate::~Delegate() {} +// AppWindow + AppWindow::AppWindow(BrowserContext* context, Delegate* delegate, const extensions::Extension* extension) @@ -154,7 +264,7 @@ void AppWindow::Init(const GURL& url, extensions::SetViewType(web_contents, extensions::VIEW_TYPE_APP_WINDOW); // Initialize the window - CreateParams new_params = LoadDefaultsAndConstrain(params); + CreateParams new_params = LoadDefaults(params); window_type_ = new_params.window_type; window_key_ = new_params.window_key; @@ -208,7 +318,10 @@ void AppWindow::Init(const GURL& url, // that to happen, we need to define a size for the content, otherwise the // layout will happen in a 0x0 area. // Note: WebContents::GetView() is guaranteed to be non-null. - web_contents->GetView()->SizeContents(new_params.bounds.size()); + gfx::Insets frame_insets = native_app_window_->GetFrameInsets(); + gfx::Rect initial_bounds = new_params.GetInitialWindowBounds(frame_insets); + initial_bounds.Inset(frame_insets); + web_contents->GetView()->SizeContents(initial_bounds.size()); } // Prevent the browser process from shutting down while this window is open. @@ -519,13 +632,13 @@ void AppWindow::ForcedFullscreen() { SetNativeWindowFullscreen(); } -void AppWindow::SetMinimumSize(const gfx::Size& min_size) { - native_app_window_->SetMinimumSize(min_size); +void AppWindow::SetContentMinimumSize(const gfx::Size& min_size) { + native_app_window_->SetContentMinimumSize(min_size); OnSizeConstraintsChanged(); } -void AppWindow::SetMaximumSize(const gfx::Size& max_size) { - native_app_window_->SetMaximumSize(max_size); +void AppWindow::SetContentMaximumSize(const gfx::Size& max_size) { + native_app_window_->SetContentMaximumSize(max_size); OnSizeConstraintsChanged(); } @@ -588,17 +701,23 @@ void AppWindow::GetSerializedState(base::DictionaryValue* properties) const { properties->SetInteger("frameColor", native_app_window_->FrameColor()); gfx::Rect content_bounds = GetClientBounds(); + gfx::Size content_min_size = native_app_window_->GetContentMinimumSize(); + gfx::Size content_max_size = native_app_window_->GetContentMaximumSize(); SetBoundsProperties(content_bounds, - native_app_window_->GetMinimumSize(), - native_app_window_->GetMaximumSize(), + content_min_size, + content_max_size, "innerBounds", properties); - // TODO(tmdiep): Frame constraints will be implemented in a future patch. + gfx::Insets frame_insets = native_app_window_->GetFrameInsets(); gfx::Rect frame_bounds = native_app_window_->GetBounds(); + gfx::Size frame_min_size = + SizeConstraints::AddFrameToConstraints(content_min_size, frame_insets); + gfx::Size frame_max_size = + SizeConstraints::AddFrameToConstraints(content_max_size, frame_insets); SetBoundsProperties(frame_bounds, - gfx::Size(), - gfx::Size(), + frame_min_size, + frame_max_size, "outerBounds", properties); } @@ -663,12 +782,13 @@ void AppWindow::UpdateExtensionAppIcon() { } void AppWindow::OnSizeConstraintsChanged() { - SizeConstraints size_constraints(native_app_window_->GetMinimumSize(), - native_app_window_->GetMaximumSize()); + SizeConstraints size_constraints(native_app_window_->GetContentMinimumSize(), + native_app_window_->GetContentMaximumSize()); gfx::Rect bounds = GetClientBounds(); gfx::Size constrained_size = size_constraints.ClampSize(bounds.size()); if (bounds.size() != constrained_size) { bounds.set_size(constrained_size); + bounds.Inset(-native_app_window_->GetFrameInsets()); native_app_window_->SetBounds(bounds); } OnNativeWindowChanged(); @@ -852,7 +972,6 @@ void AppWindow::SaveWindowPosition() { AppWindowGeometryCache::Get(browser_context()); gfx::Rect bounds = native_app_window_->GetRestoredBounds(); - bounds.Inset(native_app_window_->GetFrameInsets()); gfx::Rect screen_bounds = gfx::Screen::GetNativeScreen()->GetDisplayMatching(bounds).work_area(); ui::WindowShowState window_state = native_app_window_->GetRestoredState(); @@ -890,12 +1009,17 @@ void AppWindow::AdjustBoundsToBeVisibleOnScreen( } } -AppWindow::CreateParams AppWindow::LoadDefaultsAndConstrain(CreateParams params) +AppWindow::CreateParams AppWindow::LoadDefaults(CreateParams params) const { - if (params.bounds.width() == 0) - params.bounds.set_width(kDefaultWidth); - if (params.bounds.height() == 0) - params.bounds.set_height(kDefaultHeight); + // Ensure width and height are specified. + if (params.content_spec.bounds.width() == 0 && + params.window_spec.bounds.width() == 0) { + params.content_spec.bounds.set_width(kDefaultWidth); + } + if (params.content_spec.bounds.height() == 0 && + params.window_spec.bounds.height() == 0) { + params.content_spec.bounds.set_height(kDefaultHeight); + } // If left and top are left undefined, the native app window will center // the window on the main screen in a platform-defined manner. @@ -913,25 +1037,27 @@ AppWindow::CreateParams AppWindow::LoadDefaultsAndConstrain(CreateParams params) &cached_bounds, &cached_screen_bounds, &cached_state)) { + // App window has cached screen bounds, make sure it fits on screen in // case the screen resolution changed. gfx::Screen* screen = gfx::Screen::GetNativeScreen(); gfx::Display display = screen->GetDisplayMatching(cached_bounds); gfx::Rect current_screen_bounds = display.work_area(); + SizeConstraints constraints(params.GetWindowMinimumSize(gfx::Insets()), + params.GetWindowMaximumSize(gfx::Insets())); AdjustBoundsToBeVisibleOnScreen(cached_bounds, cached_screen_bounds, current_screen_bounds, - params.minimum_size, - ¶ms.bounds); + constraints.GetMinimumSize(), + ¶ms.window_spec.bounds); params.state = cached_state; + + // Since we are restoring a cached state, reset the content bounds spec to + // ensure it is not used. + params.content_spec.ResetBounds(); } } - SizeConstraints size_constraints(params.minimum_size, params.maximum_size); - params.bounds.set_size(size_constraints.ClampSize(params.bounds.size())); - params.minimum_size = size_constraints.GetMinimumSize(); - params.maximum_size = size_constraints.GetMaximumSize(); - return params; } diff --git a/apps/app_window.h b/apps/app_window.h index 2614a40..9729484 100644 --- a/apps/app_window.h +++ b/apps/app_window.h @@ -114,6 +114,26 @@ class AppWindow : public content::NotificationObserver, FULLSCREEN_TYPE_FORCED = 1 << 3, }; + struct BoundsSpecification { + // INT_MIN represents an unspecified position component. + static const int kUnspecifiedPosition; + + BoundsSpecification(); + ~BoundsSpecification(); + + // INT_MIN designates 'unspecified' for the position components, and 0 + // designates 'unspecified' for the size components. When unspecified, + // they should be replaced with a default value. + gfx::Rect bounds; + + gfx::Size minimum_size; + gfx::Size maximum_size; + + // Reset the bounds fields to their 'unspecified' values. The minimum and + // maximum size constraints remain unchanged. + void ResetBounds(); + }; + struct CreateParams { CreateParams(); ~CreateParams(); @@ -125,14 +145,13 @@ class AppWindow : public content::NotificationObserver, SkColor frame_color; bool transparent_background; // Only supported on ash. - // Specify the initial content bounds of the window (excluding any window - // decorations). INT_MIN designates 'unspecified' for the position - // components, and 0 for the size components. When unspecified, they should - // be replaced with a default value. - gfx::Rect bounds; + // The initial content/inner bounds specification (excluding any window + // decorations). + BoundsSpecification content_spec; - gfx::Size minimum_size; - gfx::Size maximum_size; + // The initial window/outer bounds specification (including window + // decorations). + BoundsSpecification window_spec; std::string window_key; @@ -154,6 +173,18 @@ class AppWindow : public content::NotificationObserver, // If true, the window will stay on top of other windows that are not // configured to be always on top. Defaults to false. bool always_on_top; + + // The API enables developers to specify content or window bounds. This + // function combines them into a single, constrained window size. + gfx::Rect GetInitialWindowBounds(const gfx::Insets& frame_insets) const; + + // The API enables developers to specify content or window size constraints. + // These functions combine them so that we can work with one set of + // constraints. + gfx::Size GetContentMinimumSize(const gfx::Insets& frame_insets) const; + gfx::Size GetContentMaximumSize(const gfx::Insets& frame_insets) const; + gfx::Size GetWindowMinimumSize(const gfx::Insets& frame_insets) const; + gfx::Size GetWindowMaximumSize(const gfx::Insets& frame_insets) const; }; class Delegate { @@ -287,9 +318,9 @@ class AppWindow : public content::NotificationObserver, // details. void ForcedFullscreen(); - // Set the minimum and maximum size that this window is allowed to be. - void SetMinimumSize(const gfx::Size& min_size); - void SetMaximumSize(const gfx::Size& max_size); + // Set the minimum and maximum size of the content bounds. + void SetContentMinimumSize(const gfx::Size& min_size); + void SetContentMaximumSize(const gfx::Size& max_size); enum ShowType { SHOW_ACTIVE, SHOW_INACTIVE }; @@ -394,17 +425,16 @@ class AppWindow : public content::NotificationObserver, void SaveWindowPosition(); // Helper method to adjust the cached bounds so that we can make sure it can - // be visible on the screen. See http://crbug.com/145752 . + // be visible on the screen. See http://crbug.com/145752. void AdjustBoundsToBeVisibleOnScreen(const gfx::Rect& cached_bounds, const gfx::Rect& cached_screen_bounds, const gfx::Rect& current_screen_bounds, const gfx::Size& minimum_size, gfx::Rect* bounds) const; - // Loads the appropriate default or cached window bounds and constrains them - // based on screen size and minimum/maximum size. Returns a new CreateParams - // that should be used to create the window. - CreateParams LoadDefaultsAndConstrain(CreateParams params) const; + // Loads the appropriate default or cached window bounds. Returns a new + // CreateParams that should be used to create the window. + CreateParams LoadDefaults(CreateParams params) const; // Load the app's image, firing a load state change when loaded. void UpdateExtensionAppIcon(); diff --git a/apps/size_constraints.cc b/apps/size_constraints.cc index bf7729a..4686965 100644 --- a/apps/size_constraints.cc +++ b/apps/size_constraints.cc @@ -6,6 +6,8 @@ #include <algorithm> +#include "ui/gfx/insets.h" + namespace apps { SizeConstraints::SizeConstraints() @@ -17,19 +19,33 @@ SizeConstraints::SizeConstraints(const gfx::Size& min_size, SizeConstraints::~SizeConstraints() {} +// static +gfx::Size SizeConstraints::AddFrameToConstraints( + const gfx::Size& size_constraints, + const gfx::Insets& frame_insets) { + return gfx::Size( + size_constraints.width() == kUnboundedSize + ? kUnboundedSize + : size_constraints.width() + frame_insets.width(), + size_constraints.height() == kUnboundedSize + ? kUnboundedSize + : size_constraints.height() + frame_insets.height()); +} + gfx::Size SizeConstraints::ClampSize(gfx::Size size) const { const gfx::Size max_size = GetMaximumSize(); if (max_size.width() != kUnboundedSize) - size.set_width(std::min(size.width(), GetMaximumSize().width())); + size.set_width(std::min(size.width(), max_size.width())); if (max_size.height() != kUnboundedSize) - size.set_height(std::min(size.height(), GetMaximumSize().height())); + size.set_height(std::min(size.height(), max_size.height())); size.SetToMax(GetMinimumSize()); return size; } bool SizeConstraints::HasMinimumSize() const { - return GetMinimumSize().width() != kUnboundedSize || - GetMinimumSize().height() != kUnboundedSize; + const gfx::Size min_size = GetMinimumSize(); + return min_size.width() != kUnboundedSize || + min_size.height() != kUnboundedSize; } bool SizeConstraints::HasMaximumSize() const { diff --git a/apps/size_constraints.h b/apps/size_constraints.h index 1af2692..353037b 100644 --- a/apps/size_constraints.h +++ b/apps/size_constraints.h @@ -7,6 +7,10 @@ #include "ui/gfx/geometry/size.h" +namespace gfx { +class Insets; +} + namespace apps { class SizeConstraints { @@ -19,6 +23,10 @@ class SizeConstraints { SizeConstraints(const gfx::Size& min_size, const gfx::Size& max_size); ~SizeConstraints(); + // Adds frame insets to a size constraint. + static gfx::Size AddFrameToConstraints(const gfx::Size& size_constraints, + const gfx::Insets& frame_insets); + // Returns the bounds with its size clamped to the min/max size. gfx::Size ClampSize(gfx::Size size) const; diff --git a/apps/ui/native_app_window.h b/apps/ui/native_app_window.h index 0093a3c..12b08d5 100644 --- a/apps/ui/native_app_window.h +++ b/apps/ui/native_app_window.h @@ -77,16 +77,16 @@ class NativeAppWindow : public ui::BaseWindow, virtual void UpdateShelfMenu() = 0; // Returns the minimum size constraints of the content. - virtual gfx::Size GetMinimumSize() const = 0; + virtual gfx::Size GetContentMinimumSize() const = 0; // Updates the minimum size constraints of the content. - virtual void SetMinimumSize(const gfx::Size& size) = 0; + virtual void SetContentMinimumSize(const gfx::Size& size) = 0; // Returns the maximum size constraints of the content. - virtual gfx::Size GetMaximumSize() const = 0; + virtual gfx::Size GetContentMaximumSize() const = 0; // Updates the maximum size constraints of the content. - virtual void SetMaximumSize(const gfx::Size& size) = 0; + virtual void SetContentMaximumSize(const gfx::Size& size) = 0; virtual ~NativeAppWindow() {} }; diff --git a/apps/ui/views/native_app_window_views.cc b/apps/ui/views/native_app_window_views.cc index 070851e..90a63af 100644 --- a/apps/ui/views/native_app_window_views.cc +++ b/apps/ui/views/native_app_window_views.cc @@ -37,8 +37,6 @@ void NativeAppWindowViews::Init(AppWindow* app_window, frameless_ = create_params.frame == AppWindow::FRAME_NONE; transparent_background_ = create_params.transparent_background; resizable_ = create_params.resizable; - size_constraints_.set_minimum_size(create_params.minimum_size); - size_constraints_.set_maximum_size(create_params.maximum_size); Observe(app_window_->web_contents()); window_ = new views::Widget; @@ -61,7 +59,8 @@ void NativeAppWindowViews::InitializeWindow( init_params.top_level = true; init_params.keep_on_top = create_params.always_on_top; window_->Init(init_params); - window_->CenterWindow(create_params.bounds.size()); + window_->CenterWindow( + create_params.GetInitialWindowBounds(gfx::Insets()).size()); } // ui::BaseWindow implementation. @@ -408,19 +407,19 @@ void NativeAppWindowViews::ShowWithApp() {} void NativeAppWindowViews::UpdateShelfMenu() {} -gfx::Size NativeAppWindowViews::GetMinimumSize() const { +gfx::Size NativeAppWindowViews::GetContentMinimumSize() const { return size_constraints_.GetMinimumSize(); } -void NativeAppWindowViews::SetMinimumSize(const gfx::Size& size) { +void NativeAppWindowViews::SetContentMinimumSize(const gfx::Size& size) { size_constraints_.set_minimum_size(size); } -gfx::Size NativeAppWindowViews::GetMaximumSize() const { +gfx::Size NativeAppWindowViews::GetContentMaximumSize() const { return size_constraints_.GetMaximumSize(); } -void NativeAppWindowViews::SetMaximumSize(const gfx::Size& size) { +void NativeAppWindowViews::SetContentMaximumSize(const gfx::Size& size) { size_constraints_.set_maximum_size(size); } diff --git a/apps/ui/views/native_app_window_views.h b/apps/ui/views/native_app_window_views.h index d8147f3..f22cea9 100644 --- a/apps/ui/views/native_app_window_views.h +++ b/apps/ui/views/native_app_window_views.h @@ -150,10 +150,10 @@ class NativeAppWindowViews : public NativeAppWindow, virtual void HideWithApp() OVERRIDE; virtual void ShowWithApp() OVERRIDE; virtual void UpdateShelfMenu() OVERRIDE; - virtual gfx::Size GetMinimumSize() const OVERRIDE; - virtual void SetMinimumSize(const gfx::Size& size) OVERRIDE; - virtual gfx::Size GetMaximumSize() const OVERRIDE; - virtual void SetMaximumSize(const gfx::Size& size) OVERRIDE; + virtual gfx::Size GetContentMinimumSize() const OVERRIDE; + virtual void SetContentMinimumSize(const gfx::Size& size) OVERRIDE; + virtual gfx::Size GetContentMaximumSize() const OVERRIDE; + virtual void SetContentMaximumSize(const gfx::Size& size) OVERRIDE; // web_modal::WebContentsModalDialogHost implementation. virtual gfx::NativeView GetHostView() const OVERRIDE; diff --git a/chrome/browser/apps/app_window_browsertest.cc b/chrome/browser/apps/app_window_browsertest.cc index dc77305..d11505a 100644 --- a/chrome/browser/apps/app_window_browsertest.cc +++ b/chrome/browser/apps/app_window_browsertest.cc @@ -126,16 +126,53 @@ class AppWindowAPITest : public extensions::PlatformAppBrowserTest { // These tests are flaky after https://codereview.chromium.org/57433010/. // See http://crbug.com/319613. -IN_PROC_BROWSER_TEST_F(AppWindowAPITest, DISABLED_TestCreate) { +IN_PROC_BROWSER_TEST_F(AppWindowAPITest, TestCreate) { ASSERT_TRUE(RunAppWindowAPITest("testCreate")) << message_; } -IN_PROC_BROWSER_TEST_F(AppWindowAPITest, TestSingleton) { - ASSERT_TRUE(RunAppWindowAPITest("testSingleton")) << message_; +#if defined(TOOLKIT_GTK) +#define MAYBE_TestDeprecatedBounds DISABLED_TestDeprecatedBounds +#else +#define MAYBE_TestDeprecatedBounds TestDeprecatedBounds +#endif + +IN_PROC_BROWSER_TEST_F(AppWindowAPITest, MAYBE_TestDeprecatedBounds) { + ASSERT_TRUE(RunAppWindowAPITest("testDeprecatedBounds")) << message_; +} + +#if defined(TOOLKIT_GTK) +#define MAYBE_TestInitialBounds DISABLED_TestInitialBounds +#else +#define MAYBE_TestInitialBounds TestInitialBounds +#endif + +IN_PROC_BROWSER_TEST_F(AppWindowAPITest, MAYBE_TestInitialBounds) { + ASSERT_TRUE(RunAppWindowAPITest("testInitialBounds")) << message_; } -IN_PROC_BROWSER_TEST_F(AppWindowAPITest, DISABLED_TestBounds) { - ASSERT_TRUE(RunAppWindowAPITest("testBounds")) << message_; +#if defined(TOOLKIT_GTK) +#define MAYBE_TestInitialBoundsInStable DISABLED_TestInitialBoundsInStable +#else +#define MAYBE_TestInitialBoundsInStable TestInitialBoundsInStable +#endif + +IN_PROC_BROWSER_TEST_F(AppWindowAPITest, MAYBE_TestInitialBoundsInStable) { + extensions::ScopedCurrentChannel channel(chrome::VersionInfo::CHANNEL_STABLE); + ASSERT_TRUE(RunAppWindowAPITest("testInitialBoundsInStable")) << message_; +} + +#if defined(TOOLKIT_GTK) +#define MAYBE_TestInitialConstraints DISABLED_TestInitialConstraints +#else +#define MAYBE_TestInitialConstraints TestInitialConstraints +#endif + +IN_PROC_BROWSER_TEST_F(AppWindowAPITest, MAYBE_TestInitialConstraints) { + ASSERT_TRUE(RunAppWindowAPITest("testInitialConstraints")) << message_; +} + +IN_PROC_BROWSER_TEST_F(AppWindowAPITest, TestSingleton) { + ASSERT_TRUE(RunAppWindowAPITest("testSingleton")) << message_; } IN_PROC_BROWSER_TEST_F(AppWindowAPITest, TestCloseEvent) { diff --git a/chrome/browser/extensions/api/app_current_window_internal/app_current_window_internal_api.cc b/chrome/browser/extensions/api/app_current_window_internal/app_current_window_internal_api.cc index ebba22d..03b7d71 100644 --- a/chrome/browser/extensions/api/app_current_window_internal/app_current_window_internal_api.cc +++ b/chrome/browser/extensions/api/app_current_window_internal/app_current_window_internal_api.cc @@ -133,20 +133,25 @@ bool AppCurrentWindowInternalSetBoundsFunction::RunWithWindow( AppWindow* window) { // Start with the current bounds, and change any values that are specified in // the incoming parameters. - gfx::Rect bounds = window->GetClientBounds(); + gfx::Rect window_bounds = window->GetBaseWindow()->GetBounds(); + gfx::Rect content_bounds = window->GetClientBounds(); + + // We need to maintain backcompatibility with a bug on Windows and ChromeOS, + // which returns the position of the window but the size of the content. scoped_ptr<SetBounds::Params> params(SetBounds::Params::Create(*args_)); CHECK(params.get()); if (params->bounds.left) - bounds.set_x(*(params->bounds.left)); + window_bounds.set_x(*(params->bounds.left)); if (params->bounds.top) - bounds.set_y(*(params->bounds.top)); + window_bounds.set_y(*(params->bounds.top)); if (params->bounds.width) - bounds.set_width(*(params->bounds.width)); + content_bounds.set_width(*(params->bounds.width)); if (params->bounds.height) - bounds.set_height(*(params->bounds.height)); + content_bounds.set_height(*(params->bounds.height)); - bounds.Inset(-window->GetBaseWindow()->GetFrameInsets()); - window->GetBaseWindow()->SetBounds(bounds); + content_bounds.Inset(-window->GetBaseWindow()->GetFrameInsets()); + window_bounds.set_size(content_bounds.size()); + window->GetBaseWindow()->SetBounds(window_bounds); return true; } @@ -159,10 +164,10 @@ bool AppCurrentWindowInternalSetMinWidthFunction::RunWithWindow( scoped_ptr<SetMinWidth::Params> params(SetMinWidth::Params::Create(*args_)); CHECK(params.get()); - gfx::Size min_size = window->GetBaseWindow()->GetMinimumSize(); + gfx::Size min_size = window->GetBaseWindow()->GetContentMinimumSize(); min_size.set_width(params->min_width.get() ? *(params->min_width) : kUnboundedSize); - window->SetMinimumSize(min_size); + window->SetContentMinimumSize(min_size); return true; } @@ -175,10 +180,10 @@ bool AppCurrentWindowInternalSetMinHeightFunction::RunWithWindow( scoped_ptr<SetMinHeight::Params> params(SetMinHeight::Params::Create(*args_)); CHECK(params.get()); - gfx::Size min_size = window->GetBaseWindow()->GetMinimumSize(); + gfx::Size min_size = window->GetBaseWindow()->GetContentMinimumSize(); min_size.set_height(params->min_height.get() ? *(params->min_height) : kUnboundedSize); - window->SetMinimumSize(min_size); + window->SetContentMinimumSize(min_size); return true; } @@ -191,10 +196,10 @@ bool AppCurrentWindowInternalSetMaxWidthFunction::RunWithWindow( scoped_ptr<SetMaxWidth::Params> params(SetMaxWidth::Params::Create(*args_)); CHECK(params.get()); - gfx::Size max_size = window->GetBaseWindow()->GetMaximumSize(); + gfx::Size max_size = window->GetBaseWindow()->GetContentMaximumSize(); max_size.set_width(params->max_width.get() ? *(params->max_width) : kUnboundedSize); - window->SetMaximumSize(max_size); + window->SetContentMaximumSize(max_size); return true; } @@ -207,10 +212,10 @@ bool AppCurrentWindowInternalSetMaxHeightFunction::RunWithWindow( scoped_ptr<SetMaxHeight::Params> params(SetMaxHeight::Params::Create(*args_)); CHECK(params.get()); - gfx::Size max_size = window->GetBaseWindow()->GetMaximumSize(); + gfx::Size max_size = window->GetBaseWindow()->GetContentMaximumSize(); max_size.set_height(params->max_height.get() ? *(params->max_height) : kUnboundedSize); - window->SetMaximumSize(max_size); + window->SetContentMaximumSize(max_size); return true; } diff --git a/chrome/browser/extensions/api/app_window/app_window_api.cc b/chrome/browser/extensions/api/app_window/app_window_api.cc index 856d82d..1fe6cd1 100644 --- a/chrome/browser/extensions/api/app_window/app_window_api.cc +++ b/chrome/browser/extensions/api/app_window/app_window_api.cc @@ -11,6 +11,7 @@ #include "apps/ui/native_app_window.h" #include "base/command_line.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" #include "base/time/time.h" #include "base/values.h" #include "chrome/browser/app_mode/app_mode_utils.h" @@ -56,6 +57,10 @@ const char kInvalidChannelForFrameOptions[] = const char kFrameOptionsAndFrame[] = "Only one of frame and frameOptions can be supplied."; const char kColorWithFrameNone[] = "Windows with no frame cannot have a color."; +const char kInvalidChannelForBounds[] = + "innerBounds and outerBounds are only available in dev channel."; +const char kConflictingBoundsOptions[] = + "The $1 property cannot be specified for both inner and outer bounds."; } // namespace app_window_constants const char kNoneFrameOption[] = "none"; @@ -93,6 +98,49 @@ class DevToolsRestorer : public base::RefCounted<DevToolsRestorer> { scoped_refptr<AppWindowCreateFunction> delayed_create_function_; }; +// If the same property is specified for the inner and outer bounds, raise an +// error. +bool CheckBoundsConflict(const scoped_ptr<int>& inner_property, + const scoped_ptr<int>& outer_property, + const std::string& property_name, + std::string* error) { + if (inner_property.get() && outer_property.get()) { + std::vector<std::string> subst; + subst.push_back(property_name); + *error = ReplaceStringPlaceholders( + app_window_constants::kConflictingBoundsOptions, subst, NULL); + return false; + } + + return true; +} + +// Copy over the bounds specification properties from the API to the +// AppWindow::CreateParams. +void CopyBoundsSpec( + const extensions::api::app_window::BoundsSpecification* input_spec, + apps::AppWindow::BoundsSpecification* create_spec) { + if (!input_spec) + return; + + if (input_spec->left.get()) + create_spec->bounds.set_x(*input_spec->left); + if (input_spec->top.get()) + create_spec->bounds.set_y(*input_spec->top); + if (input_spec->width.get()) + create_spec->bounds.set_width(*input_spec->width); + if (input_spec->height.get()) + create_spec->bounds.set_height(*input_spec->height); + if (input_spec->min_width.get()) + create_spec->minimum_size.set_width(*input_spec->min_width); + if (input_spec->min_height.get()) + create_spec->minimum_size.set_height(*input_spec->min_height); + if (input_spec->max_width.get()) + create_spec->maximum_size.set_width(*input_spec->max_width); + if (input_spec->max_height.get()) + create_spec->maximum_size.set_height(*input_spec->max_height); +} + } // namespace AppWindowCreateFunction::AppWindowCreateFunction() @@ -173,37 +221,8 @@ bool AppWindowCreateFunction::RunImpl() { } } - // TODO(jeremya): remove these, since they do the same thing as - // left/top/width/height. - if (options->default_width.get()) - create_params.bounds.set_width(*options->default_width.get()); - if (options->default_height.get()) - create_params.bounds.set_height(*options->default_height.get()); - if (options->default_left.get()) - create_params.bounds.set_x(*options->default_left.get()); - if (options->default_top.get()) - create_params.bounds.set_y(*options->default_top.get()); - - if (options->width.get()) - create_params.bounds.set_width(*options->width.get()); - if (options->height.get()) - create_params.bounds.set_height(*options->height.get()); - if (options->left.get()) - create_params.bounds.set_x(*options->left.get()); - if (options->top.get()) - create_params.bounds.set_y(*options->top.get()); - - if (options->bounds.get()) { - app_window::ContentBounds* bounds = options->bounds.get(); - if (bounds->width.get()) - create_params.bounds.set_width(*bounds->width.get()); - if (bounds->height.get()) - create_params.bounds.set_height(*bounds->height.get()); - if (bounds->left.get()) - create_params.bounds.set_x(*bounds->left.get()); - if (bounds->top.get()) - create_params.bounds.set_y(*bounds->top.get()); - } + if (!GetBoundsSpec(*options, &create_params, &error_)) + return false; if (GetCurrentChannel() <= chrome::VersionInfo::CHANNEL_DEV || GetExtension()->location() == extensions::Manifest::COMPONENT) { @@ -222,17 +241,6 @@ bool AppWindowCreateFunction::RunImpl() { create_params.transparent_background = *options->transparent_background; } - gfx::Size& minimum_size = create_params.minimum_size; - if (options->min_width.get()) - minimum_size.set_width(*options->min_width); - if (options->min_height.get()) - minimum_size.set_height(*options->min_height); - gfx::Size& maximum_size = create_params.maximum_size; - if (options->max_width.get()) - maximum_size.set_width(*options->max_width); - if (options->max_height.get()) - maximum_size.set_height(*options->max_height); - if (options->hidden.get()) create_params.hidden = *options->hidden.get(); @@ -301,6 +309,123 @@ bool AppWindowCreateFunction::RunImpl() { return true; } +bool AppWindowCreateFunction::GetBoundsSpec( + const extensions::api::app_window::CreateWindowOptions& options, + apps::AppWindow::CreateParams* params, + std::string* error) { + DCHECK(params); + DCHECK(error); + + if (options.inner_bounds.get() || options.outer_bounds.get()) { + // Parse the inner and outer bounds specifications. If developers use the + // new API, the deprecated fields will be ignored - do not attempt to merge + // them. + + if (GetCurrentChannel() > chrome::VersionInfo::CHANNEL_DEV) { + *error = app_window_constants::kInvalidChannelForBounds; + return false; + } + + const extensions::api::app_window::BoundsSpecification* inner_bounds = + options.inner_bounds.get(); + const extensions::api::app_window::BoundsSpecification* outer_bounds = + options.outer_bounds.get(); + if (inner_bounds && outer_bounds) { + if (!CheckBoundsConflict( + inner_bounds->left, outer_bounds->left, "left", error)) { + return false; + } + if (!CheckBoundsConflict( + inner_bounds->top, outer_bounds->top, "top", error)) { + return false; + } + if (!CheckBoundsConflict( + inner_bounds->width, outer_bounds->width, "width", error)) { + return false; + } + if (!CheckBoundsConflict( + inner_bounds->height, outer_bounds->height, "height", error)) { + return false; + } + if (!CheckBoundsConflict(inner_bounds->min_width, + outer_bounds->min_width, + "minWidth", + error)) { + return false; + } + if (!CheckBoundsConflict(inner_bounds->min_height, + outer_bounds->min_height, + "minHeight", + error)) { + return false; + } + if (!CheckBoundsConflict(inner_bounds->max_width, + outer_bounds->max_width, + "maxWidth", + error)) { + return false; + } + if (!CheckBoundsConflict(inner_bounds->max_height, + outer_bounds->max_height, + "maxHeight", + error)) { + return false; + } + } + + CopyBoundsSpec(inner_bounds, &(params->content_spec)); + CopyBoundsSpec(outer_bounds, &(params->window_spec)); + } else { + // Parse deprecated fields. + // Due to a bug in NativeAppWindow::GetFrameInsets() on Windows and ChromeOS + // the bounds set the position of the window and the size of the content. + // This will be preserved as apps may be relying on this behavior. + + if (options.default_width.get()) + params->content_spec.bounds.set_width(*options.default_width.get()); + if (options.default_height.get()) + params->content_spec.bounds.set_height(*options.default_height.get()); + if (options.default_left.get()) + params->window_spec.bounds.set_x(*options.default_left.get()); + if (options.default_top.get()) + params->window_spec.bounds.set_y(*options.default_top.get()); + + if (options.width.get()) + params->content_spec.bounds.set_width(*options.width.get()); + if (options.height.get()) + params->content_spec.bounds.set_height(*options.height.get()); + if (options.left.get()) + params->window_spec.bounds.set_x(*options.left.get()); + if (options.top.get()) + params->window_spec.bounds.set_y(*options.top.get()); + + if (options.bounds.get()) { + app_window::ContentBounds* bounds = options.bounds.get(); + if (bounds->width.get()) + params->content_spec.bounds.set_width(*bounds->width.get()); + if (bounds->height.get()) + params->content_spec.bounds.set_height(*bounds->height.get()); + if (bounds->left.get()) + params->window_spec.bounds.set_x(*bounds->left.get()); + if (bounds->top.get()) + params->window_spec.bounds.set_y(*bounds->top.get()); + } + + gfx::Size& minimum_size = params->content_spec.minimum_size; + if (options.min_width.get()) + minimum_size.set_width(*options.min_width); + if (options.min_height.get()) + minimum_size.set_height(*options.min_height); + gfx::Size& maximum_size = params->content_spec.maximum_size; + if (options.max_width.get()) + maximum_size.set_width(*options.max_width); + if (options.max_height.get()) + maximum_size.set_height(*options.max_height); + } + + return true; +} + AppWindow::Frame AppWindowCreateFunction::GetFrameFromString( const std::string& frame_string) { if (frame_string == kHtmlFrameOption && diff --git a/chrome/browser/extensions/api/app_window/app_window_api.h b/chrome/browser/extensions/api/app_window/app_window_api.h index 28c3585..1dbb105 100644 --- a/chrome/browser/extensions/api/app_window/app_window_api.h +++ b/chrome/browser/extensions/api/app_window/app_window_api.h @@ -28,7 +28,10 @@ class AppWindowCreateFunction : public ChromeAsyncExtensionFunction { virtual bool RunImpl() OVERRIDE; private: - bool inject_html_titlebar_; + bool GetBoundsSpec( + const extensions::api::app_window::CreateWindowOptions& options, + apps::AppWindow::CreateParams* params, + std::string* error); apps::AppWindow::Frame GetFrameFromString(const std::string& frame_string); bool GetFrameOptions( @@ -36,6 +39,8 @@ class AppWindowCreateFunction : public ChromeAsyncExtensionFunction { apps::AppWindow::CreateParams* create_params); void UpdateFrameOptionsForChannel( apps::AppWindow::CreateParams* create_params); + + bool inject_html_titlebar_; }; } // namespace extensions diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc index a809ab2..0a1c36d 100644 --- a/chrome/browser/extensions/api/tabs/tabs_api.cc +++ b/chrome/browser/extensions/api/tabs/tabs_api.cc @@ -525,7 +525,7 @@ bool WindowsCreateFunction::RunImpl() { if (chrome::GetActiveDesktop() == chrome::HOST_DESKTOP_TYPE_ASH) { AppWindow::CreateParams create_params; create_params.window_type = AppWindow::WINDOW_TYPE_V1_PANEL; - create_params.bounds = window_bounds; + create_params.window_spec.bounds = window_bounds; create_params.focused = saw_focus_key && focused; AppWindow* app_window = new AppWindow( window_profile, new ChromeAppWindowDelegate(), GetExtension()); diff --git a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h index 2527836..e7ca796 100644 --- a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h +++ b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.h @@ -136,10 +136,10 @@ class NativeAppWindowCocoa : public apps::NativeAppWindow, virtual void ShowWithApp() OVERRIDE; virtual void HideWithApp() OVERRIDE; virtual void UpdateShelfMenu() OVERRIDE; - virtual gfx::Size GetMinimumSize() const OVERRIDE; - virtual void SetMinimumSize(const gfx::Size& size) OVERRIDE; - virtual gfx::Size GetMaximumSize() const OVERRIDE; - virtual void SetMaximumSize(const gfx::Size& size) OVERRIDE; + virtual gfx::Size GetContentMinimumSize() const OVERRIDE; + virtual void SetContentMinimumSize(const gfx::Size& size) OVERRIDE; + virtual gfx::Size GetContentMaximumSize() const OVERRIDE; + virtual void SetContentMaximumSize(const gfx::Size& size) OVERRIDE; // WebContentsObserver implementation. virtual void RenderViewCreated(content::RenderViewHost* rvh) OVERRIDE; @@ -185,6 +185,11 @@ class NativeAppWindowCocoa : public apps::NativeAppWindow, // Cache |restored_bounds_| only if the window is currently restored. void UpdateRestoredBounds(); + // Update the minimum and maximum size constraints, fullscreen and resize + // controls. + void SetContentSizeConstraints(const gfx::Size& minimum_size, + const gfx::Size& maximum_size); + // Hides the window unconditionally. Used by Hide and HideWithApp. void HideWithoutMarkingHidden(); @@ -203,6 +208,7 @@ class NativeAppWindowCocoa : public apps::NativeAppWindow, bool is_fullscreen_; NSRect restored_bounds_; + bool is_resizable_; bool shows_resize_controls_; bool shows_fullscreen_controls_; diff --git a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.mm b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.mm index 53812b9..917c2dd 100644 --- a/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.mm +++ b/chrome/browser/ui/cocoa/apps/native_app_window_cocoa.mm @@ -87,6 +87,23 @@ NSInteger AlwaysOnTopWindowLevel() { return NSFloatingWindowLevel; } +NSRect GfxToCocoaBounds(gfx::Rect bounds) { + typedef apps::AppWindow::BoundsSpecification BoundsSpecification; + + NSRect main_screen_rect = [[[NSScreen screens] objectAtIndex:0] frame]; + + // If coordinates are unspecified, center window on primary screen. + if (bounds.x() == BoundsSpecification::kUnspecifiedPosition) + bounds.set_x(floor((NSWidth(main_screen_rect) - bounds.width()) / 2)); + if (bounds.y() == BoundsSpecification::kUnspecifiedPosition) + bounds.set_y(floor((NSHeight(main_screen_rect) - bounds.height()) / 2)); + + // Convert to Mac coordinates. + NSRect cocoa_bounds = NSRectFromCGRect(bounds.ToCGRect()); + cocoa_bounds.origin.y = NSHeight(main_screen_rect) - NSMaxY(cocoa_bounds); + return cocoa_bounds; +} + } // namespace @implementation NativeAppWindowController @@ -284,29 +301,13 @@ NativeAppWindowCocoa::NativeAppWindowCocoa( is_hidden_with_app_(false), is_maximized_(false), is_fullscreen_(false), + is_resizable_(params.resizable), + shows_resize_controls_(true), + shows_fullscreen_controls_(true), attention_request_id_(0), use_system_drag_(true) { Observe(web_contents()); - // Flip coordinates based on the primary screen. - NSRect main_screen_rect = [[[NSScreen screens] objectAtIndex:0] frame]; - NSRect cocoa_bounds = NSMakeRect(params.bounds.x(), - NSHeight(main_screen_rect) - params.bounds.y() - params.bounds.height(), - params.bounds.width(), params.bounds.height()); - - // If coordinates are < 0, center window on primary screen. - if (params.bounds.x() == INT_MIN) { - cocoa_bounds.origin.x = - floor((NSWidth(main_screen_rect) - NSWidth(cocoa_bounds)) / 2); - } - if (params.bounds.y() == INT_MIN) { - cocoa_bounds.origin.y = - floor((NSHeight(main_screen_rect) - NSHeight(cocoa_bounds)) / 2); - } - - // Initialize |restored_bounds_| after |cocoa_bounds| have been sanitized. - restored_bounds_ = cocoa_bounds; - base::scoped_nsobject<NSWindow> window; Class window_class; if (has_frame_) { @@ -319,12 +320,10 @@ NativeAppWindowCocoa::NativeAppWindowCocoa( window_class = [ShellFramelessNSWindow class]; } - size_constraints_.set_minimum_size(params.minimum_size); - size_constraints_.set_maximum_size(params.maximum_size); - shows_resize_controls_ = - params.resizable && !size_constraints_.HasFixedSize(); - shows_fullscreen_controls_ = - params.resizable && !size_constraints_.HasMaximumSize(); + // Estimate the initial bounds of the window. Once the frame insets are known, + // the window bounds and constraints can be set precisely. + NSRect cocoa_bounds = GfxToCocoaBounds( + params.GetInitialWindowBounds(gfx::Insets())); window.reset([[window_class alloc] initWithContentRect:cocoa_bounds styleMask:GetWindowStyleMask() @@ -340,12 +339,6 @@ NativeAppWindowCocoa::NativeAppWindowCocoa( [window setLevel:AlwaysOnTopWindowLevel()]; InitCollectionBehavior(window); - // Set the window to participate in Lion Fullscreen mode. Setting this flag - // has no effect on Snow Leopard or earlier. UI controls for fullscreen are - // only shown for apps that have unbounded size. - if (shows_fullscreen_controls_) - SetFullScreenCollectionBehavior(window, true); - window_controller_.reset( [[NativeAppWindowController alloc] initWithWindow:window.release()]); @@ -364,9 +357,14 @@ NativeAppWindowCocoa::NativeAppWindowCocoa( [[window_controller_ window] setDelegate:window_controller_]; [window_controller_ setAppWindow:this]; - // Update the size constraints of the NSWindow. - SetMinimumSize(params.minimum_size); - SetMaximumSize(params.maximum_size); + // We can now compute the precise window bounds and constraints. + gfx::Insets insets = GetFrameInsets(); + SetBounds(params.GetInitialWindowBounds(insets)); + SetContentSizeConstraints(params.GetContentMinimumSize(insets), + params.GetContentMaximumSize(insets)); + + // Initialize |restored_bounds_|. + restored_bounds_ = [this->window() frame]; extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryCocoa( Profile::FromBrowserContext(app_window_->browser_context()), @@ -613,13 +611,7 @@ void NativeAppWindowCocoa::SetBounds(const gfx::Rect& bounds) { if (checked_bounds.height() > max_size.height) checked_bounds.set_height(max_size.height); - NSRect cocoa_bounds = NSMakeRect(checked_bounds.x(), 0, - checked_bounds.width(), - checked_bounds.height()); - // Flip coordinates based on the primary screen. - NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; - cocoa_bounds.origin.y = NSHeight([screen frame]) - checked_bounds.bottom(); - + NSRect cocoa_bounds = GfxToCocoaBounds(checked_bounds); [window() setFrame:cocoa_bounds display:YES]; // setFrame: without animate: does not trigger a windowDidEndLiveResize: so // call it here. @@ -1037,34 +1029,56 @@ void NativeAppWindowCocoa::UpdateRestoredBounds() { restored_bounds_ = [window() frame]; } +void NativeAppWindowCocoa::SetContentSizeConstraints( + const gfx::Size& minimum_size, const gfx::Size& maximum_size) { + // Update the size constraints. + size_constraints_.set_minimum_size(minimum_size); + size_constraints_.set_maximum_size(maximum_size); + + gfx::Size min_size = size_constraints_.GetMinimumSize(); + [window() setContentMinSize:NSMakeSize(min_size.width(), min_size.height())]; + + gfx::Size max_size = size_constraints_.GetMaximumSize(); + const int kUnboundedSize = apps::SizeConstraints::kUnboundedSize; + CGFloat max_width = max_size.width() == kUnboundedSize ? + CGFLOAT_MAX : max_size.width(); + CGFloat max_height = max_size.height() == kUnboundedSize ? + CGFLOAT_MAX : max_size.height(); + [window() setContentMaxSize:NSMakeSize(max_width, max_height)]; + + // Update the window controls. + shows_resize_controls_ = + is_resizable_ && !size_constraints_.HasFixedSize(); + shows_fullscreen_controls_ = + is_resizable_ && !size_constraints_.HasMaximumSize(); + + if (!is_fullscreen_) { + [window() setStyleMask:GetWindowStyleMask()]; + + // Set the window to participate in Lion Fullscreen mode. Setting this flag + // has no effect on Snow Leopard or earlier. UI controls for fullscreen are + // only shown for apps that have unbounded size. + SetFullScreenCollectionBehavior(window(), shows_fullscreen_controls_); + } +} + void NativeAppWindowCocoa::UpdateShelfMenu() { // TODO(tmdiep): To be implemented for Mac. NOTIMPLEMENTED(); } -gfx::Size NativeAppWindowCocoa::GetMinimumSize() const { +gfx::Size NativeAppWindowCocoa::GetContentMinimumSize() const { return size_constraints_.GetMinimumSize(); } -void NativeAppWindowCocoa::SetMinimumSize(const gfx::Size& size) { - size_constraints_.set_minimum_size(size); - - gfx::Size min_size = size_constraints_.GetMinimumSize(); - [window() setContentMinSize:NSMakeSize(min_size.width(), min_size.height())]; +void NativeAppWindowCocoa::SetContentMinimumSize(const gfx::Size& size) { + SetContentSizeConstraints(size, size_constraints_.GetMaximumSize()); } -gfx::Size NativeAppWindowCocoa::GetMaximumSize() const { +gfx::Size NativeAppWindowCocoa::GetContentMaximumSize() const { return size_constraints_.GetMaximumSize(); } -void NativeAppWindowCocoa::SetMaximumSize(const gfx::Size& size) { - size_constraints_.set_maximum_size(size); - - gfx::Size max_size = size_constraints_.GetMaximumSize(); - const int kUnboundedSize = apps::SizeConstraints::kUnboundedSize; - CGFloat max_width = max_size.width() == kUnboundedSize ? - CGFLOAT_MAX : max_size.width(); - CGFloat max_height = max_size.height() == kUnboundedSize ? - CGFLOAT_MAX : max_size.height(); - [window() setContentMaxSize:NSMakeSize(max_width, max_height)]; +void NativeAppWindowCocoa::SetContentMaximumSize(const gfx::Size& size) { + SetContentSizeConstraints(size_constraints_.GetMinimumSize(), size); } diff --git a/chrome/browser/ui/gtk/apps/native_app_window_gtk.cc b/chrome/browser/ui/gtk/apps/native_app_window_gtk.cc index 1216992..c421c0a 100644 --- a/chrome/browser/ui/gtk/apps/native_app_window_gtk.cc +++ b/chrome/browser/ui/gtk/apps/native_app_window_gtk.cc @@ -61,30 +61,36 @@ NativeAppWindowGtk::NativeAppWindowGtk(AppWindow* app_window, web_contents()->GetView()->GetNativeView(); gtk_container_add(GTK_CONTAINER(window_), native_view); - if (params.bounds.x() != INT_MIN && params.bounds.y() != INT_MIN) - gtk_window_move(window_, params.bounds.x(), params.bounds.y()); + gfx::Insets frame_insets = GetFrameInsets(); + gfx::Rect initial_bounds = params.GetInitialWindowBounds(frame_insets); + + typedef apps::AppWindow::BoundsSpecification BoundsSpecification; + if (initial_bounds.x() != BoundsSpecification::kUnspecifiedPosition && + initial_bounds.y() != BoundsSpecification::kUnspecifiedPosition) { + gtk_window_move(window_, initial_bounds.x(), initial_bounds.y()); + } // This is done to avoid a WM "feature" where setting the window size to // the monitor size causes the WM to set the EWMH for full screen mode. - int win_height = params.bounds.height(); + int win_height = initial_bounds.height(); if (frameless_ && - gtk_window_util::BoundsMatchMonitorSize(window_, params.bounds)) { + gtk_window_util::BoundsMatchMonitorSize(window_, initial_bounds)) { win_height -= 1; } - gtk_window_set_default_size(window_, params.bounds.width(), win_height); + gtk_window_set_default_size(window_, initial_bounds.width(), win_height); resizable_ = params.resizable; if (!resizable_) { // If the window doesn't have a size request when we set resizable to // false, GTK will shrink the window to 1x1px. gtk_widget_set_size_request(GTK_WIDGET(window_), - params.bounds.width(), win_height); + initial_bounds.width(), win_height); gtk_window_set_resizable(window_, FALSE); } // make sure bounds_ and restored_bounds_ have correct values until we // get our first configure-event - bounds_ = restored_bounds_ = params.bounds; + bounds_ = restored_bounds_ = initial_bounds; gint x, y; gtk_window_get_position(window_, &x, &y); bounds_.set_origin(gfx::Point(x, y)); @@ -96,9 +102,11 @@ NativeAppWindowGtk::NativeAppWindowGtk(AppWindow* app_window, if (always_on_top_) gtk_window_set_keep_above(window_, TRUE); - size_constraints_.set_minimum_size(params.minimum_size); - size_constraints_.set_maximum_size(params.maximum_size); - UpdateWindowMinMaxSize(); + size_constraints_.set_minimum_size( + params.GetContentMinimumSize(frame_insets)); + size_constraints_.set_maximum_size( + params.GetContentMaximumSize(frame_insets)); + UpdateContentMinMaxSize(); // In some (older) versions of compiz, raising top-level windows when they // are partially off-screen causes them to get snapped back on screen, not @@ -479,7 +487,7 @@ void NativeAppWindowGtk::OnConfigureDebounced() { } } -void NativeAppWindowGtk::UpdateWindowMinMaxSize() { +void NativeAppWindowGtk::UpdateContentMinMaxSize() { GdkGeometry hints; int hints_mask = 0; if (size_constraints_.HasMinimumSize()) { @@ -739,20 +747,20 @@ void NativeAppWindowGtk::UpdateShelfMenu() { NOTIMPLEMENTED(); } -gfx::Size NativeAppWindowGtk::GetMinimumSize() const { +gfx::Size NativeAppWindowGtk::GetContentMinimumSize() const { return size_constraints_.GetMinimumSize(); } -void NativeAppWindowGtk::SetMinimumSize(const gfx::Size& size) { +void NativeAppWindowGtk::SetContentMinimumSize(const gfx::Size& size) { size_constraints_.set_minimum_size(size); - UpdateWindowMinMaxSize(); + UpdateContentMinMaxSize(); } -gfx::Size NativeAppWindowGtk::GetMaximumSize() const { +gfx::Size NativeAppWindowGtk::GetContentMaximumSize() const { return size_constraints_.GetMaximumSize(); } -void NativeAppWindowGtk::SetMaximumSize(const gfx::Size& size) { +void NativeAppWindowGtk::SetContentMaximumSize(const gfx::Size& size) { size_constraints_.set_maximum_size(size); - UpdateWindowMinMaxSize(); + UpdateContentMinMaxSize(); } diff --git a/chrome/browser/ui/gtk/apps/native_app_window_gtk.h b/chrome/browser/ui/gtk/apps/native_app_window_gtk.h index 0ba3edb..dca8474 100644 --- a/chrome/browser/ui/gtk/apps/native_app_window_gtk.h +++ b/chrome/browser/ui/gtk/apps/native_app_window_gtk.h @@ -87,10 +87,10 @@ class NativeAppWindowGtk : public apps::NativeAppWindow, virtual void ShowWithApp() OVERRIDE; virtual void UpdateShelfMenu() OVERRIDE; // Calls gtk_window_set_geometry_hints with the current size constraints. - virtual gfx::Size GetMinimumSize() const OVERRIDE; - virtual void SetMinimumSize(const gfx::Size& size) OVERRIDE; - virtual gfx::Size GetMaximumSize() const OVERRIDE; - virtual void SetMaximumSize(const gfx::Size& size) OVERRIDE; + virtual gfx::Size GetContentMinimumSize() const OVERRIDE; + virtual void SetContentMinimumSize(const gfx::Size& size) OVERRIDE; + virtual gfx::Size GetContentMaximumSize() const OVERRIDE; + virtual void SetContentMaximumSize(const gfx::Size& size) OVERRIDE; // web_modal::WebContentsModalDialogHost implementation. virtual gfx::NativeView GetHostView() const OVERRIDE; @@ -131,7 +131,7 @@ class NativeAppWindowGtk : public apps::NativeAppWindow, void OnConfigureDebounced(); - void UpdateWindowMinMaxSize(); + void UpdateContentMinMaxSize(); apps::AppWindow* app_window_; // weak - AppWindow owns NativeAppWindow. diff --git a/chrome/browser/ui/views/apps/chrome_native_app_window_views.cc b/chrome/browser/ui/views/apps/chrome_native_app_window_views.cc index e4c5fe3..e75277b 100644 --- a/chrome/browser/ui/views/apps/chrome_native_app_window_views.cc +++ b/chrome/browser/ui/views/apps/chrome_native_app_window_views.cc @@ -224,11 +224,6 @@ void ChromeNativeAppWindowViews::InitializeDefaultWindow( if (create_params.transparent_background) init_params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; init_params.keep_on_top = create_params.always_on_top; - gfx::Rect window_bounds = create_params.bounds; - bool position_specified = - window_bounds.x() != INT_MIN && window_bounds.y() != INT_MIN; - if (position_specified && !window_bounds.IsEmpty()) - init_params.bounds = window_bounds; #if defined(OS_LINUX) && !defined(OS_CHROMEOS) // Set up a custom WM_CLASS for app windows. This allows task switchers in @@ -242,13 +237,22 @@ void ChromeNativeAppWindowViews::InitializeDefaultWindow( OnBeforeWidgetInit(&init_params, window()); window()->Init(init_params); - gfx::Rect adjusted_bounds = window_bounds; - adjusted_bounds.Inset(-GetFrameInsets()); - // Center window if no position was specified. - if (!position_specified) - window()->CenterWindow(adjusted_bounds.size()); - else if (!adjusted_bounds.IsEmpty() && adjusted_bounds != window_bounds) - window()->SetBounds(adjusted_bounds); + // The frame insets are required to resolve the bounds specifications + // correctly. So we set the window bounds and constraints now. + gfx::Insets frame_insets = GetFrameInsets(); + gfx::Rect window_bounds = create_params.GetInitialWindowBounds(frame_insets); + SetContentMinimumSize(create_params.GetContentMinimumSize(frame_insets)); + SetContentMaximumSize(create_params.GetContentMaximumSize(frame_insets)); + if (!window_bounds.IsEmpty()) { + typedef apps::AppWindow::BoundsSpecification BoundsSpecification; + bool position_specified = + window_bounds.x() != BoundsSpecification::kUnspecifiedPosition && + window_bounds.y() != BoundsSpecification::kUnspecifiedPosition; + if (!position_specified) + window()->CenterWindow(window_bounds.size()); + else + window()->SetBounds(window_bounds); + } // Register accelarators supported by app windows. // TODO(jeremya/stevenjb): should these be registered for panels too? @@ -283,8 +287,10 @@ void ChromeNativeAppWindowViews::InitializePanelWindow( views::Widget::InitParams params(views::Widget::InitParams::TYPE_PANEL); params.delegate = this; - preferred_size_ = gfx::Size(create_params.bounds.width(), - create_params.bounds.height()); + gfx::Rect initial_window_bounds = + create_params.GetInitialWindowBounds(gfx::Insets()); + preferred_size_ = gfx::Size(initial_window_bounds.width(), + initial_window_bounds.height()); if (preferred_size_.width() == 0) preferred_size_.set_width(kDefaultPanelWidth); else if (preferred_size_.width() < kMinPanelWidth) @@ -314,8 +320,8 @@ void ChromeNativeAppWindowViews::InitializePanelWindow( #if defined(USE_ASH) if (create_params.state == ui::SHOW_STATE_DETACHED) { - gfx::Rect window_bounds(create_params.bounds.x(), - create_params.bounds.y(), + gfx::Rect window_bounds(initial_window_bounds.x(), + initial_window_bounds.y(), preferred_size_.width(), preferred_size_.height()); aura::Window* native_window = GetNativeWindow(); diff --git a/chrome/common/extensions/api/app_window.idl b/chrome/common/extensions/api/app_window.idl index 6ccbac7..66f728f 100644 --- a/chrome/common/extensions/api/app_window.idl +++ b/chrome/common/extensions/api/app_window.idl @@ -15,7 +15,7 @@ namespace app.window { long? height; }; - [nodoc] dictionary BoundsSpecification { + dictionary BoundsSpecification { // The X coordinate of the content or window. long? left; @@ -93,6 +93,34 @@ namespace app.window { // creating a new window. DOMString? id; + // Used to specify the initial position, initial size and constraints of the + // window's content (excluding window decorations). + // If an <code>id</code> is also specified and a window with a matching + // <code>id</code> has been shown before, the remembered bounds will be used + // instead. + // + // Note that the padding between the inner and outer bounds is determined by + // the OS. Therefore setting the same bounds property for both the + // <code>innerBounds</code> and <code>outerBounds</code> will result in an + // error. + // + // Currently only available on the Dev channel from Chrome 35. + BoundsSpecification? innerBounds; + + // Used to specify the initial position, initial size and constraints of the + // window (including window decorations such as the title bar and frame). + // If an <code>id</code> is also specified and a window with a matching + // <code>id</code> has been shown before, the remembered bounds will be used + // instead. + // + // Note that the padding between the inner and outer bounds is determined by + // the OS. Therefore setting the same bounds property for both the + // <code>innerBounds</code> and <code>outerBounds</code> will result in an + // error. + // + // Currently only available on the Dev channel from Chrome 35. + BoundsSpecification? outerBounds; + // Default width of the window. [nodoc, deprecated="Use $ref:BoundsSpecification."] long? defaultWidth; @@ -252,36 +280,36 @@ namespace app.window { // Set the window's inner bounds. static void setBounds(ContentBounds bounds); - // Get the current minimum width of the window. Returns |undefined| if there - // is no minimum. Currently only available on the Dev channel. + // Get the current minimum width of the window. Returns <code>null</code> if + // there is no minimum. Currently only available on the Dev channel. [nocompile] static long getMinWidth(); - // Get the current minimum height of the window. Returns |undefined| if - // there is no minimum. Currently only available on the Dev channel. + // Get the current minimum height of the window. Returns <code>null</code> + // if there is no minimum. Currently only available on the Dev channel. [nocompile] static long getMinHeight(); - // Get the current maximum width of the window. Returns |undefined| if there - // is no maximum. Currently only available on the Dev channel. + // Get the current maximum width of the window. Returns <code>null</code> if + // there is no maximum. Currently only available on the Dev channel. [nocompile] static long getMaxWidth(); - // Get the current maximum height of the window. Returns |undefined| if - // there is no maximum. Currently only available on the Dev channel. + // Get the current maximum height of the window. Returns <code>null</code> + // if there is no maximum. Currently only available on the Dev channel. [nocompile] static long getMaxHeight(); - // Set the current minimum width of the window. Set to |null| to remove the - // constraint. Currently only available on the Dev channel. + // Set the current minimum width of the window. Set to <code>null</code> to + // remove the constraint. Currently only available on the Dev channel. static void setMinWidth(optional long minWidth); - // Set the current minimum height of the window. Set to |null| to remove the - // constraint. Currently only available on the Dev channel. + // Set the current minimum height of the window. Set to <code>null</code> to + // remove the constraint. Currently only available on the Dev channel. static void setMinHeight(optional long minHeight); - // Set the current maximum width of the window. Set to |null| to remove the - // constraint. Currently only available on the Dev channel. + // Set the current maximum width of the window. Set to <code>null</code> to + // remove the constraint. Currently only available on the Dev channel. static void setMaxWidth(optional long maxWidth); - // Set the current maximum height of the window. Set to |null| to remove the - // constraint. Currently only available on the Dev channel. + // Set the current maximum height of the window. Set to <code>null</code> to + // remove the constraint. Currently only available on the Dev channel. static void setMaxHeight(optional long maxHeight); // Set the app icon for the window (experimental). @@ -331,11 +359,11 @@ namespace app.window { // all, in which case a default size and platform dependent position will // be used. // - // Another option is to use the bounds property, which will put the window - // at the specified coordinates with the specified size. If the window has - // a frame, it's total size will be the size given plus the size of the - // frame; that is, the size in bounds is the content size, not the window - // size. + // Another option is to use the <code>bounds</code> property, which will put + // the window at the specified coordinates with the specified size. If the + // window has a frame, it's total size will be the size given plus the size + // of the frame; that is, the size in bounds is the content size, not the + // window size. // // To automatically remember the positions of windows you can give them ids. // If a window has an id, This id is used to remember the size and position diff --git a/chrome/renderer/resources/extensions/app_window_custom_bindings.js b/chrome/renderer/resources/extensions/app_window_custom_bindings.js index 53eed44..8950667 100644 --- a/chrome/renderer/resources/extensions/app_window_custom_bindings.js +++ b/chrome/renderer/resources/extensions/app_window_custom_bindings.js @@ -169,9 +169,13 @@ appWindow.registerCustomHook(function(bindingsAPI) { this.contentWindow.close(); }; AppWindow.prototype.getBounds = function() { - var bounds = appWindowData.innerBounds; - return { left: bounds.left, top: bounds.top, - width: bounds.width, height: bounds.height }; + // This is to maintain backcompatibility with a bug on Windows and + // ChromeOS, which returns the position of the window but the size of + // the content. + var innerBounds = appWindowData.innerBounds; + var outerBounds = appWindowData.outerBounds; + return { left: outerBounds.left, top: outerBounds.top, + width: innerBounds.width, height: innerBounds.height }; }; AppWindow.prototype.getMinWidth = function() { return appWindowData.innerBounds.minWidth; diff --git a/chrome/test/data/extensions/platform_apps/window_api/test.js b/chrome/test/data/extensions/platform_apps/window_api/test.js index 9760900..62edcae 100644 --- a/chrome/test/data/extensions/platform_apps/window_api/test.js +++ b/chrome/test/data/extensions/platform_apps/window_api/test.js @@ -49,6 +49,65 @@ function waitForLoad(win, callback) { })); } +function assertConstraintsUnspecified(win) { + chrome.test.assertEq(null, win.innerBounds.minWidth); + chrome.test.assertEq(null, win.innerBounds.minHeight); + chrome.test.assertEq(null, win.innerBounds.maxWidth); + chrome.test.assertEq(null, win.innerBounds.maxHeight); + chrome.test.assertEq(null, win.outerBounds.minWidth); + chrome.test.assertEq(null, win.outerBounds.minHeight); + chrome.test.assertEq(null, win.outerBounds.maxWidth); + chrome.test.assertEq(null, win.outerBounds.maxHeight); +} + +function assertBoundsConsistent(win) { + // Ensure that the inner and outer bounds are consistent. Since platforms + // have different frame padding, we cannot check the sizes precisely. + // It is a reasonable assumption that all platforms will have a title bar at + // the top of the window. + chrome.test.assertTrue(win.innerBounds.left >= win.outerBounds.left); + chrome.test.assertTrue(win.innerBounds.top > win.outerBounds.top); + chrome.test.assertTrue(win.innerBounds.width <= win.outerBounds.width); + chrome.test.assertTrue(win.innerBounds.height < win.outerBounds.height); + + if (win.innerBounds.minWidth === null) + chrome.test.assertEq(null, win.outerBounds.minWidth); + else + chrome.test.assertTrue( + win.innerBounds.minWidth <= win.outerBounds.minWidth); + + if (win.innerBounds.minHeight === null) + chrome.test.assertEq(null, win.outerBounds.minHeight); + else + chrome.test.assertTrue( + win.innerBounds.minHeight < win.outerBounds.minHeight); + + if (win.innerBounds.maxWidth === null) + chrome.test.assertEq(null, win.outerBounds.maxWidth); + else + chrome.test.assertTrue( + win.innerBounds.maxWidth <= win.outerBounds.maxWidth); + + if (win.innerBounds.maxHeight === null) + chrome.test.assertEq(null, win.outerBounds.maxHeight); + else + chrome.test.assertTrue( + win.innerBounds.maxHeight < win.outerBounds.maxHeight); +} + +function testConflictingBoundsProperty(propertyName) { + var innerBounds = {}; + var outerBounds = {}; + innerBounds[propertyName] = 20; + outerBounds[propertyName] = 20; + chrome.app.window.create('test.html', { + innerBounds: innerBounds, + outerBounds: outerBounds + }, callbackFail('The ' + propertyName + ' property cannot be specified for ' + + 'both inner and outer bounds.') + ); +} + function testCreate() { chrome.test.runTests([ function basic() { @@ -103,51 +162,471 @@ function testCreate() { win2.contentWindow.close(); })); })); - }, + } + ]); +} +function testDeprecatedBounds() { + chrome.test.runTests([ function contentSize() { - chrome.app.window.create('test.html', - { bounds: { width: 250, height: 200 } }, callbackPass(function(win) { - assertFuzzyEq(250, win.contentWindow.innerWidth, defaultFuzzFactor); - assertFuzzyEq(200, win.contentWindow.innerHeight, defaultFuzzFactor); + var options = { bounds: { width: 250, height: 200 } }; + chrome.app.window.create('test.html', options, callbackPass( + function(win) { + var bounds = win.getBounds(); + chrome.test.assertEq(options.bounds.width, bounds.width); + chrome.test.assertEq(options.bounds.height, bounds.height); + chrome.test.assertEq(options.bounds.width, win.innerBounds.width); + chrome.test.assertEq(options.bounds.height, win.innerBounds.height); + win.close(); + })); + }, + + function windowPosition() { + var options = { bounds: { left: 250, top: 200 } }; + chrome.app.window.create('test.html', options, callbackPass( + function(win) { + var bounds = win.getBounds(); + chrome.test.assertEq(options.bounds.left, bounds.left); + chrome.test.assertEq(options.bounds.top, bounds.top); + chrome.test.assertEq(options.bounds.left, win.outerBounds.left); + chrome.test.assertEq(options.bounds.top, win.outerBounds.top); win.close(); })); }, function minSize() { - chrome.app.window.create('test.html', { + var options = { bounds: { width: 250, height: 250 }, minWidth: 400, minHeight: 450 - }, callbackPass(function(win) { - var w = win.contentWindow; - assertFuzzyEq(400, w.innerWidth, defaultFuzzFactor); - assertFuzzyEq(450, w.innerHeight, defaultFuzzFactor); - w.close(); + }; + chrome.app.window.create('test.html', options, callbackPass( + function(win) { + var bounds = win.getBounds(); + chrome.test.assertEq(options.minWidth, bounds.width); + chrome.test.assertEq(options.minHeight, bounds.height); + win.close(); })); }, function maxSize() { - chrome.app.window.create('test.html', { + var options = { bounds: { width: 250, height: 250 }, maxWidth: 200, maxHeight: 150 - }, callbackPass(function(win) { - var w = win.contentWindow; - assertFuzzyEq(200, w.innerWidth, defaultFuzzFactor); - assertFuzzyEq(150, w.innerHeight, defaultFuzzFactor); - w.close(); + }; + chrome.app.window.create('test.html', options, callbackPass( + function(win) { + var bounds = win.getBounds(); + chrome.test.assertEq(options.maxWidth, bounds.width); + chrome.test.assertEq(options.maxHeight, bounds.height); + win.close(); })); }, function minAndMaxSize() { - chrome.app.window.create('test.html', { + var options = { bounds: { width: 250, height: 250 }, minWidth: 400, minHeight: 450, maxWidth: 200, maxHeight: 150 + }; + chrome.app.window.create('test.html', options, callbackPass( + function(win) { + var bounds = win.getBounds(); + chrome.test.assertEq(options.minWidth, bounds.width); + chrome.test.assertEq(options.minHeight, bounds.height); + win.close(); + })); + }, + + function simpleSetBounds() { + chrome.app.window.create('test.html', + { bounds: { width: 250, height: 200 } }, callbackPass(function(win) { + var newBounds = {width: 400, height: 450}; + win.setBounds(newBounds); + chrome.test.waitForRoundTrip('msg', callbackPass(function() { + var bounds = win.getBounds(); + chrome.test.assertEq(newBounds.width, bounds.width); + chrome.test.assertEq(newBounds.height, bounds.height); + win.close(); + })); + })); + }, + + function heightOnlySetBounds() { + chrome.app.window.create('test.html', { + bounds: { width: 512, height: 256 } }, callbackPass(function(win) { - var w = win.contentWindow; - assertFuzzyEq(400, w.innerWidth, defaultFuzzFactor); - assertFuzzyEq(450, w.innerHeight, defaultFuzzFactor); - w.close(); + win.setBounds({ height: 512 }); + chrome.test.waitForRoundTrip('msg', callbackPass(function() { + var bounds = win.getBounds(); + chrome.test.assertEq(512, bounds.width); + chrome.test.assertEq(512, bounds.height); + win.close(); + })); + })); + }, + ]); +} + +function testInitialBounds() { + chrome.test.runTests([ + function testNoOptions() { + chrome.app.window.create('test.html', { + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertTrue(win.innerBounds.width > 0); + chrome.test.assertTrue(win.innerBounds.height > 0); + chrome.test.assertTrue(win.outerBounds.width > 0); + chrome.test.assertTrue(win.outerBounds.height > 0); + assertConstraintsUnspecified(win); + assertBoundsConsistent(win); + win.close(); + })); + }, + + function testInnerBoundsOnly() { + var innerBounds = { + left: 150, + top: 100, + width: 400, + height: 300 + }; + chrome.app.window.create('test.html', { + innerBounds: innerBounds + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(innerBounds.left, win.innerBounds.left); + chrome.test.assertEq(innerBounds.top, win.innerBounds.top); + chrome.test.assertEq(innerBounds.width, win.innerBounds.width); + chrome.test.assertEq(innerBounds.height, win.innerBounds.height); + assertBoundsConsistent(win); + assertConstraintsUnspecified(win); + win.close(); + })); + }, + + function testOuterBoundsOnly() { + var outerBounds = { + left: 150, + top: 100, + width: 400, + height: 300 + }; + chrome.app.window.create('test.html', { + outerBounds: outerBounds + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(outerBounds.left, win.outerBounds.left); + chrome.test.assertEq(outerBounds.top, win.outerBounds.top); + chrome.test.assertEq(outerBounds.width, win.outerBounds.width); + chrome.test.assertEq(outerBounds.height, win.outerBounds.height); + assertBoundsConsistent(win); + assertConstraintsUnspecified(win); + win.close(); + })); + }, + + function testFrameless() { + var outerBounds = { + left: 150, + top: 100, + width: 400, + height: 300 + }; + chrome.app.window.create('test.html', { + outerBounds: outerBounds, + frame: 'none' + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(outerBounds.left, win.outerBounds.left); + chrome.test.assertEq(outerBounds.top, win.outerBounds.top); + chrome.test.assertEq(outerBounds.width, win.outerBounds.width); + chrome.test.assertEq(outerBounds.height, win.outerBounds.height); + chrome.test.assertEq(outerBounds.left, win.innerBounds.left); + chrome.test.assertEq(outerBounds.top, win.innerBounds.top); + chrome.test.assertEq(outerBounds.width, win.innerBounds.width); + chrome.test.assertEq(outerBounds.height, win.innerBounds.height); + assertConstraintsUnspecified(win); + win.close(); + })); + }, + + function testInnerSizeAndOuterPos() { + var innerBounds = { + width: 400, + height: 300 + }; + var outerBounds = { + left: 150, + top: 100 + }; + chrome.app.window.create('test.html', { + innerBounds: innerBounds, + outerBounds: outerBounds + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(outerBounds.left, win.outerBounds.left); + chrome.test.assertEq(outerBounds.top, win.outerBounds.top); + chrome.test.assertEq(innerBounds.width, win.innerBounds.width); + chrome.test.assertEq(innerBounds.height, win.innerBounds.height); + assertBoundsConsistent(win); + assertConstraintsUnspecified(win); + win.close(); + })); + }, + + function testInnerAndOuterBoundsEdgeCase() { + var innerBounds = { + left: 150, + height: 300 + }; + var outerBounds = { + width: 400, + top: 100 + }; + chrome.app.window.create('test.html', { + innerBounds: innerBounds, + outerBounds: outerBounds + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(innerBounds.left, win.innerBounds.left); + chrome.test.assertEq(innerBounds.height, win.innerBounds.height); + chrome.test.assertEq(outerBounds.top, win.outerBounds.top); + chrome.test.assertEq(outerBounds.width, win.outerBounds.width); + assertBoundsConsistent(win); + assertConstraintsUnspecified(win); + win.close(); + })); + }, + + function testPositionOnly() { + var outerBounds = { + left: 150, + top: 100 + }; + chrome.app.window.create('test.html', { + outerBounds: outerBounds + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(outerBounds.left, win.outerBounds.left); + chrome.test.assertEq(outerBounds.top, win.outerBounds.top); + chrome.test.assertTrue(win.innerBounds.width > 0); + chrome.test.assertTrue(win.innerBounds.height > 0); + chrome.test.assertTrue(win.outerBounds.width > 0); + chrome.test.assertTrue(win.outerBounds.height > 0); + assertBoundsConsistent(win); + assertConstraintsUnspecified(win); + win.close(); + })); + }, + + function testSizeOnly() { + var outerBounds = { + width: 500, + height: 400 + }; + chrome.app.window.create('test.html', { + outerBounds: outerBounds + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(outerBounds.width, win.outerBounds.width); + chrome.test.assertEq(outerBounds.height, win.outerBounds.height); + assertBoundsConsistent(win); + assertConstraintsUnspecified(win); + win.close(); + })); + }, + + function testConflictingProperties() { + testConflictingBoundsProperty("width"); + testConflictingBoundsProperty("height"); + testConflictingBoundsProperty("left"); + testConflictingBoundsProperty("top"); + testConflictingBoundsProperty("minWidth"); + testConflictingBoundsProperty("minHeight"); + testConflictingBoundsProperty("maxWidth"); + testConflictingBoundsProperty("maxHeight"); + } + ]); +} + +function testInitialBoundsInStable() { + chrome.test.runTests([ + function testInnerBounds() { + var innerBounds = { + width: 600, + height: 400 + }; + chrome.app.window.create('test.html', { + innerBounds: innerBounds + }, callbackFail('innerBounds and outerBounds are only available in'+ + ' dev channel.') + ); + }, + + function testOuterBounds() { + var outerBounds = { + width: 600, + height: 400 + }; + chrome.app.window.create('test.html', { + outerBounds: outerBounds + }, callbackFail('innerBounds and outerBounds are only available in'+ + ' dev channel.') + ); + } + ]); +} + +function testInitialConstraints() { + chrome.test.runTests([ + function testMaxInnerConstraints() { + var innerBounds = { + width: 800, + height: 600, + maxWidth: 500, + maxHeight: 400 + }; + chrome.app.window.create('test.html', { + innerBounds: innerBounds + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(innerBounds.maxWidth, win.innerBounds.width); + chrome.test.assertEq(innerBounds.maxHeight, win.innerBounds.height); + chrome.test.assertEq(innerBounds.maxWidth, win.innerBounds.maxWidth); + chrome.test.assertEq(innerBounds.maxHeight, win.innerBounds.maxHeight); + assertBoundsConsistent(win); + win.close(); + })); + }, + + function testMinInnerConstraints() { + var innerBounds = { + width: 100, + height: 100, + minWidth: 300, + minHeight: 200 + }; + chrome.app.window.create('test.html', { + innerBounds: innerBounds + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(innerBounds.minWidth, win.innerBounds.width); + chrome.test.assertEq(innerBounds.minHeight, win.innerBounds.height); + chrome.test.assertEq(innerBounds.minWidth, win.innerBounds.minWidth); + chrome.test.assertEq(innerBounds.minHeight, win.innerBounds.minHeight); + assertBoundsConsistent(win); + win.close(); + })); + }, + + function testMaxOuterConstraints() { + var outerBounds = { + width: 800, + height: 600, + maxWidth: 500, + maxHeight: 400 + }; + chrome.app.window.create('test.html', { + outerBounds: outerBounds + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(outerBounds.maxWidth, win.outerBounds.width); + chrome.test.assertEq(outerBounds.maxHeight, win.outerBounds.height); + chrome.test.assertEq(outerBounds.maxWidth, win.outerBounds.maxWidth); + chrome.test.assertEq(outerBounds.maxHeight, win.outerBounds.maxHeight); + assertBoundsConsistent(win); + win.close(); + })); + }, + + function testMinOuterConstraints() { + var outerBounds = { + width: 100, + height: 100, + minWidth: 300, + minHeight: 200 + }; + chrome.app.window.create('test.html', { + outerBounds: outerBounds + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(outerBounds.minWidth, win.outerBounds.width); + chrome.test.assertEq(outerBounds.minHeight, win.outerBounds.height); + chrome.test.assertEq(outerBounds.minWidth, win.outerBounds.minWidth); + chrome.test.assertEq(outerBounds.minHeight, win.outerBounds.minHeight); + assertBoundsConsistent(win); + win.close(); + })); + }, + + function testMixedConstraints() { + var innerBounds = { + width: 100, + minHeight: 300 + }; + var outerBounds = { + height: 100, + minWidth: 400, + }; + chrome.app.window.create('test.html', { + innerBounds: innerBounds, + outerBounds: outerBounds + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(outerBounds.minWidth, win.outerBounds.width); + chrome.test.assertEq(innerBounds.minHeight, win.innerBounds.height); + chrome.test.assertEq(outerBounds.minWidth, win.outerBounds.minWidth); + chrome.test.assertEq(innerBounds.minHeight, win.innerBounds.minHeight); + assertBoundsConsistent(win); + win.close(); + })); + }, + + function testBadConstraints() { + var outerBounds = { + width: 500, + height: 400, + minWidth: 800, + minHeight: 700, + maxWidth: 300, + maxHeight: 200 + }; + chrome.app.window.create('test.html', { + outerBounds: outerBounds + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(outerBounds.minWidth, win.outerBounds.width); + chrome.test.assertEq(outerBounds.minHeight, win.outerBounds.height); + chrome.test.assertEq(outerBounds.minWidth, win.outerBounds.minWidth); + chrome.test.assertEq(outerBounds.minHeight, win.outerBounds.minHeight); + chrome.test.assertEq(outerBounds.minWidth, win.outerBounds.maxWidth); + chrome.test.assertEq(outerBounds.minHeight, win.outerBounds.maxHeight); + assertBoundsConsistent(win); + win.close(); + })); + }, + + function testFrameless() { + var outerBounds = { + minWidth: 50, + minHeight: 50, + maxWidth: 800, + maxHeight: 800 + }; + chrome.app.window.create('test.html', { + outerBounds: outerBounds, + frame: 'none' + }, callbackPass(function(win) { + chrome.test.assertTrue(win != null); + chrome.test.assertEq(outerBounds.minWidth, win.outerBounds.minWidth); + chrome.test.assertEq(outerBounds.minHeight, win.outerBounds.minHeight); + chrome.test.assertEq(outerBounds.maxWidth, win.outerBounds.maxWidth); + chrome.test.assertEq(outerBounds.maxHeight, win.outerBounds.maxHeight); + chrome.test.assertEq(outerBounds.minWidth, win.innerBounds.minWidth); + chrome.test.assertEq(outerBounds.minHeight, win.innerBounds.minHeight); + chrome.test.assertEq(outerBounds.maxWidth, win.innerBounds.maxWidth); + chrome.test.assertEq(outerBounds.maxHeight, win.innerBounds.maxHeight); + win.close(); })); } ]); @@ -177,37 +656,6 @@ function testSingleton() { ]); } -function testBounds() { - chrome.test.runTests([ - function simpleSetBounds() { - chrome.app.window.create('test.html', - { bounds: { width: 250, height: 200 } }, callbackPass(function(win) { - var b = win.getBounds(); - win.setBounds({width: 400, height: 450}) - // Listen to onresize here rather than win.onBoundsChanged, because - // onBoundsChanged is fired before the web contents are resized. - win.contentWindow.onresize = callbackPass(function() { - assertFuzzyEq(400, win.contentWindow.innerWidth, defaultFuzzFactor); - assertFuzzyEq(450, win.contentWindow.innerHeight, defaultFuzzFactor); - win.close(); - }); - })); - }, - - function heightOnlySetBounds() { - chrome.app.window.create('test.html', { - bounds: { width: 512, height: 256 } - }, callbackPass(function(win) { - win.setBounds({ height: 512 }); - win.contentWindow.onresize = callbackPass(function() { - assertFuzzyEq(512, win.contentWindow.innerHeight, defaultFuzzFactor); - win.close(); - }); - })); - }, - ]); -} - function testCloseEvent() { chrome.test.runTests([ function basic() { |