chromium/chrome/browser/ui/window_sizer/window_sizer_chromeos.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/window_sizer/window_sizer_chromeos.h"

#include <utility>

#include "base/command_line.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "ui/aura/window.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"

namespace {

// When the screen is this width or narrower, the initial browser launched on
// first run will be maximized.
constexpr int kForceMaximizeWidthLimit = 1366;

bool ShouldForceMaximizeOnFirstRun(Profile* profile) {
  return profile->GetPrefs()->GetBoolean(prefs::kForceMaximizeOnFirstRun);
}

}  // namespace

WindowSizerChromeOS::WindowSizerChromeOS(
    std::unique_ptr<StateProvider> state_provider,
    const Browser* browser)
    : WindowSizer(std::move(state_provider), browser) {}

WindowSizerChromeOS::~WindowSizerChromeOS() = default;

void WindowSizerChromeOS::DetermineWindowBoundsAndShowState(
    const gfx::Rect& specified_bounds,
    gfx::Rect* bounds,
    ui::WindowShowState* show_state) {
  // If we got *both* the bounds and show state, we're done.
  if (GetBrowserBounds(bounds, show_state))
    return;

  // Fall back to cross-platform behavior. Note that |show_state| may have been
  // changed by the function above.
  WindowSizer::DetermineWindowBoundsAndShowState(specified_bounds, bounds,
                                                 show_state);
}

gfx::Rect WindowSizerChromeOS::GetDefaultWindowBounds(
    const display::Display& display) const {
  // Let apps set their own default.
  if (browser() && browser()->app_controller()) {
    gfx::Rect bounds = browser()->app_controller()->GetDefaultBounds();
    if (!bounds.IsEmpty())
      return bounds;
  }

  const gfx::Rect work_area = display.work_area();
  // There should be a 'desktop' border around the window at the left and right
  // side.
  int default_width = work_area.width() - 2 * kDesktopBorderSize;
  // There should also be a 'desktop' border around the window at the top.
  // Since the workspace excludes the tray area we only need one border size.
  int default_height = work_area.height() - kDesktopBorderSize;
  int offset_x = kDesktopBorderSize;
  if (default_width > kMaximumWindowWidth) {
    // The window should get centered on the screen and not follow the grid.
    offset_x = (work_area.width() - kMaximumWindowWidth) / 2;
    default_width = kMaximumWindowWidth;
  }
  return gfx::Rect(work_area.x() + offset_x, work_area.y() + kDesktopBorderSize,
                   default_width, default_height);
}

bool WindowSizerChromeOS::GetBrowserBounds(
    gfx::Rect* bounds,
    ui::WindowShowState* show_state) const {
  if (!browser())
    return false;

  // This should not be called on a Browser that already has a window.
  DCHECK(!browser()->window());

  bool determined = false;
  if (bounds->IsEmpty()) {
    if (browser()->is_type_normal()) {
      GetTabbedBrowserBounds(bounds, show_state);
      determined = true;
    } else if (browser()->is_trusted_source()) {
      // For trusted popups (v1 apps and system windows), do not use the last
      // active window bounds, only use saved or default bounds.
      // For PWA app windows (which are also a trusted source) we do want to use
      // the last active window bounds.
      if (!browser()->is_type_app() || !browser()->app_controller() ||
          !GetAppBrowserBoundsFromLastActive(bounds, show_state)) {
        if (!browser()->create_params().can_resize ||
            !GetSavedWindowBounds(bounds, show_state)) {
          *bounds = GetDefaultWindowBounds(GetDisplayForNewWindow());
        }
      }
      determined = true;
    } else if (state_provider()) {
      // Finally, prioritize the last saved |show_state|. If you have questions
      // or comments about this behavior please contact [email protected].
      gfx::Rect ignored_bounds, ignored_work_area;
      // TODO(ellyjones): This code shouldn't ignore the return value of
      // GetPersistentState()... we could end up using an undefined
      // |show_state|?
      state_provider()->GetPersistentState(&ignored_bounds, &ignored_work_area,
                                           show_state);
      // |determined| is not set here, so we fall back to cross-platform window
      // bounds computation.
    }
  }

  if (browser()->is_type_normal() && *show_state == ui::SHOW_STATE_DEFAULT) {
    display::Display display =
        display::Screen::GetScreen()->GetDisplayMatching(*bounds);
    gfx::Rect work_area = display.work_area();
    bounds->AdjustToFit(work_area);
    if (*bounds == work_area) {
      // A browser that occupies the whole work area gets maximized. The
      // |bounds| returned here become the restore bounds once the window
      // gets maximized after this method returns. Return a sensible default
      // in order to make restored state visibly different from maximized.
      *show_state = ui::SHOW_STATE_MAXIMIZED;
      *bounds = GetDefaultWindowBounds(display);
      determined = true;
    }
  }
  return determined;
}

