// Copyright 2020 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 "base/base_switches.h" #include "base/cfi_buildflags.h" #include "base/command_line.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/process/launch.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/test/bind_test_util.h" #include "build/build_config.h" #include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" #include "content/public/test/content_browser_test.h" #include "content/shell/common/shell_controller.test-mojom.h" #include "content/shell/common/shell_switches.h" #include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/platform/platform_channel.h" #include "mojo/public/cpp/system/invitation.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(USE_OZONE) #include "ui/ozone/public/ozone_switches.h" #endif #if defined(OS_CHROMEOS) #include "ui/gl/gl_switches.h" #endif namespace content { namespace { #if defined(OS_WIN) const char kShellExecutableName[] = "content_shell.exe"; #else const char kShellExecutableName[] = "content_shell"; const char kMojoCoreLibraryName[] = "libmojo_core.so"; #endif const char* kSwitchesToCopy[] = { #if defined(USE_OZONE) // Keep the kOzonePlatform switch that the Ozone must use. switches::kOzonePlatform, #endif // Some tests use custom cmdline that doesn't hold switches from previous // cmdline. Only a couple of switches are copied. That can result in // incorrect initialization of a process. For example, the work that we do // to have use_x11 && use_ozone, requires UseOzonePlatform feature flag to // be passed to all the process to ensure correct path is chosen. // TODO(https://crbug.com/1096425): update this comment once USE_X11 goes // away. switches::kEnableFeatures, switches::kDisableFeatures, }; base::FilePath GetCurrentDirectory() { base::FilePath current_directory; CHECK(base::GetCurrentDirectory(¤t_directory)); return current_directory; } class LaunchAsMojoClientBrowserTest : public ContentBrowserTest { public: LaunchAsMojoClientBrowserTest() { CHECK(temp_dir_.CreateUniqueTempDir()); } ~LaunchAsMojoClientBrowserTest() override { // Ensure that the launched Content Shell process is dead before the test // tears down, otherwise the temp profile dir may fail to delete. Note that // tests must explicitly request shutdown through ShellController before // finishing, otherwise this will time out. CHECK(content_shell_process_.WaitForExit(nullptr)); CHECK(temp_dir_.Delete()); } base::CommandLine MakeShellCommandLine() { base::CommandLine command_line( GetFilePathNextToCurrentExecutable(kShellExecutableName)); command_line.AppendSwitchPath(switches::kContentShellDataPath, temp_dir_.GetPath()); const base::CommandLine& cmdline = *base::CommandLine::ForCurrentProcess(); command_line.CopySwitchesFrom(cmdline, kSwitchesToCopy, base::size(kSwitchesToCopy)); #if defined(OS_CHROMEOS) command_line.AppendSwitchASCII(switches::kUseGL, "swiftshader"); #endif return command_line; } mojo::Remote LaunchContentShell( const base::CommandLine& command_line) { mojo::PlatformChannel channel; base::LaunchOptions options; base::CommandLine shell_command_line(command_line); channel.PrepareToPassRemoteEndpoint(&options, &shell_command_line); content_shell_process_ = base::LaunchProcess(shell_command_line, options); channel.RemoteProcessLaunchAttempted(); mojo::OutgoingInvitation invitation; mojo::Remote controller( mojo::PendingRemote( invitation.AttachMessagePipe(0), /*version=*/0)); mojo::OutgoingInvitation::Send(std::move(invitation), content_shell_process_.Handle(), channel.TakeLocalEndpoint()); return controller; } #if defined(OS_LINUX) || defined(OS_CHROMEOS) base::FilePath GetMojoCoreLibraryPath() { return GetFilePathNextToCurrentExecutable(kMojoCoreLibraryName); } #endif private: base::FilePath GetFilePathNextToCurrentExecutable( const std::string& filename) { base::FilePath executable_dir = base::CommandLine::ForCurrentProcess()->GetProgram().DirName(); if (executable_dir.IsAbsolute()) return executable_dir.AppendASCII(filename); // If the current executable path is relative, resolve it to an absolute // path before swapping in |filename|. This ensures that the path is OK to // use with base::LaunchProcess. Otherwise we could end up with a path // containing only |filename|, and this can fail to execute in environments // where "." is not in the PATH (common on e.g. Linux). return current_directory_.Append(executable_dir).AppendASCII(filename); } base::ScopedTempDir temp_dir_; const base::FilePath current_directory_ = GetCurrentDirectory(); base::Process content_shell_process_; mojo::Remote shell_controller_; }; IN_PROC_BROWSER_TEST_F(LaunchAsMojoClientBrowserTest, LaunchAndBindInterface) { // Verifies that we can launch an instance of Content Shell with a Mojo // invitation on the command line and reach the new browser process's exposed // ShellController interface. const char kExtraSwitchName[] = "extra-switch-for-testing"; const char kExtraSwitchValue[] = "42"; base::CommandLine command_line = MakeShellCommandLine(); command_line.AppendSwitchASCII(kExtraSwitchName, kExtraSwitchValue); mojo::Remote shell_controller = LaunchContentShell(command_line); base::RunLoop loop; shell_controller->GetSwitchValue( kExtraSwitchName, base::BindLambdaForTesting([&](const base::Optional& value) { ASSERT_TRUE(value); EXPECT_EQ(kExtraSwitchValue, *value); loop.Quit(); })); loop.Run(); shell_controller->ShutDown(); } // Running a Content embedder with a dynamically loaded Mojo Core library is // currently only supported on Linux and Chrome OS. // // TODO(crbug.com/1096899): Re-enable on MSan if possible. MSan complains about // spurious uninitialized memory reads inside base::PlatformThread due to what // appears to be poor interaction among MSan, PlatformThread's thread_local // storage, and Mojo's use of dlopen(). #if defined(OS_LINUX) || defined(OS_CHROMEOS) #if defined(MEMORY_SANITIZER) || BUILDFLAG(CFI_ICALL_CHECK) #define MAYBE_WithMojoCoreLibrary DISABLED_WithMojoCoreLibrary #else #define MAYBE_WithMojoCoreLibrary WithMojoCoreLibrary #endif IN_PROC_BROWSER_TEST_F(LaunchAsMojoClientBrowserTest, MAYBE_WithMojoCoreLibrary) { // Instructs a newly launched Content Shell browser to initialize Mojo Core // dynamically from a shared library, rather than using the version linked // into the Content Shell binary. // // This exercises end-to-end JS in order to cover real IPC behavior between // the browser and a renderer. base::CommandLine command_line = MakeShellCommandLine(); command_line.AppendSwitchPath(switches::kMojoCoreLibraryPath, GetMojoCoreLibraryPath()); mojo::Remote shell_controller = LaunchContentShell(command_line); // Indisputable proof that we're evaluating JavaScript. const std::string kExpressionToEvaluate = "'ba'+ +'a'+'as'"; const base::Value kExpectedValue("baNaNas"); base::RunLoop loop; shell_controller->ExecuteJavaScript( base::ASCIIToUTF16(kExpressionToEvaluate), base::BindLambdaForTesting([&](base::Value value) { EXPECT_EQ(kExpectedValue, value); loop.Quit(); })); loop.Run(); shell_controller->ShutDown(); } #endif // defined(OS_LINUX) || defined(OS_CHROMEOS) } // namespace } // namespace content