blob: c50e082afd37fcda8b1b3d68dc71d4da1dabbbe3 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fuchsia/element/cpp/fidl.h>
#include <fuchsia/io/cpp/fidl.h>
#include <fuchsia/web/cpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/cpp/interface_ptr.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/sys/cpp/service_directory.h>
#include <iostream>
#include <optional>
#include <utility>
#include "base/base_paths.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/fuchsia/file_utils.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/process_context.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/message_loop/message_pump_type.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_executor.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/fuchsia_component_support/annotations_manager.h"
#include "fuchsia_web/common/init_logging.h"
#include "fuchsia_web/shell/present_frame.h"
#include "fuchsia_web/shell/remote_debugging_port.h"
#include "fuchsia_web/shell/shell_relauncher.h"
#include "fuchsia_web/webinstance_host/web_instance_host.h"
#include "fuchsia_web/webinstance_host/web_instance_host_constants.h"
#include "third_party/widevine/cdm/buildflags.h"
#include "url/gurl.h"
namespace {
constexpr char kHeadlessSwitch[] = "headless";
constexpr char kEnableProtectedMediaIdentifier[] =
"enable-protected-media-identifier";
// TODO(crbug.com/1421342): This flag will be removed. Keep for now to prevent
// users from failing.
constexpr char kUseWebInstance[] = "use-web-instance";
constexpr char kUseContextProvider[] = "use-context-provider";
constexpr char kEnableWebInstanceTmp[] = "enable-web-instance-tmp";
void PrintUsage() {
std::cerr << "Usage: "
<< base::CommandLine::ForCurrentProcess()->GetProgram().BaseName()
<< " [--" << kRemoteDebuggingPortSwitch << "] [--"
<< kHeadlessSwitch << "] [--" << switches::kWithWebui
<< "] URL [--] [--{extra_flag1}] "
<< "[--{extra_flag2}]" << std::endl
<< "Setting " << kRemoteDebuggingPortSwitch << " to 0 will "
<< "automatically choose an available port." << std::endl
<< "Setting " << kHeadlessSwitch << " will prevent creation of "
<< "a view." << std::endl
<< "Extra flags will be passed to "
<< "WebEngine to be processed." << std::endl;
}
GURL GetUrlFromArgs(const base::CommandLine::StringVector& args) {
if (args.empty()) {
LOG(ERROR) << "No URL provided.";
return GURL();
}
GURL url = GURL(args.front());
if (!url.is_valid()) {
LOG(ERROR) << "URL is not valid: " << url.spec();
return GURL();
}
return url;
}
} // namespace
int main(int argc, char** argv) {
base::CommandLine::Init(argc, argv);
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
CHECK(InitLoggingFromCommandLine(*command_line));
base::SingleThreadTaskExecutor executor(base::MessagePumpType::IO);
const bool use_web_instance = command_line->HasSwitch(kUseWebInstance);
const bool use_context_provider =
command_line->HasSwitch(kUseContextProvider);
if (use_web_instance && use_context_provider) {
LOG(ERROR) << "Cannot use " << kUseWebInstance << " and "
<< kUseContextProvider << " simultaneously.";
return 1;
}
if (use_web_instance) {
LOG(WARNING) << "Flag " << kUseWebInstance << " is deprecated and has no "
<< "effect as WebInstance is used by default.";
}
if (!use_context_provider) {
if (auto optional_exit_code = RelaunchForWebInstanceHostIfParent(
"#meta/web_engine_shell_for_web_instance_host.cm", *command_line);
optional_exit_code.has_value()) {
return optional_exit_code.value();
}
}
std::optional<uint16_t> remote_debugging_port =
GetRemoteDebuggingPort(*command_line);
if (!remote_debugging_port) {
PrintUsage();
return 1;
}
const bool is_headless = command_line->HasSwitch(kHeadlessSwitch);
const bool enable_protected_media_identifier_access =
command_line->HasSwitch(kEnableProtectedMediaIdentifier);
const bool enable_web_instance_tmp =
command_line->HasSwitch(kEnableWebInstanceTmp);
const bool with_webui = command_line->HasSwitch(switches::kWithWebui);
base::CommandLine::StringVector additional_args = command_line->GetArgs();
GURL url(GetUrlFromArgs(additional_args));
if (!url.is_valid()) {
PrintUsage();
return 1;
}
// Remove the url since we don't pass it into WebEngine
additional_args.erase(additional_args.begin());
if (enable_web_instance_tmp && use_context_provider) {
LOG(ERROR) << "Cannot use --enable-web-instance-tmp without "
<< "--use-web-instance";
return 1;
}
if (with_webui && use_context_provider) {
LOG(ERROR) << "Cannot use --with-webui without --use-web-instance";
return 1;
}
if (!additional_args.empty() && use_context_provider) {
LOG(ERROR) << "Cannot use extra args without --use-web-instance";
return 1;
}
// Set up the content directory fuchsia-pkg://shell-data/, which will host
// the files stored under //fuchsia_web/shell/data.
fuchsia::web::CreateContextParams create_context_params;
fuchsia::web::ContentDirectoryProvider content_directory;
base::FilePath pkg_path;
base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &pkg_path);
content_directory.set_directory(base::OpenDirectoryHandle(
pkg_path.AppendASCII("fuchsia_web/shell/data")));
content_directory.set_name("shell-data");
std::vector<fuchsia::web::ContentDirectoryProvider> content_directories;
content_directories.emplace_back(std::move(content_directory));
create_context_params.set_content_directories(
{std::move(content_directories)});
// Enable other WebEngine features.
fuchsia::web::ContextFeatureFlags features =
fuchsia::web::ContextFeatureFlags::AUDIO |
fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER |
fuchsia::web::ContextFeatureFlags::KEYBOARD |
fuchsia::web::ContextFeatureFlags::NETWORK |
fuchsia::web::ContextFeatureFlags::VIRTUAL_KEYBOARD;
#if BUILDFLAG(ENABLE_WIDEVINE)
features |= fuchsia::web::ContextFeatureFlags::WIDEVINE_CDM;
#endif
if (is_headless) {
features |= fuchsia::web::ContextFeatureFlags::HEADLESS;
} else {
features |= fuchsia::web::ContextFeatureFlags::VULKAN;
}
create_context_params.set_features(features);
create_context_params.set_remote_debugging_port(*remote_debugging_port);
// DRM services require cdm_data_directory to be populated, so create a
// directory under /data and use that as the cdm_data_directory.
base::FilePath cdm_data_path =
base::FilePath(base::kPersistedDataDirectoryPath).Append("cdm_data");
base::File::Error error;
CHECK(base::CreateDirectoryAndGetError(cdm_data_path, &error)) << error;
create_context_params.set_cdm_data_directory(
base::OpenDirectoryHandle(cdm_data_path));
CHECK(create_context_params.cdm_data_directory());
base::RunLoop run_loop;
fuchsia::web::ContextProviderPtr web_context_provider;
std::unique_ptr<WebInstanceHost> web_instance_host;
fuchsia::web::ContextPtr context;
fuchsia::io::DirectoryHandle tmp_directory;
if (use_context_provider) {
// Connect to the system instance of the ContextProvider.
// WebEngine Contexts can only make use of the services provided by the
// embedder application. By passing a handle to this process' service
// directory to the ContextProvider, we are allowing the Context access to
// the same set of services available to this application.
create_context_params.set_service_directory(
base::OpenDirectoryHandle(base::FilePath(base::kServiceDirectoryPath)));
web_context_provider = base::ComponentContextForProcess()
->svc()
->Connect<fuchsia::web::ContextProvider>();
web_context_provider->Create(std::move(create_context_params),
context.NewRequest());
} else {
// Route services dynamically from web_engine_shell's parent down into
// created web_instances.
web_instance_host =
std::make_unique<WebInstanceHostWithServicesFromThisComponent>(
*base::ComponentContextForProcess()->outgoing(),
/*is_web_instance_component_in_same_package=*/false);
if (enable_web_instance_tmp) {
const zx_status_t status = fdio_open(
"/tmp",
static_cast<uint32_t>(fuchsia::io::OpenFlags::RIGHT_READABLE |
fuchsia::io::OpenFlags::RIGHT_WRITABLE |
fuchsia::io::OpenFlags::DIRECTORY),
tmp_directory.NewRequest().TakeChannel().release());
ZX_CHECK(status == ZX_OK, status) << "fdio_open(/tmp)";
web_instance_host->set_tmp_dir(std::move(tmp_directory));
}
fidl::InterfaceRequest<fuchsia::io::Directory> services_request;
auto services = sys::ServiceDirectory::CreateWithRequest(&services_request);
zx_status_t result =
web_instance_host->CreateInstanceForContextWithCopiedArgs(
std::move(create_context_params), std::move(services_request),
base::CommandLine(additional_args));
if (result == ZX_OK) {
services->Connect(context.NewRequest());
} else {
ZX_LOG(ERROR, result) << "CreateInstanceForContextWithCopiedArgs failed";
return 2;
}
}
context.set_error_handler(
[quit_run_loop = run_loop.QuitClosure()](zx_status_t status) {
ZX_LOG(ERROR, status) << "Context connection lost:";
quit_run_loop.Run();
});
// Create the browser |frame| which will contain the webpage.
fuchsia::web::CreateFrameParams frame_params;
frame_params.set_enable_remote_debugging(true);
fuchsia::web::FramePtr frame;
context->CreateFrameWithParams(std::move(frame_params), frame.NewRequest());
frame.set_error_handler(
[quit_run_loop = run_loop.QuitClosure()](zx_status_t status) {
ZX_LOG(ERROR, status) << "Frame connection lost:";
quit_run_loop.Run();
});
fuchsia::web::ContentAreaSettings settings;
settings.set_autoplay_policy(fuchsia::web::AutoplayPolicy::ALLOW);
frame->SetContentAreaSettings(std::move(settings));
// Log the debugging port.
context->GetRemoteDebuggingPort(
[](fuchsia::web::Context_GetRemoteDebuggingPort_Result result) {
if (result.is_err()) {
LOG(ERROR) << "Remote debugging service was not opened.";
return;
}
// Telemetry expects this exact format of log line output to retrieve
// the remote debugging port.
LOG(INFO) << "Remote debugging port: " << result.response().port;
});
// Navigate |frame| to |url|.
fuchsia::web::LoadUrlParams load_params;
load_params.set_type(fuchsia::web::LoadUrlReason::TYPED);
load_params.set_was_user_activated(true);
fuchsia::web::NavigationControllerPtr nav_controller;
frame->GetNavigationController(nav_controller.NewRequest());
nav_controller->LoadUrl(
url.spec(), std::move(load_params),
[quit_run_loop = run_loop.QuitClosure()](
fuchsia::web::NavigationController_LoadUrl_Result result) {
if (result.is_err()) {
LOG(ERROR) << "LoadUrl failed.";
quit_run_loop.Run();
}
});
// Since this is for development, enable all logging.
frame->SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel::DEBUG);
if (enable_protected_media_identifier_access) {
fuchsia::web::PermissionDescriptor protected_media_permission;
protected_media_permission.set_type(
fuchsia::web::PermissionType::PROTECTED_MEDIA_IDENTIFIER);
frame->SetPermissionState(std::move(protected_media_permission),
url.DeprecatedGetOriginAsURL().spec(),
fuchsia::web::PermissionState::GRANTED);
}
// The underlying PresentView call expects an AnnotationController and will
// return PresentViewError.INVALID_ARGS without one. The AnnotationController
// should serve WatchAnnotations, but it doesn't need to do anything.
// TODO(b/264899156): Remove this when AnnotationController becomes
// optional.
auto annotations_manager =
std::make_unique<fuchsia_component_support::AnnotationsManager>();
fuchsia::element::AnnotationControllerPtr annotation_controller;
annotations_manager->Connect(annotation_controller.NewRequest());
fuchsia::element::GraphicalPresenterPtr presenter;
if (is_headless) {
frame->EnableHeadlessRendering();
} else {
presenter = PresentFrame(frame.get(), std::move(annotation_controller));
}
LOG(INFO) << "Launched browser at URL " << url.spec();
base::ComponentContextForProcess()->outgoing()->ServeFromStartupInfo();
// Run until the process is killed with CTRL-C or the connections to Web
// Engine interfaces are dropped.
run_loop.Run();
return 0;
}