// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/gtk/apps/native_app_window_gtk.h" #include #include #include "base/message_loop/message_pump_gtk.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/gtk/extensions/extension_keybinding_registry_gtk.h" #include "chrome/browser/ui/gtk/gtk_util.h" #include "chrome/browser/ui/gtk/gtk_window_util.h" #include "chrome/browser/web_applications/web_app.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" #include "extensions/common/extension.h" #include "ui/base/x/active_window_watcher_x.h" #include "ui/gfx/gtk_util.h" #include "ui/gfx/image/image.h" #include "ui/gfx/rect.h" using apps::AppWindow; namespace { // The timeout in milliseconds before we'll get the true window position with // gtk_window_get_position() after the last GTK configure-event signal. const int kDebounceTimeoutMilliseconds = 100; const char* kAtomsToCache[] = { "_NET_WM_STATE", "_NET_WM_STATE_HIDDEN", NULL }; } // namespace NativeAppWindowGtk::NativeAppWindowGtk(AppWindow* app_window, const AppWindow::CreateParams& params) : app_window_(app_window), window_(NULL), state_(GDK_WINDOW_STATE_WITHDRAWN), is_active_(false), content_thinks_its_fullscreen_(false), maximize_pending_(false), frameless_(params.frame == AppWindow::FRAME_NONE), always_on_top_(params.always_on_top), frame_cursor_(NULL), atom_cache_(base::MessagePumpGtk::GetDefaultXDisplay(), kAtomsToCache), is_x_event_listened_(false) { Observe(web_contents()); window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); gfx::NativeView native_view = web_contents()->GetView()->GetNativeView(); gtk_container_add(GTK_CONTAINER(window_), native_view); 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 = initial_bounds.height(); if (frameless_ && gtk_window_util::BoundsMatchMonitorSize(window_, initial_bounds)) { win_height -= 1; } 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_), 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_ = initial_bounds; gint x, y; gtk_window_get_position(window_, &x, &y); bounds_.set_origin(gfx::Point(x, y)); // Hide titlebar when {frame: 'none'} specified on AppWindow.. if (frameless_) gtk_window_set_decorated(window_, false); if (always_on_top_) gtk_window_set_keep_above(window_, TRUE); SetContentSizeConstraints(params.GetContentMinimumSize(frame_insets), params.GetContentMaximumSize(frame_insets)); // 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 // always even on the current virtual desktop. If we are running under // compiz, suppress such raises, as they are not necessary in compiz anyway. if (ui::GuessWindowManager() == ui::WM_COMPIZ) suppress_window_raise_ = true; gtk_window_set_title(window_, extension()->name().c_str()); std::string app_name = web_app::GenerateApplicationNameFromExtensionId( extension()->id()); gtk_window_util::SetWindowCustomClass(window_, web_app::GetWMClassFromAppName(app_name)); g_signal_connect(window_, "delete-event", G_CALLBACK(OnMainWindowDeleteEventThunk), this); g_signal_connect(window_, "configure-event", G_CALLBACK(OnConfigureThunk), this); g_signal_connect(window_, "window-state-event", G_CALLBACK(OnWindowStateThunk), this); if (frameless_) { g_signal_connect(window_, "button-press-event", G_CALLBACK(OnButtonPressThunk), this); g_signal_connect(window_, "motion-notify-event", G_CALLBACK(OnMouseMoveEventThunk), this); } // If _NET_WM_STATE_HIDDEN is in _NET_SUPPORTED, listen for XEvent to work // around GTK+ not reporting minimization state changes. See comment in the // |OnXEvent|. std::vector< ::Atom> supported_atoms; if (ui::GetAtomArrayProperty(ui::GetX11RootWindow(), "_NET_SUPPORTED", &supported_atoms)) { if (std::find(supported_atoms.begin(), supported_atoms.end(), atom_cache_.GetAtom("_NET_WM_STATE_HIDDEN")) != supported_atoms.end()) { GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(window_)); gdk_window_add_filter(window, &NativeAppWindowGtk::OnXEventThunk, this); is_x_event_listened_ = true; } } // Add the keybinding registry. extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryGtk( Profile::FromBrowserContext(app_window_->browser_context()), window_, extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY, NULL)); ui::ActiveWindowWatcherX::AddObserver(this); } NativeAppWindowGtk::~NativeAppWindowGtk() { ui::ActiveWindowWatcherX::RemoveObserver(this); if (is_x_event_listened_) { gdk_window_remove_filter(NULL, &NativeAppWindowGtk::OnXEventThunk, this); } } bool NativeAppWindowGtk::IsActive() const { if (ui::ActiveWindowWatcherX::WMSupportsActivation()) return is_active_; // This still works even though we don't get the activation notification. return gtk_window_is_active(window_); } bool NativeAppWindowGtk::IsMaximized() const { return (state_ & GDK_WINDOW_STATE_MAXIMIZED); } bool NativeAppWindowGtk::IsMinimized() const { return (state_ & GDK_WINDOW_STATE_ICONIFIED); } bool NativeAppWindowGtk::IsFullscreen() const { return (state_ & GDK_WINDOW_STATE_FULLSCREEN); } gfx::NativeWindow NativeAppWindowGtk::GetNativeWindow() { return window_; } gfx::Rect NativeAppWindowGtk::GetRestoredBounds() const { gfx::Rect window_bounds = restored_bounds_; window_bounds.Inset(-GetFrameInsets()); return window_bounds; } ui::WindowShowState NativeAppWindowGtk::GetRestoredState() const { if (IsMaximized()) return ui::SHOW_STATE_MAXIMIZED; if (IsFullscreen()) return ui::SHOW_STATE_FULLSCREEN; return ui::SHOW_STATE_NORMAL; } gfx::Rect NativeAppWindowGtk::GetBounds() const { // :GetBounds() is expecting the outer window bounds to be returned (ie. // including window decorations). The internal |bounds_| is not including them // and trying to add the decoration to |bounds_| would require calling // gdk_window_get_frame_extents. The best thing to do is to directly get the // frame bounds and only use the internal value if we can't. GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); if (!gdk_window) return bounds_; GdkRectangle window_bounds = {0}; gdk_window_get_frame_extents(gdk_window, &window_bounds); return gfx::Rect(window_bounds.x, window_bounds.y, window_bounds.width, window_bounds.height); } void NativeAppWindowGtk::Show() { gtk_window_present(window_); } void NativeAppWindowGtk::ShowInactive() { gtk_window_set_focus_on_map(window_, false); gtk_widget_show(GTK_WIDGET(window_)); } void NativeAppWindowGtk::Hide() { gtk_widget_hide(GTK_WIDGET(window_)); } void NativeAppWindowGtk::Close() { app_window_->OnNativeWindowChanged(); // Cancel any pending callback from the window configure debounce timer. window_configure_debounce_timer_.Stop(); GtkWidget* window = GTK_WIDGET(window_); // To help catch bugs in any event handlers that might get fired during the // destruction, set window_ to NULL before any handlers will run. window_ = NULL; // OnNativeClose does a delete this so no other members should // be accessed after. gtk_widget_destroy is safe (and must // be last). app_window_->OnNativeClose(); gtk_widget_destroy(window); } void NativeAppWindowGtk::Activate() { gtk_window_present(window_); } void NativeAppWindowGtk::Deactivate() { gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_))); } void NativeAppWindowGtk::Maximize() { // Represent the window first in order to keep the maximization behavior // consistency with Windows platform. Otherwise the window will be hidden if // it has been minimized. gtk_window_present(window_); if (!resizable_) { // When the window is not resizable, we still want to make this call succeed // but gtk will not allow it if the window is not resizable. The actual // maximization will happen with the subsequent OnConfigureDebounced call, // that will be triggered when the window manager's resizable property // changes. maximize_pending_ = true; gtk_window_set_resizable(window_, TRUE); } else { gtk_window_maximize(window_); } } void NativeAppWindowGtk::Minimize() { gtk_window_iconify(window_); } void NativeAppWindowGtk::Restore() { if (IsMaximized()) gtk_window_unmaximize(window_); else if (IsMinimized()) gtk_window_deiconify(window_); // Represent the window to keep restoration behavior consistency with Windows // platform. // TODO(zhchbin): verify whether we need this until http://crbug.com/261013 is // fixed. gtk_window_present(window_); } void NativeAppWindowGtk::SetBounds(const gfx::Rect& bounds) { gfx::Rect content_bounds = bounds; gtk_window_move(window_, content_bounds.x(), content_bounds.y()); if (!resizable_) { if (frameless_ && gtk_window_util::BoundsMatchMonitorSize(window_, content_bounds)) { content_bounds.set_height(content_bounds.height() - 1); } // TODO(jeremya): set_size_request doesn't honor min/max size, so the // bounds should be constrained manually. gtk_widget_set_size_request(GTK_WIDGET(window_), content_bounds.width(), content_bounds.height()); } else { gtk_window_util::SetWindowSize(window_, gfx::Size(bounds.width(), bounds.height())); } } GdkFilterReturn NativeAppWindowGtk::OnXEvent(GdkXEvent* gdk_x_event, GdkEvent* gdk_event) { // Work around GTK+ not reporting minimization state changes. Listen // for _NET_WM_STATE property changes and use _NET_WM_STATE_HIDDEN's // presence to set or clear the iconified bit if _NET_WM_STATE_HIDDEN // is supported. http://crbug.com/162794. XEvent* x_event = static_cast(gdk_x_event); std::vector< ::Atom> atom_list; if (x_event->type == PropertyNotify && x_event->xproperty.atom == atom_cache_.GetAtom("_NET_WM_STATE") && GTK_WIDGET(window_)->window && ui::GetAtomArrayProperty(GDK_WINDOW_XWINDOW(GTK_WIDGET(window_)->window), "_NET_WM_STATE", &atom_list)) { std::vector< ::Atom>::iterator it = std::find(atom_list.begin(), atom_list.end(), atom_cache_.GetAtom("_NET_WM_STATE_HIDDEN")); GdkWindowState previous_state = state_; state_ = (it != atom_list.end()) ? GDK_WINDOW_STATE_ICONIFIED : static_cast(state_ & ~GDK_WINDOW_STATE_ICONIFIED); if (previous_state != state_) { app_window_->OnNativeWindowChanged(); } } return GDK_FILTER_CONTINUE; } void NativeAppWindowGtk::FlashFrame(bool flash) { gtk_window_set_urgency_hint(window_, flash); } bool NativeAppWindowGtk::IsAlwaysOnTop() const { return always_on_top_; } void NativeAppWindowGtk::RenderViewHostChanged( content::RenderViewHost* old_host, content::RenderViewHost* new_host) { web_contents()->GetView()->Focus(); } void NativeAppWindowGtk::SetAlwaysOnTop(bool always_on_top) { if (always_on_top_ != always_on_top) { // gdk_window_get_state() does not give us the correct value for the // GDK_WINDOW_STATE_ABOVE bit. Cache the current state. always_on_top_ = always_on_top; gtk_window_set_keep_above(window_, always_on_top_ ? TRUE : FALSE); } } gfx::NativeView NativeAppWindowGtk::GetHostView() const { NOTIMPLEMENTED(); return NULL; } gfx::Point NativeAppWindowGtk::GetDialogPosition(const gfx::Size& size) { gint current_width = 0; gint current_height = 0; gtk_window_get_size(window_, ¤t_width, ¤t_height); return gfx::Point(current_width / 2 - size.width() / 2, current_height / 2 - size.height() / 2); } gfx::Size NativeAppWindowGtk::GetMaximumDialogSize() { gint current_width = 0; gint current_height = 0; gtk_window_get_size(window_, ¤t_width, ¤t_height); return gfx::Size(current_width, current_height); } void NativeAppWindowGtk::AddObserver( web_modal::ModalDialogHostObserver* observer) { observer_list_.AddObserver(observer); } void NativeAppWindowGtk::RemoveObserver( web_modal::ModalDialogHostObserver* observer) { observer_list_.RemoveObserver(observer); } void NativeAppWindowGtk::ActiveWindowChanged(GdkWindow* active_window) { // Do nothing if we're in the process of closing the browser window. if (!window_) return; is_active_ = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window; if (is_active_) app_window_->OnNativeWindowActivated(); } // Callback for the delete event. This event is fired when the user tries to // close the window (e.g., clicking on the X in the window manager title bar). gboolean NativeAppWindowGtk::OnMainWindowDeleteEvent(GtkWidget* widget, GdkEvent* event) { Close(); // Return true to prevent the GTK window from being destroyed. Close will // destroy it for us. return TRUE; } gboolean NativeAppWindowGtk::OnConfigure(GtkWidget* widget, GdkEventConfigure* event) { // We update |bounds_| but not |restored_bounds_| here. The latter needs // to be updated conditionally when the window is non-maximized and non- // fullscreen, but whether those state updates have been processed yet is // window-manager specific. We update |restored_bounds_| in the debounced // handler below, after the window state has been updated. bounds_.SetRect(event->x, event->y, event->width, event->height); // The GdkEventConfigure* we get here doesn't have quite the right // coordinates though (they're relative to the drawable window area, rather // than any window manager decorations, if enabled), so we need to call // gtk_window_get_position() to get the right values. (Otherwise session // restore, if enabled, will restore windows to incorrect positions.) That's // a round trip to the X server though, so we set a debounce timer and only // call it (in OnConfigureDebounced() below) after we haven't seen a // reconfigure event in a short while. // We don't use Reset() because the timer may not yet be running. // (In that case Stop() is a no-op.) window_configure_debounce_timer_.Stop(); window_configure_debounce_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds), this, &NativeAppWindowGtk::OnConfigureDebounced); return FALSE; } void NativeAppWindowGtk::OnConfigureDebounced() { gtk_window_util::UpdateWindowPosition(this, &bounds_, &restored_bounds_); app_window_->OnNativeWindowChanged(); FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver, observer_list_, OnPositionRequiresUpdate()); // Fullscreen of non-resizable windows requires them to be made resizable // first. After that takes effect and OnConfigure is called we transition // to fullscreen. if (!IsFullscreen() && IsFullscreenOrPending()) { gtk_window_fullscreen(window_); } // maximize_pending_ is the boolean that lets us know that the window is in // the process of being maximized but was set as not resizable. // This function will be called twice during the maximization process: // 1. gtk_window_maximize() is called to maximize the window; // 2. gtk_set_resizable(, FALSE) is called to make the window no longer // resizable. // gtk_window_maximize() will cause ::OnConfigureDebounced to be called // again, at which time we will run into the second step. if (maximize_pending_) { if (!(state_ & GDK_WINDOW_STATE_MAXIMIZED)) { gtk_window_maximize(window_); } else { maximize_pending_ = false; if (!resizable_) gtk_window_set_resizable(window_, FALSE); } } } void NativeAppWindowGtk::UpdateContentMinMaxSize() { GdkGeometry hints; int hints_mask = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE; gfx::Size min_size = size_constraints_.GetMinimumSize(); hints.min_height = min_size.height(); hints.min_width = min_size.width(); gfx::Size max_size = size_constraints_.GetMaximumSize(); const int kUnboundedSize = apps::SizeConstraints::kUnboundedSize; hints.max_height = max_size.height() == kUnboundedSize ? G_MAXINT : max_size.height(); hints.max_width = max_size.width() == kUnboundedSize ? G_MAXINT : max_size.width(); gtk_window_set_geometry_hints( window_, GTK_WIDGET(window_), &hints, static_cast(hints_mask)); } gboolean NativeAppWindowGtk::OnWindowState(GtkWidget* sender, GdkEventWindowState* event) { state_ = event->new_window_state; if (content_thinks_its_fullscreen_ && !(state_ & GDK_WINDOW_STATE_FULLSCREEN)) { content_thinks_its_fullscreen_ = false; content::RenderViewHost* rvh = web_contents()->GetRenderViewHost(); if (rvh) rvh->ExitFullscreen(); } return FALSE; } bool NativeAppWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) { if (!frameless_) return false; if (IsMaximized() || IsFullscreen()) return false; return gtk_window_util::GetWindowEdge(bounds_.size(), 0, x, y, edge); } gboolean NativeAppWindowGtk::OnMouseMoveEvent(GtkWidget* widget, GdkEventMotion* event) { if (!frameless_) { // Reset the cursor. if (frame_cursor_) { frame_cursor_ = NULL; gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), NULL); } return FALSE; } if (!resizable_) return FALSE; // Update the cursor if we're on the custom frame border. GdkWindowEdge edge; bool has_hit_edge = GetWindowEdge(static_cast(event->x), static_cast(event->y), &edge); GdkCursorType new_cursor = GDK_LAST_CURSOR; if (has_hit_edge) new_cursor = gtk_window_util::GdkWindowEdgeToGdkCursorType(edge); GdkCursorType last_cursor = GDK_LAST_CURSOR; if (frame_cursor_) last_cursor = frame_cursor_->type; if (last_cursor != new_cursor) { frame_cursor_ = has_hit_edge ? gfx::GetCursor(new_cursor) : NULL; gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), frame_cursor_); } return FALSE; } gboolean NativeAppWindowGtk::OnButtonPress(GtkWidget* widget, GdkEventButton* event) { DCHECK(frameless_); // Make the button press coordinate relative to the browser window. int win_x, win_y; GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); gdk_window_get_origin(gdk_window, &win_x, &win_y); GdkWindowEdge edge; gfx::Point point(static_cast(event->x_root - win_x), static_cast(event->y_root - win_y)); bool has_hit_edge = resizable_ && GetWindowEdge(point.x(), point.y(), &edge); bool has_hit_titlebar = draggable_region_ && draggable_region_->contains(event->x, event->y); if (event->button == 1) { if (GDK_BUTTON_PRESS == event->type) { // Raise the window after a click on either the titlebar or the border to // match the behavior of most window managers, unless that behavior has // been suppressed. if ((has_hit_titlebar || has_hit_edge) && !suppress_window_raise_) gdk_window_raise(GTK_WIDGET(widget)->window); if (has_hit_edge) { gtk_window_begin_resize_drag(window_, edge, event->button, static_cast(event->x_root), static_cast(event->y_root), event->time); return TRUE; } else if (has_hit_titlebar) { return gtk_window_util::HandleTitleBarLeftMousePress( window_, bounds_, event); } } else if (GDK_2BUTTON_PRESS == event->type) { if (has_hit_titlebar && resizable_) { // Maximize/restore on double click. if (IsMaximized()) { gtk_window_util::UnMaximize(GTK_WINDOW(widget), bounds_, restored_bounds_); } else { gtk_window_maximize(window_); } return TRUE; } } } else if (event->button == 2) { if (has_hit_titlebar || has_hit_edge) gdk_window_lower(gdk_window); return TRUE; } return FALSE; } // NativeAppWindow implementation: void NativeAppWindowGtk::SetFullscreen(int fullscreen_types) { bool fullscreen = (fullscreen_types != AppWindow::FULLSCREEN_TYPE_NONE); content_thinks_its_fullscreen_ = fullscreen; if (fullscreen) { if (resizable_) { gtk_window_fullscreen(window_); } else { // We must first make the window resizable. That won't take effect // immediately, so OnConfigureDebounced completes the fullscreen call. gtk_window_set_resizable(window_, TRUE); } } else { gtk_window_unfullscreen(window_); if (!resizable_) gtk_window_set_resizable(window_, FALSE); } } bool NativeAppWindowGtk::IsFullscreenOrPending() const { // |content_thinks_its_fullscreen_| is used when transitioning, and when // the state change will not be made for some time. However, it is possible // for a state update to be made before the final fullscreen state comes. // In that case, |content_thinks_its_fullscreen_| will be cleared, but we // will fall back to |IsFullscreen| which will soon have the correct state. return content_thinks_its_fullscreen_ || IsFullscreen(); } bool NativeAppWindowGtk::IsDetached() const { return false; } void NativeAppWindowGtk::UpdateWindowIcon() { Profile* profile = Profile::FromBrowserContext(app_window_->browser_context()); gfx::Image app_icon = app_window_->app_icon(); if (!app_icon.IsEmpty()) gtk_util::SetWindowIcon(window_, profile, app_icon.ToGdkPixbuf()); else gtk_util::SetWindowIcon(window_, profile); } void NativeAppWindowGtk::UpdateWindowTitle() { base::string16 title = app_window_->GetTitle(); gtk_window_set_title(window_, base::UTF16ToUTF8(title).c_str()); } void NativeAppWindowGtk::UpdateBadgeIcon() { NOTIMPLEMENTED(); } void NativeAppWindowGtk::UpdateDraggableRegions( const std::vector& regions) { // Draggable region is not supported for non-frameless window. if (!frameless_) return; draggable_region_.reset(AppWindow::RawDraggableRegionsToSkRegion(regions)); } SkRegion* NativeAppWindowGtk::GetDraggableRegion() { return draggable_region_.get(); } void NativeAppWindowGtk::UpdateShape(scoped_ptr region) { NOTIMPLEMENTED(); } void NativeAppWindowGtk::HandleKeyboardEvent( const content::NativeWebKeyboardEvent& event) { // No-op. } bool NativeAppWindowGtk::IsFrameless() const { return frameless_; } bool NativeAppWindowGtk::HasFrameColor() const { return false; } SkColor NativeAppWindowGtk::FrameColor() const { return SkColor(); } gfx::Insets NativeAppWindowGtk::GetFrameInsets() const { if (frameless_) return gfx::Insets(); GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); if (!gdk_window) return gfx::Insets(); gint current_width = 0; gint current_height = 0; gtk_window_get_size(window_, ¤t_width, ¤t_height); gint current_x = 0; gint current_y = 0; gdk_window_get_position(gdk_window, ¤t_x, ¤t_y); GdkRectangle rect_with_decorations = {0}; gdk_window_get_frame_extents(gdk_window, &rect_with_decorations); int left_inset = current_x - rect_with_decorations.x; int top_inset = current_y - rect_with_decorations.y; return gfx::Insets( top_inset, left_inset, rect_with_decorations.height - current_height - top_inset, rect_with_decorations.width - current_width - left_inset); } void NativeAppWindowGtk::HideWithApp() {} void NativeAppWindowGtk::ShowWithApp() {} void NativeAppWindowGtk::UpdateShelfMenu() { // TODO(tmdiep): To be implemented for GTK. NOTIMPLEMENTED(); } gfx::Size NativeAppWindowGtk::GetContentMinimumSize() const { return size_constraints_.GetMinimumSize(); } gfx::Size NativeAppWindowGtk::GetContentMaximumSize() const { return size_constraints_.GetMaximumSize(); } void NativeAppWindowGtk::SetContentSizeConstraints( const gfx::Size& min_size, const gfx::Size& max_size) { bool changed = size_constraints_.GetMinimumSize() != min_size || size_constraints_.GetMaximumSize() != max_size; if (!changed) return; size_constraints_.set_minimum_size(min_size); size_constraints_.set_maximum_size(max_size); UpdateContentMinMaxSize(); }