1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/gtk/apps/native_app_window_gtk.h" 6 7 #include <gdk/gdkx.h> 8 #include <vector> 9 10 #include "base/message_loop/message_pump_gtk.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "chrome/browser/profiles/profile.h" 13 #include "chrome/browser/ui/gtk/extensions/extension_keybinding_registry_gtk.h" 14 #include "chrome/browser/ui/gtk/gtk_util.h" 15 #include "chrome/browser/ui/gtk/gtk_window_util.h" 16 #include "chrome/browser/web_applications/web_app.h" 17 #include "chrome/common/extensions/extension.h" 18 #include "content/public/browser/render_view_host.h" 19 #include "content/public/browser/render_widget_host_view.h" 20 #include "content/public/browser/web_contents.h" 21 #include "content/public/browser/web_contents_view.h" 22 #include "ui/base/x/active_window_watcher_x.h" 23 #include "ui/gfx/gtk_util.h" 24 #include "ui/gfx/image/image.h" 25 #include "ui/gfx/rect.h" 26 27 using apps::ShellWindow; 28 29 namespace { 30 31 // The timeout in milliseconds before we'll get the true window position with 32 // gtk_window_get_position() after the last GTK configure-event signal. 33 const int kDebounceTimeoutMilliseconds = 100; 34 35 const char* kAtomsToCache[] = { 36 "_NET_WM_STATE", 37 "_NET_WM_STATE_HIDDEN", 38 NULL 39 }; 40 41 } // namespace 42 43 NativeAppWindowGtk::NativeAppWindowGtk(ShellWindow* shell_window, 44 const ShellWindow::CreateParams& params) 45 : shell_window_(shell_window), 46 window_(NULL), 47 state_(GDK_WINDOW_STATE_WITHDRAWN), 48 is_active_(false), 49 content_thinks_its_fullscreen_(false), 50 frameless_(params.frame == ShellWindow::FRAME_NONE), 51 frame_cursor_(NULL), 52 atom_cache_(base::MessagePumpGtk::GetDefaultXDisplay(), kAtomsToCache), 53 is_x_event_listened_(false) { 54 window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); 55 56 gfx::NativeView native_view = 57 web_contents()->GetView()->GetNativeView(); 58 gtk_container_add(GTK_CONTAINER(window_), native_view); 59 60 if (params.bounds.x() != INT_MIN && params.bounds.y() != INT_MIN) 61 gtk_window_move(window_, params.bounds.x(), params.bounds.y()); 62 63 // This is done to avoid a WM "feature" where setting the window size to 64 // the monitor size causes the WM to set the EWMH for full screen mode. 65 int win_height = params.bounds.height(); 66 if (frameless_ && 67 gtk_window_util::BoundsMatchMonitorSize(window_, params.bounds)) { 68 win_height -= 1; 69 } 70 gtk_window_set_default_size(window_, params.bounds.width(), win_height); 71 72 resizable_ = params.resizable; 73 if (!resizable_) { 74 // If the window doesn't have a size request when we set resizable to 75 // false, GTK will shrink the window to 1x1px. 76 gtk_widget_set_size_request(GTK_WIDGET(window_), 77 params.bounds.width(), win_height); 78 gtk_window_set_resizable(window_, FALSE); 79 } 80 81 // make sure bounds_ and restored_bounds_ have correct values until we 82 // get our first configure-event 83 bounds_ = restored_bounds_ = params.bounds; 84 gint x, y; 85 gtk_window_get_position(window_, &x, &y); 86 bounds_.set_origin(gfx::Point(x, y)); 87 88 // Hide titlebar when {frame: 'none'} specified on ShellWindow. 89 if (frameless_) 90 gtk_window_set_decorated(window_, false); 91 92 int min_width = params.minimum_size.width(); 93 int min_height = params.minimum_size.height(); 94 int max_width = params.maximum_size.width(); 95 int max_height = params.maximum_size.height(); 96 GdkGeometry hints; 97 int hints_mask = 0; 98 if (min_width || min_height) { 99 hints.min_height = min_height; 100 hints.min_width = min_width; 101 hints_mask |= GDK_HINT_MIN_SIZE; 102 } 103 if (max_width || max_height) { 104 hints.max_height = max_height ? max_height : G_MAXINT; 105 hints.max_width = max_width ? max_width : G_MAXINT; 106 hints_mask |= GDK_HINT_MAX_SIZE; 107 } 108 if (hints_mask) { 109 gtk_window_set_geometry_hints( 110 window_, 111 GTK_WIDGET(window_), 112 &hints, 113 static_cast<GdkWindowHints>(hints_mask)); 114 } 115 116 // In some (older) versions of compiz, raising top-level windows when they 117 // are partially off-screen causes them to get snapped back on screen, not 118 // always even on the current virtual desktop. If we are running under 119 // compiz, suppress such raises, as they are not necessary in compiz anyway. 120 if (ui::GuessWindowManager() == ui::WM_COMPIZ) 121 suppress_window_raise_ = true; 122 123 gtk_window_set_title(window_, extension()->name().c_str()); 124 125 std::string app_name = web_app::GenerateApplicationNameFromExtensionId( 126 extension()->id()); 127 gtk_window_util::SetWindowCustomClass(window_, 128 web_app::GetWMClassFromAppName(app_name)); 129 130 g_signal_connect(window_, "delete-event", 131 G_CALLBACK(OnMainWindowDeleteEventThunk), this); 132 g_signal_connect(window_, "configure-event", 133 G_CALLBACK(OnConfigureThunk), this); 134 g_signal_connect(window_, "window-state-event", 135 G_CALLBACK(OnWindowStateThunk), this); 136 if (frameless_) { 137 g_signal_connect(window_, "button-press-event", 138 G_CALLBACK(OnButtonPressThunk), this); 139 g_signal_connect(window_, "motion-notify-event", 140 G_CALLBACK(OnMouseMoveEventThunk), this); 141 } 142 143 // If _NET_WM_STATE_HIDDEN is in _NET_SUPPORTED, listen for XEvent to work 144 // around GTK+ not reporting minimization state changes. See comment in the 145 // |OnXEvent|. 146 std::vector< ::Atom> supported_atoms; 147 if (ui::GetAtomArrayProperty(ui::GetX11RootWindow(), 148 "_NET_SUPPORTED", 149 &supported_atoms)) { 150 if (std::find(supported_atoms.begin(), 151 supported_atoms.end(), 152 atom_cache_.GetAtom("_NET_WM_STATE_HIDDEN")) != 153 supported_atoms.end()) { 154 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(window_)); 155 gdk_window_add_filter(window, 156 &NativeAppWindowGtk::OnXEventThunk, 157 this); 158 is_x_event_listened_ = true; 159 } 160 } 161 162 // Add the keybinding registry. 163 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryGtk( 164 shell_window_->profile(), 165 window_, 166 extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY, 167 shell_window_)); 168 169 ui::ActiveWindowWatcherX::AddObserver(this); 170 } 171 172 NativeAppWindowGtk::~NativeAppWindowGtk() { 173 ui::ActiveWindowWatcherX::RemoveObserver(this); 174 if (is_x_event_listened_) { 175 gdk_window_remove_filter(NULL, 176 &NativeAppWindowGtk::OnXEventThunk, 177 this); 178 } 179 } 180 181 bool NativeAppWindowGtk::IsActive() const { 182 if (ui::ActiveWindowWatcherX::WMSupportsActivation()) 183 return is_active_; 184 185 // This still works even though we don't get the activation notification. 186 return gtk_window_is_active(window_); 187 } 188 189 bool NativeAppWindowGtk::IsMaximized() const { 190 return (state_ & GDK_WINDOW_STATE_MAXIMIZED); 191 } 192 193 bool NativeAppWindowGtk::IsMinimized() const { 194 return (state_ & GDK_WINDOW_STATE_ICONIFIED); 195 } 196 197 bool NativeAppWindowGtk::IsFullscreen() const { 198 return (state_ & GDK_WINDOW_STATE_FULLSCREEN); 199 } 200 201 gfx::NativeWindow NativeAppWindowGtk::GetNativeWindow() { 202 return window_; 203 } 204 205 gfx::Rect NativeAppWindowGtk::GetRestoredBounds() const { 206 gfx::Rect window_bounds = restored_bounds_; 207 window_bounds.Inset(-GetFrameInsets()); 208 return window_bounds; 209 } 210 211 ui::WindowShowState NativeAppWindowGtk::GetRestoredState() const { 212 if (IsMaximized()) 213 return ui::SHOW_STATE_MAXIMIZED; 214 return ui::SHOW_STATE_NORMAL; 215 } 216 217 gfx::Rect NativeAppWindowGtk::GetBounds() const { 218 gfx::Rect window_bounds = bounds_; 219 window_bounds.Inset(-GetFrameInsets()); 220 return window_bounds; 221 } 222 223 void NativeAppWindowGtk::Show() { 224 gtk_window_present(window_); 225 } 226 227 void NativeAppWindowGtk::ShowInactive() { 228 gtk_window_set_focus_on_map(window_, false); 229 gtk_widget_show(GTK_WIDGET(window_)); 230 } 231 232 void NativeAppWindowGtk::Hide() { 233 gtk_widget_hide(GTK_WIDGET(window_)); 234 } 235 236 void NativeAppWindowGtk::Close() { 237 shell_window_->OnNativeWindowChanged(); 238 239 // Cancel any pending callback from the window configure debounce timer. 240 window_configure_debounce_timer_.Stop(); 241 242 GtkWidget* window = GTK_WIDGET(window_); 243 // To help catch bugs in any event handlers that might get fired during the 244 // destruction, set window_ to NULL before any handlers will run. 245 window_ = NULL; 246 247 // OnNativeClose does a delete this so no other members should 248 // be accessed after. gtk_widget_destroy is safe (and must 249 // be last). 250 shell_window_->OnNativeClose(); 251 gtk_widget_destroy(window); 252 } 253 254 void NativeAppWindowGtk::Activate() { 255 gtk_window_present(window_); 256 } 257 258 void NativeAppWindowGtk::Deactivate() { 259 gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_))); 260 } 261 262 void NativeAppWindowGtk::Maximize() { 263 // Represent the window first in order to keep the maximization behavior 264 // consistency with Windows platform. Otherwise the window will be hidden if 265 // it has been minimized. 266 gtk_window_present(window_); 267 gtk_window_maximize(window_); 268 } 269 270 void NativeAppWindowGtk::Minimize() { 271 gtk_window_iconify(window_); 272 } 273 274 void NativeAppWindowGtk::Restore() { 275 if (IsMaximized()) 276 gtk_window_unmaximize(window_); 277 else if (IsMinimized()) 278 gtk_window_deiconify(window_); 279 280 // Represent the window to keep restoration behavior consistency with Windows 281 // platform. 282 // TODO(zhchbin): verify whether we need this until http://crbug.com/261013 is 283 // fixed. 284 gtk_window_present(window_); 285 } 286 287 void NativeAppWindowGtk::SetBounds(const gfx::Rect& bounds) { 288 gfx::Rect content_bounds = bounds; 289 content_bounds.Inset(GetFrameInsets()); 290 gtk_window_move(window_, content_bounds.x(), content_bounds.y()); 291 if (!resizable_) { 292 if (frameless_ && 293 gtk_window_util::BoundsMatchMonitorSize(window_, content_bounds)) { 294 content_bounds.set_height(content_bounds.height() - 1); 295 } 296 // TODO(jeremya): set_size_request doesn't honor min/max size, so the 297 // bounds should be constrained manually. 298 gtk_widget_set_size_request(GTK_WIDGET(window_), 299 content_bounds.width(), content_bounds.height()); 300 } else { 301 gtk_window_util::SetWindowSize(window_, 302 gfx::Size(bounds.width(), bounds.height())); 303 } 304 } 305 306 GdkFilterReturn NativeAppWindowGtk::OnXEvent(GdkXEvent* gdk_x_event, 307 GdkEvent* gdk_event) { 308 // Work around GTK+ not reporting minimization state changes. Listen 309 // for _NET_WM_STATE property changes and use _NET_WM_STATE_HIDDEN's 310 // presence to set or clear the iconified bit if _NET_WM_STATE_HIDDEN 311 // is supported. http://crbug.com/162794. 312 XEvent* x_event = static_cast<XEvent*>(gdk_x_event); 313 std::vector< ::Atom> atom_list; 314 315 if (x_event->type == PropertyNotify && 316 x_event->xproperty.atom == atom_cache_.GetAtom("_NET_WM_STATE") && 317 ui::GetAtomArrayProperty(GDK_WINDOW_XWINDOW(GTK_WIDGET(window_)->window), 318 "_NET_WM_STATE", 319 &atom_list)) { 320 std::vector< ::Atom>::iterator it = 321 std::find(atom_list.begin(), 322 atom_list.end(), 323 atom_cache_.GetAtom("_NET_WM_STATE_HIDDEN")); 324 state_ = (it != atom_list.end()) ? GDK_WINDOW_STATE_ICONIFIED : 325 static_cast<GdkWindowState>(state_ & ~GDK_WINDOW_STATE_ICONIFIED); 326 327 shell_window_->OnNativeWindowChanged(); 328 } 329 330 return GDK_FILTER_CONTINUE; 331 } 332 333 void NativeAppWindowGtk::FlashFrame(bool flash) { 334 gtk_window_set_urgency_hint(window_, flash); 335 } 336 337 bool NativeAppWindowGtk::IsAlwaysOnTop() const { 338 return false; 339 } 340 341 void NativeAppWindowGtk::RenderViewHostChanged() { 342 web_contents()->GetView()->Focus(); 343 } 344 345 gfx::Insets NativeAppWindowGtk::GetFrameInsets() const { 346 if (frameless_) 347 return gfx::Insets(); 348 GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); 349 if (!gdk_window) 350 return gfx::Insets(); 351 352 gint current_width = 0; 353 gint current_height = 0; 354 gtk_window_get_size(window_, ¤t_width, ¤t_height); 355 gint current_x = 0; 356 gint current_y = 0; 357 gdk_window_get_position(gdk_window, ¤t_x, ¤t_y); 358 GdkRectangle rect_with_decorations = {0}; 359 gdk_window_get_frame_extents(gdk_window, 360 &rect_with_decorations); 361 362 int left_inset = current_x - rect_with_decorations.x; 363 int top_inset = current_y - rect_with_decorations.y; 364 return gfx::Insets( 365 top_inset, 366 left_inset, 367 rect_with_decorations.height - current_height - top_inset, 368 rect_with_decorations.width - current_width - left_inset); 369 } 370 371 gfx::NativeView NativeAppWindowGtk::GetHostView() const { 372 NOTIMPLEMENTED(); 373 return NULL; 374 } 375 376 gfx::Point NativeAppWindowGtk::GetDialogPosition(const gfx::Size& size) { 377 gint current_width = 0; 378 gint current_height = 0; 379 gtk_window_get_size(window_, ¤t_width, ¤t_height); 380 return gfx::Point(current_width / 2 - size.width() / 2, 381 current_height / 2 - size.height() / 2); 382 } 383 384 void NativeAppWindowGtk::AddObserver( 385 web_modal::WebContentsModalDialogHostObserver* observer) { 386 observer_list_.AddObserver(observer); 387 } 388 389 void NativeAppWindowGtk::RemoveObserver( 390 web_modal::WebContentsModalDialogHostObserver* observer) { 391 observer_list_.RemoveObserver(observer); 392 } 393 394 void NativeAppWindowGtk::ActiveWindowChanged(GdkWindow* active_window) { 395 // Do nothing if we're in the process of closing the browser window. 396 if (!window_) 397 return; 398 399 is_active_ = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window; 400 if (is_active_) 401 shell_window_->OnNativeWindowActivated(); 402 } 403 404 // Callback for the delete event. This event is fired when the user tries to 405 // close the window (e.g., clicking on the X in the window manager title bar). 406 gboolean NativeAppWindowGtk::OnMainWindowDeleteEvent(GtkWidget* widget, 407 GdkEvent* event) { 408 Close(); 409 410 // Return true to prevent the GTK window from being destroyed. Close will 411 // destroy it for us. 412 return TRUE; 413 } 414 415 gboolean NativeAppWindowGtk::OnConfigure(GtkWidget* widget, 416 GdkEventConfigure* event) { 417 // We update |bounds_| but not |restored_bounds_| here. The latter needs 418 // to be updated conditionally when the window is non-maximized and non- 419 // fullscreen, but whether those state updates have been processed yet is 420 // window-manager specific. We update |restored_bounds_| in the debounced 421 // handler below, after the window state has been updated. 422 bounds_.SetRect(event->x, event->y, event->width, event->height); 423 424 // The GdkEventConfigure* we get here doesn't have quite the right 425 // coordinates though (they're relative to the drawable window area, rather 426 // than any window manager decorations, if enabled), so we need to call 427 // gtk_window_get_position() to get the right values. (Otherwise session 428 // restore, if enabled, will restore windows to incorrect positions.) That's 429 // a round trip to the X server though, so we set a debounce timer and only 430 // call it (in OnConfigureDebounced() below) after we haven't seen a 431 // reconfigure event in a short while. 432 // We don't use Reset() because the timer may not yet be running. 433 // (In that case Stop() is a no-op.) 434 window_configure_debounce_timer_.Stop(); 435 window_configure_debounce_timer_.Start(FROM_HERE, 436 base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds), this, 437 &NativeAppWindowGtk::OnConfigureDebounced); 438 439 return FALSE; 440 } 441 442 void NativeAppWindowGtk::OnConfigureDebounced() { 443 gtk_window_util::UpdateWindowPosition(this, &bounds_, &restored_bounds_); 444 shell_window_->OnNativeWindowChanged(); 445 446 FOR_EACH_OBSERVER(web_modal::WebContentsModalDialogHostObserver, 447 observer_list_, 448 OnPositionRequiresUpdate()); 449 450 // Fullscreen of non-resizable windows requires them to be made resizable 451 // first. After that takes effect and OnConfigure is called we transition 452 // to fullscreen. 453 if (!IsFullscreen() && IsFullscreenOrPending()) { 454 gtk_window_fullscreen(window_); 455 } 456 } 457 458 gboolean NativeAppWindowGtk::OnWindowState(GtkWidget* sender, 459 GdkEventWindowState* event) { 460 state_ = event->new_window_state; 461 462 if (content_thinks_its_fullscreen_ && 463 !(state_ & GDK_WINDOW_STATE_FULLSCREEN)) { 464 content_thinks_its_fullscreen_ = false; 465 content::RenderViewHost* rvh = web_contents()->GetRenderViewHost(); 466 if (rvh) 467 rvh->ExitFullscreen(); 468 } 469 470 return FALSE; 471 } 472 473 bool NativeAppWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) { 474 if (!frameless_) 475 return false; 476 477 if (IsMaximized() || IsFullscreen()) 478 return false; 479 480 return gtk_window_util::GetWindowEdge(bounds_.size(), 0, x, y, edge); 481 } 482 483 gboolean NativeAppWindowGtk::OnMouseMoveEvent(GtkWidget* widget, 484 GdkEventMotion* event) { 485 if (!frameless_) { 486 // Reset the cursor. 487 if (frame_cursor_) { 488 frame_cursor_ = NULL; 489 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), NULL); 490 } 491 return FALSE; 492 } 493 494 if (!resizable_) 495 return FALSE; 496 497 // Update the cursor if we're on the custom frame border. 498 GdkWindowEdge edge; 499 bool has_hit_edge = GetWindowEdge(static_cast<int>(event->x), 500 static_cast<int>(event->y), &edge); 501 GdkCursorType new_cursor = GDK_LAST_CURSOR; 502 if (has_hit_edge) 503 new_cursor = gtk_window_util::GdkWindowEdgeToGdkCursorType(edge); 504 505 GdkCursorType last_cursor = GDK_LAST_CURSOR; 506 if (frame_cursor_) 507 last_cursor = frame_cursor_->type; 508 509 if (last_cursor != new_cursor) { 510 frame_cursor_ = has_hit_edge ? gfx::GetCursor(new_cursor) : NULL; 511 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), 512 frame_cursor_); 513 } 514 return FALSE; 515 } 516 517 gboolean NativeAppWindowGtk::OnButtonPress(GtkWidget* widget, 518 GdkEventButton* event) { 519 DCHECK(frameless_); 520 // Make the button press coordinate relative to the browser window. 521 int win_x, win_y; 522 GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); 523 gdk_window_get_origin(gdk_window, &win_x, &win_y); 524 525 GdkWindowEdge edge; 526 gfx::Point point(static_cast<int>(event->x_root - win_x), 527 static_cast<int>(event->y_root - win_y)); 528 bool has_hit_edge = resizable_ && GetWindowEdge(point.x(), point.y(), &edge); 529 bool has_hit_titlebar = 530 draggable_region_ && draggable_region_->contains(event->x, event->y); 531 532 if (event->button == 1) { 533 if (GDK_BUTTON_PRESS == event->type) { 534 // Raise the window after a click on either the titlebar or the border to 535 // match the behavior of most window managers, unless that behavior has 536 // been suppressed. 537 if ((has_hit_titlebar || has_hit_edge) && !suppress_window_raise_) 538 gdk_window_raise(GTK_WIDGET(widget)->window); 539 540 if (has_hit_edge) { 541 gtk_window_begin_resize_drag(window_, edge, event->button, 542 static_cast<gint>(event->x_root), 543 static_cast<gint>(event->y_root), 544 event->time); 545 return TRUE; 546 } else if (has_hit_titlebar) { 547 return gtk_window_util::HandleTitleBarLeftMousePress( 548 window_, bounds_, event); 549 } 550 } else if (GDK_2BUTTON_PRESS == event->type) { 551 if (has_hit_titlebar && resizable_) { 552 // Maximize/restore on double click. 553 if (IsMaximized()) { 554 gtk_window_util::UnMaximize(GTK_WINDOW(widget), 555 bounds_, restored_bounds_); 556 } else { 557 gtk_window_maximize(window_); 558 } 559 return TRUE; 560 } 561 } 562 } else if (event->button == 2) { 563 if (has_hit_titlebar || has_hit_edge) 564 gdk_window_lower(gdk_window); 565 return TRUE; 566 } 567 568 return FALSE; 569 } 570 571 void NativeAppWindowGtk::SetFullscreen(bool fullscreen) { 572 content_thinks_its_fullscreen_ = fullscreen; 573 if (fullscreen){ 574 if (resizable_) { 575 gtk_window_fullscreen(window_); 576 } else { 577 // We must first make the window resizable. That won't take effect 578 // immediately, so OnConfigureDebounced completes the fullscreen call. 579 gtk_window_set_resizable(window_, TRUE); 580 } 581 } else { 582 gtk_window_unfullscreen(window_); 583 if (!resizable_) 584 gtk_window_set_resizable(window_, FALSE); 585 } 586 } 587 588 bool NativeAppWindowGtk::IsFullscreenOrPending() const { 589 return content_thinks_its_fullscreen_; 590 } 591 592 bool NativeAppWindowGtk::IsDetached() const { 593 return false; 594 } 595 596 void NativeAppWindowGtk::UpdateWindowIcon() { 597 Profile* profile = shell_window_->profile(); 598 gfx::Image app_icon = shell_window_->app_icon(); 599 if (!app_icon.IsEmpty()) 600 gtk_util::SetWindowIcon(window_, profile, app_icon.ToGdkPixbuf()); 601 else 602 gtk_util::SetWindowIcon(window_, profile); 603 } 604 605 void NativeAppWindowGtk::UpdateWindowTitle() { 606 string16 title = shell_window_->GetTitle(); 607 gtk_window_set_title(window_, UTF16ToUTF8(title).c_str()); 608 } 609 610 void NativeAppWindowGtk::HandleKeyboardEvent( 611 const content::NativeWebKeyboardEvent& event) { 612 // No-op. 613 } 614 615 void NativeAppWindowGtk::UpdateDraggableRegions( 616 const std::vector<extensions::DraggableRegion>& regions) { 617 // Draggable region is not supported for non-frameless window. 618 if (!frameless_) 619 return; 620 621 draggable_region_.reset(ShellWindow::RawDraggableRegionsToSkRegion(regions)); 622 } 623