blob: c5a655b881009c605bcddc2199fec77579327f29 [file] [log] [blame]
// Copyright 2018 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/app_shim/app_shim_controller.h"
#import <Cocoa/Cocoa.h>
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app_shim/app_shim_delegate.h"
#include "chrome/browser/ui/cocoa/browser_window_command_handler.h"
#include "chrome/browser/ui/cocoa/chrome_command_dispatcher_delegate.h"
#include "chrome/browser/ui/cocoa/main_menu_builder.h"
#include "content/public/browser/ns_view_bridge_factory_impl.h"
#include "content/public/common/ns_view_bridge_factory.mojom.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views_bridge_mac/bridge_factory_impl.h"
#include "ui/views_bridge_mac/bridged_native_widget_impl.h"
#include "ui/views_bridge_mac/mojo/bridge_factory.mojom.h"
AppShimController::AppShimController(
const app_mode::ChromeAppModeInfo* app_mode_info)
: app_mode_info_(app_mode_info),
shim_binding_(this),
host_request_(mojo::MakeRequest(&host_)),
delegate_([[AppShimDelegate alloc] init]),
launch_app_done_(false),
ping_chrome_reply_received_(false),
attention_request_id_(0) {
// Since AppShimController is created before the main message loop starts,
// NSApp will not be set, so use sharedApplication.
NSApplication* sharedApplication = [NSApplication sharedApplication];
[sharedApplication setDelegate:delegate_];
}
AppShimController::~AppShimController() {
// Un-set the delegate since NSApplication does not retain it.
NSApplication* sharedApplication = [NSApplication sharedApplication];
[sharedApplication setDelegate:nil];
}
void AppShimController::OnPingChromeReply(bool success) {
ping_chrome_reply_received_ = true;
if (!success) {
[NSApp terminate:nil];
return;
}
InitBootstrapPipe();
}
void AppShimController::OnPingChromeTimeout() {
if (!ping_chrome_reply_received_)
[NSApp terminate:nil];
}
void AppShimController::InitBootstrapPipe() {
SetUpMenu();
// Chrome will relaunch shims when relaunching apps.
[NSApp disableRelaunchOnLogin];
// The user_data_dir for shims actually contains the app_data_path.
// I.e. <user_data_dir>/<profile_dir>/Web Applications/_crx_extensionid/
base::FilePath user_data_dir = base::FilePath(app_mode_info_->user_data_dir)
.DirName()
.DirName()
.DirName();
CHECK(!user_data_dir.empty());
base::FilePath symlink_path =
user_data_dir.Append(app_mode::kAppShimSocketSymlinkName);
base::FilePath socket_path;
if (base::ReadSymbolicLink(symlink_path, &socket_path)) {
app_mode::VerifySocketPermissions(socket_path);
CreateChannelAndSendLaunchApp(socket_path);
} else {
// The path in the user data dir is not a symlink, try connecting directly.
CreateChannelAndSendLaunchApp(symlink_path);
}
}
void AppShimController::CreateChannelAndSendLaunchApp(
const base::FilePath& socket_path) {
mojo::ScopedMessagePipeHandle message_pipe =
bootstrap_mojo_connection_.Connect(
mojo::NamedPlatformChannel::ConnectToServer(socket_path.value()));
CHECK(message_pipe.is_valid());
host_bootstrap_ = chrome::mojom::AppShimHostBootstrapPtr(
chrome::mojom::AppShimHostBootstrapPtrInfo(std::move(message_pipe), 0));
host_bootstrap_.set_connection_error_with_reason_handler(base::BindOnce(
&AppShimController::BootstrapChannelError, base::Unretained(this)));
bool launched_by_chrome = base::CommandLine::ForCurrentProcess()->HasSwitch(
app_mode::kLaunchedByChromeProcessId);
apps::AppShimLaunchType launch_type =
launched_by_chrome ? apps::APP_SHIM_LAUNCH_REGISTER_ONLY
: apps::APP_SHIM_LAUNCH_NORMAL;
[delegate_ setController:this];
std::vector<base::FilePath> files;
[delegate_ getFilesToOpenAtStartup:&files];
host_bootstrap_->LaunchApp(std::move(host_request_),
base::FilePath(app_mode_info_->profile_dir),
app_mode_info_->app_mode_id, launch_type, files,
base::BindOnce(&AppShimController::LaunchAppDone,
base::Unretained(this)));
}
void AppShimController::SetUpMenu() {
chrome::BuildMainMenu(NSApp, delegate_,
base::UTF8ToUTF16(app_mode_info_->app_mode_name), true);
}
void AppShimController::BootstrapChannelError(uint32_t custom_reason,
const std::string& description) {
// The bootstrap channel is expected to close after sending LaunchAppDone.
if (launch_app_done_)
return;
LOG(ERROR) << "Channel error custom_reason:" << custom_reason
<< " description: " << description;
Close();
}
void AppShimController::ChannelError(uint32_t custom_reason,
const std::string& description) {
LOG(ERROR) << "Channel error custom_reason:" << custom_reason
<< " description: " << description;
Close();
}
void AppShimController::LaunchAppDone(
apps::AppShimLaunchResult result,
chrome::mojom::AppShimRequest app_shim_request) {
if (result != apps::APP_SHIM_LAUNCH_SUCCESS) {
Close();
return;
}
shim_binding_.Bind(std::move(app_shim_request),
ui::WindowResizeHelperMac::Get()->task_runner());
shim_binding_.set_connection_error_with_reason_handler(
base::BindOnce(&AppShimController::ChannelError, base::Unretained(this)));
std::vector<base::FilePath> files;
if ([delegate_ getFilesToOpenAtStartup:&files])
SendFocusApp(apps::APP_SHIM_FOCUS_OPEN_FILES, files);
launch_app_done_ = true;
host_bootstrap_.reset();
}
void AppShimController::CreateViewsBridgeFactory(
views_bridge_mac::mojom::BridgeFactoryAssociatedRequest request) {
views_bridge_mac::BridgeFactoryImpl::Get()->BindRequest(std::move(request));
}
void AppShimController::CreateContentNSViewBridgeFactory(
content::mojom::NSViewBridgeFactoryAssociatedRequest request) {
content::NSViewBridgeFactoryImpl::Get()->BindRequest(std::move(request));
}
void AppShimController::CreateCommandDispatcherForWidget(uint64_t widget_id) {
if (auto* bridge = views::BridgedNativeWidgetImpl::GetFromId(widget_id)) {
bridge->SetCommandDispatcher(
[[[ChromeCommandDispatcherDelegate alloc] init] autorelease],
[[[BrowserWindowCommandHandler alloc] init] autorelease]);
} else {
LOG(ERROR) << "Failed to find host for command dispatcher.";
}
}
void AppShimController::Hide() {
[NSApp hide:nil];
}
void AppShimController::SetBadgeLabel(const std::string& badge_label) {
NSApp.dockTile.badgeLabel = base::SysUTF8ToNSString(badge_label);
}
void AppShimController::UnhideWithoutActivation() {
[NSApp unhideWithoutActivation];
}
void AppShimController::SetUserAttention(
apps::AppShimAttentionType attention_type) {
switch (attention_type) {
case apps::APP_SHIM_ATTENTION_CANCEL:
[NSApp cancelUserAttentionRequest:attention_request_id_];
attention_request_id_ = 0;
break;
case apps::APP_SHIM_ATTENTION_CRITICAL:
attention_request_id_ = [NSApp requestUserAttention:NSCriticalRequest];
break;
case apps::APP_SHIM_ATTENTION_INFORMATIONAL:
attention_request_id_ =
[NSApp requestUserAttention:NSInformationalRequest];
break;
case apps::APP_SHIM_ATTENTION_NUM_TYPES:
NOTREACHED();
}
}
void AppShimController::Close() {
[NSApp terminate:nil];
}
bool AppShimController::SendFocusApp(apps::AppShimFocusType focus_type,
const std::vector<base::FilePath>& files) {
if (launch_app_done_) {
host_->FocusApp(focus_type, files);
return true;
}
return false;
}