910e62b5创建于 1月15日历史提交
// Copyright 2017 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/devtools/protocol/browser_handler.h"

#include <set>
#include <vector>

#include "base/functional/bind.h"
#include "base/memory/ref_counted_memory.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/devtools/chrome_devtools_manager_delegate.h"
#include "chrome/browser/devtools/devtools_dock_tile.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "ui/display/types/display_constants.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_png_rep.h"

using protocol::Response;

namespace {

BrowserWindow* GetBrowserWindow(int window_id) {
  BrowserWindow* result = nullptr;
  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
      [window_id, &result](BrowserWindowInterface* browser_window_interface) {
        if (browser_window_interface->GetSessionID().id() == window_id) {
          result =
              browser_window_interface->GetBrowserForMigrationOnly()->window();
          return false;
        }
        return true;
      });
  return result;
}

std::unique_ptr<protocol::Browser::Bounds> GetBrowserWindowBounds(
    ui::BaseWindow* window) {
  std::string window_state = "normal";
  if (window->IsMinimized())
    window_state = "minimized";
  if (window->IsMaximized())
    window_state = "maximized";
  if (window->IsFullscreen())
    window_state = "fullscreen";

  gfx::Rect bounds;
  if (window->IsMinimized())
    bounds = window->GetRestoredBounds();
  else
    bounds = window->GetBounds();
  return protocol::Browser::Bounds::Create()
      .SetLeft(bounds.x())
      .SetTop(bounds.y())
      .SetWidth(bounds.width())
      .SetHeight(bounds.height())
      .SetWindowState(window_state)
      .Build();
}

}  // namespace

BrowserHandler::BrowserHandler(protocol::UberDispatcher* dispatcher,
                               const std::string& target_id)
    : target_id_(target_id) {
  // Dispatcher can be null in tests.
  if (dispatcher)
    protocol::Browser::Dispatcher::wire(dispatcher, this);
}

BrowserHandler::~BrowserHandler() = default;

Response BrowserHandler::GetWindowForTarget(
    std::optional<std::string> target_id,
    int* out_window_id,
    std::unique_ptr<protocol::Browser::Bounds>* out_bounds) {
  auto host =
      content::DevToolsAgentHost::GetForId(target_id.value_or(target_id_));
  if (!host)
    return Response::ServerError("No target with given id");
  content::WebContents* web_contents = host->GetWebContents();
  if (!web_contents) {
    return Response::ServerError("No web contents in the target");
  }

  BrowserWindowInterface* found_browser = nullptr;
  ForEachCurrentBrowserWindowInterfaceOrderedByActivation(
      [web_contents,
       &found_browser](BrowserWindowInterface* browser_window_interface) {
        int tab_index =
            browser_window_interface->GetTabStripModel()->GetIndexOfWebContents(
                web_contents);
        if (tab_index != TabStripModel::kNoTab) {
          found_browser = browser_window_interface;
          return false;
        }
        return true;
      });
  if (!found_browser) {
    return Response::ServerError("Browser window not found");
  }

  ui::BaseWindow* window = found_browser->GetWindow();
  *out_window_id = found_browser->GetSessionID().id();
  *out_bounds = GetBrowserWindowBounds(window);
  return Response::Success();
}

Response BrowserHandler::GetWindowBounds(
    int window_id,
    std::unique_ptr<protocol::Browser::Bounds>* out_bounds) {
  BrowserWindow* window = GetBrowserWindow(window_id);
  if (!window)
    return Response::ServerError("Browser window not found");

  *out_bounds = GetBrowserWindowBounds(window);
  return Response::Success();
}

Response BrowserHandler::Close() {
  ChromeDevToolsManagerDelegate::CloseBrowserSoon();
  return Response::Success();
}