void WindowSizerChromeOS::GetTabbedBrowserBounds(
    gfx::Rect* bounds_in_screen,
    ui::WindowShowState* show_state) const {
  DCHECK(show_state);
  DCHECK(bounds_in_screen);
  DCHECK(browser()->is_type_normal());
  DCHECK(bounds_in_screen->IsEmpty());

  const ui::WindowShowState passed_show_state = *show_state;

  bool is_saved_bounds = GetSavedWindowBounds(bounds_in_screen, show_state);
  display::Display display = GetDisplayForNewWindow(*bounds_in_screen);
  if (!is_saved_bounds)
    *bounds_in_screen = GetDefaultWindowBounds(display);
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();

  if (browser()->is_session_restore()) {
    // Respect display for saved bounds during session restore.
    display =
        display::Screen::GetScreen()->GetDisplayMatching(*bounds_in_screen);
  } else if (BrowserList::GetInstance()->empty() && !is_saved_bounds &&
             (ShouldForceMaximizeOnFirstRun(browser()->profile()) ||
              (display.work_area().width() <= kForceMaximizeWidthLimit &&
               !command_line->HasSwitch(
                   switches::kDisableAutoMaximizeForTests)))) {
    // No browsers, no saved bounds: assume first run. Maximize if set by policy
    // or if the screen is narrower than a predetermined size.
    *show_state = ui::SHOW_STATE_MAXIMIZED;
  } else {
    // Take the show state from the last active window and copy its restored
    // bounds only if we don't have saved bounds.
    gfx::Rect bounds_copy = *bounds_in_screen;
    ui::WindowShowState show_state_copy = passed_show_state;
    if (state_provider() && state_provider()->GetLastActiveWindowState(
                                &bounds_copy, &show_state_copy)) {
      *show_state = show_state_copy;
      if (!is_saved_bounds) {
        *bounds_in_screen = bounds_copy;
        bounds_in_screen->Offset(kWindowTilePixels, kWindowTilePixels);
      }
    }
  }

  bounds_in_screen->AdjustToFit(display.work_area());
}

bool WindowSizerChromeOS::GetAppBrowserBoundsFromLastActive(
    gfx::Rect* bounds_in_screen,
    ui::WindowShowState* show_state) const {
  DCHECK(show_state);
  DCHECK(bounds_in_screen);
  DCHECK(browser()->app_controller());

  if (state_provider() && state_provider()->GetLastActiveWindowState(
                              bounds_in_screen, show_state)) {
    bounds_in_screen->Offset(kWindowTilePixels, kWindowTilePixels);
    // Adjusting bounds_in_screen to fit on the display as returned by
    // GetDisplayForNewWindow here matches behavior for tabbed browsers above.
    // This would mean that we might take into account the size of the last
    // active matching window but ignore the position, if it is on a different
    // display. However the current implementation for GetLastActiveWindowState
    // only looks for windows on the same display, so in practice there should
    // never be a mismatch.
    bounds_in_screen->AdjustToFit(GetDisplayForNewWindow().work_area());
    return true;
  }
  return false;
}