Response BrowserHandler::SetWindowBounds(
    int window_id,
    std::unique_ptr<protocol::Browser::Bounds> window_bounds) {
  BrowserWindow* window = GetBrowserWindow(window_id);
  if (!window)
    return Response::ServerError("Browser window not found");
  gfx::Rect bounds = window->GetBounds();
  const bool set_bounds = window_bounds->HasLeft() || window_bounds->HasTop() ||
                          window_bounds->HasWidth() ||
                          window_bounds->HasHeight();
  if (set_bounds) {
    bounds.set_x(window_bounds->GetLeft(bounds.x()));
    bounds.set_y(window_bounds->GetTop(bounds.y()));
    bounds.set_width(window_bounds->GetWidth(bounds.width()));
    bounds.set_height(window_bounds->GetHeight(bounds.height()));
  }

  const std::string window_state = window_bounds->GetWindowState("normal");
  if (set_bounds && window_state != "normal") {
    return Response::InvalidParams(
        "The 'minimized', 'maximized' and 'fullscreen' states cannot be "
        "combined with 'left', 'top', 'width' or 'height'");
  }

  if (window_state == "fullscreen") {
    if (window->IsMinimized()) {
      return Response::ServerError(
          "To make minimized window fullscreen, "
          "restore it to normal state first.");
    }
    window->GetExclusiveAccessContext()->EnterFullscreen(
        url::Origin(), EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE,
        FullscreenTabParams());
  } else if (window_state == "maximized") {
    if (window->IsMinimized() || window->IsFullscreen()) {
      return Response::ServerError(
          "To maximize a minimized or fullscreen "
          "window, restore it to normal state first.");
    }
    window->Maximize();
  } else if (window_state == "minimized") {
    if (window->IsFullscreen()) {
      return Response::ServerError(
          "To minimize a fullscreen window, restore it to normal "
          "state first.");
    }
    window->Minimize();
  } else if (window_state == "normal") {
    if (window->IsFullscreen()) {
      window->GetExclusiveAccessContext()->ExitFullscreen();
    } else if (window->IsMinimized() || window->IsMaximized()) {
      window->Restore();
    } else if (set_bounds) {
      window->SetBounds(bounds);
    }
  } else {
    NOTREACHED();
  }

  return Response::Success();
}

protocol::Response BrowserHandler::SetContentsSize(int window_id,
                                                   std::optional<int> width,
                                                   std::optional<int> height) {
  BrowserWindow* window = GetBrowserWindow(window_id);
  if (!window) {
    return Response::ServerError("Browser window not found");
  }

  gfx::Size contents_size = window->GetContentsSize();
  if (contents_size.IsEmpty()) {
    return Response::ServerError("Active contents not found");
  }

  if (window->IsMinimized() || window->IsMaximized() ||
      window->IsFullscreen()) {
    return Response::ServerError(
        "Restore window to normal state before setting content size");
  }

  if (!width && !height) {
    return Response::InvalidParams(
        "At least one of 'width' or 'height' must be specified");
  }

  if (width && width.value() <= 0) {
    return Response::InvalidParams("Contents 'width' must be a positive value");
  }

  if (height && height.value() <= 0) {
    return Response::InvalidParams(
        "Contents 'height' must be a positive value");
  }

  // We cannot just call BrowserView::SetContentsSize() here because it will
  // constrain the browser window to the current screen work area which is not
  // desirable.
  const int width_diff = width ? width.value() - contents_size.width() : 0;
  const int height_diff = height ? height.value() - contents_size.height() : 0;
  if (width_diff || height_diff) {
    gfx::Rect bounds = window->GetBounds();
    bounds.set_width(bounds.width() + width_diff);
    bounds.set_height(bounds.height() + height_diff);
    window->SetBounds(bounds);
  }

  return Response::Success();
}

protocol::Response BrowserHandler::SetDockTile(
    std::optional<std::string> label,
    std::optional<protocol::Binary> image) {
  std::vector<gfx::ImagePNGRep> reps;
  if (image.has_value()) {
    reps.emplace_back(image.value().bytes(), 1);
  }
  DevToolsDockTile::Update(label.value_or(std::string()),
                           !reps.empty() ? gfx::Image(reps) : gfx::Image());
  return Response::Success();
}

protocol::Response BrowserHandler::ExecuteBrowserCommand(
    const protocol::Browser::BrowserCommandId& command_id) {
  static auto& command_id_map =
      *new std::map<protocol::Browser::BrowserCommandId, int>{
          {protocol::Browser::BrowserCommandIdEnum::OpenTabSearch,
           IDC_TAB_SEARCH},
          {protocol::Browser::BrowserCommandIdEnum::CloseTabSearch,
           IDC_TAB_SEARCH_CLOSE},
          {protocol::Browser::BrowserCommandIdEnum::OpenGlic, IDC_OPEN_GLIC},
      };
  if (command_id_map.count(command_id) == 0) {
    return Response::InvalidParams("Invalid BrowserCommandId: " + command_id);
  }
  if (!chrome::ExecuteCommand(
          GetLastActiveBrowserWindowInterfaceWithAnyProfile(),
          command_id_map[command_id])) {
    return Response::InvalidRequest(
        "Browser command not supported. BrowserCommandId: " + command_id);
  }
  return Response::Success();
}

protocol::Response BrowserHandler::AddPrivacySandboxEnrollmentOverride(
    const std::string& in_url) {
  auto host = content::DevToolsAgentHost::GetForId(target_id_);
  if (!host) {
    return Response::ServerError("No host found");
  }

  GURL url_to_add = GURL(in_url);

  if (!url_to_add.is_valid()) {
    return Response::InvalidParams("Invalid URL");
  }

  privacy_sandbox::PrivacySandboxAttestations::GetInstance()->AddOverride(
      net::SchemefulSite(url_to_add));
  return Response::Success();
}