0

Reland "Support dynamic Mojo Core on Linux"

This is a reland of 002e044483

No changes other than disabling the test under MSan where
it is broken and probably not worth fixing.

Original change's description:
> Support dynamic Mojo Core on Linux
>
> This introduces a new --mojo-core-library-path Content switch which
> instructs Content to initialize each process with an implementation
> of Mojo Core found in the referenced shared library rather than using
> the version linked into the main binary.
>
> This allows for IPC interoperability between a Content embedder and
> another application which provides its own copy of Mojo Core.
>
> Fixed: 1082473
> Change-Id: I1e50c505e91a53e60056a4b8c691d91728f7a5ea
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2229664
> Reviewed-by: Avi Drissman <avi@chromium.org>
> Commit-Queue: Ken Rockot <rockot@google.com>
> Cr-Commit-Position: refs/heads/master@{#778002}

Tbr: avi@chromium.org
Change-Id: I6a2270c87c294455907fb369640b3be0819b2431
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2247166
Commit-Queue: Ken Rockot <rockot@google.com>
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Ken Rockot <rockot@google.com>
Cr-Commit-Position: refs/heads/master@{#780151}
This commit is contained in:
Ken Rockot
2020-06-19 03:21:30 +00:00
committed by Commit Bot
parent f53c2e7991
commit 2fc09267f9
21 changed files with 245 additions and 34 deletions

@ -62,6 +62,7 @@
#include "content/child/field_trial.h"
#include "content/common/android/cpu_time_metrics.h"
#include "content/common/content_constants_internal.h"
#include "content/common/mojo_core_library_support.h"
#include "content/common/url_schemes.h"
#include "content/gpu/in_process_gpu_thread.h"
#include "content/public/app/content_main_delegate.h"
@ -85,6 +86,7 @@
#include "media/media_buildflags.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/dynamic_library_support.h"
#include "mojo/public/cpp/system/invitation.h"
#include "mojo/public/mojom/base/binder.mojom.h"
#include "ppapi/buildflags/buildflags.h"
@ -235,15 +237,20 @@ pid_t LaunchZygoteHelper(base::CommandLine* cmd_line,
// Append any switches from the browser process that need to be forwarded on
// to the zygote/renderers.
static const char* const kForwardSwitches[] = {
switches::kAndroidFontsPath, switches::kClearKeyCdmPathForTesting,
switches::kAndroidFontsPath,
switches::kClearKeyCdmPathForTesting,
switches::kEnableLogging, // Support, e.g., --enable-logging=stderr.
// Need to tell the zygote that it is headless so that we don't try to use
// the wrong type of main delegate.
switches::kHeadless,
// Zygote process needs to know what resources to have loaded when it
// becomes a renderer process.
switches::kForceDeviceScaleFactor, switches::kLoggingLevel,
switches::kPpapiInProcess, switches::kRegisterPepperPlugins, switches::kV,
switches::kForceDeviceScaleFactor,
switches::kLoggingLevel,
switches::kMojoCoreLibraryPath,
switches::kPpapiInProcess,
switches::kRegisterPepperPlugins,
switches::kV,
switches::kVModule,
};
cmd_line->CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(),
@ -837,13 +844,26 @@ int ContentMainRunnerImpl::Run(bool start_service_manager_only) {
*base::CommandLine::ForCurrentProcess();
std::string process_type =
command_line.GetSwitchValueASCII(switches::kProcessType);
// Run this logic on all child processes.
if (!process_type.empty()) {
if (process_type != service_manager::switches::kZygoteProcess) {
// Zygotes will run this at a later point in time when the command line
// has been updated.
InitializeFieldTrialAndFeatureList();
delegate_->PostFieldTrialInitialization();
}
// Run this logic on all child processes. Zygotes will run this at a later
// point in time when the command line has been updated.
if (!process_type.empty() &&
process_type != service_manager::switches::kZygoteProcess) {
InitializeFieldTrialAndFeatureList();
delegate_->PostFieldTrialInitialization();
#if defined(OS_LINUX)
// If dynamic Mojo Core is being used, ensure that it's loaded very early in
// the child/zygote process, before any sandbox is initialized. The library
// is not fully initialized with IPC support until a ChildProcess is later
// constructed, as initialization spawns a background thread which would be
// unsafe here.
if (IsMojoCoreSharedLibraryEnabled()) {
CHECK_EQ(mojo::LoadCoreLibrary(GetMojoCoreSharedLibraryPath()),
MOJO_RESULT_OK);
}
#endif // defined(OS_LINUX)
}
MainFunctionParams main_params(command_line);

@ -6,10 +6,13 @@
#include "base/command_line.h"
#include "content/app/content_main_runner_impl.h"
#include "content/common/mojo_core_library_support.h"
#include "content/public/app/content_main_delegate.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/service_names.mojom.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/system/dynamic_library_support.h"
#include "services/service_manager/embedder/switches.h"
#if defined(OS_WIN)
@ -68,12 +71,13 @@ ContentServiceManagerMainDelegate::OverrideProcessType() {
return content_main_params_.delegate->OverrideProcessType();
}
void ContentServiceManagerMainDelegate::OverrideMojoConfiguration(
void ContentServiceManagerMainDelegate::InitializeMojo(
mojo::core::Configuration* config) {
// If this is the browser process and there's no Mojo invitation pipe on the
// command line, we will serve as the global Mojo broker.
const auto& command_line = *base::CommandLine::ForCurrentProcess();
if (!command_line.HasSwitch(switches::kProcessType)) {
const bool is_browser = !command_line.HasSwitch(switches::kProcessType);
if (is_browser) {
if (mojo::PlatformChannel::CommandLineHasPassedEndpoint(command_line)) {
config->is_broker_process = false;
config->force_direct_shared_memory_allocation = true;
@ -90,6 +94,27 @@ void ContentServiceManagerMainDelegate::OverrideMojoConfiguration(
}
#endif
}
if (!IsMojoCoreSharedLibraryEnabled()) {
mojo::core::Init(*config);
return;
}
if (!is_browser) {
// Note that when dynamic Mojo Core is used, initialization for child
// processes happens elsewhere. See ContentMainRunnerImpl::Run() and
// ChildProcess construction.
return;
}
MojoInitializeFlags flags = MOJO_INITIALIZE_FLAG_NONE;
if (config->is_broker_process)
flags |= MOJO_INITIALIZE_FLAG_AS_BROKER;
if (config->force_direct_shared_memory_allocation)
flags |= MOJO_INITIALIZE_FLAG_FORCE_DIRECT_SHARED_MEMORY_ALLOCATION;
MojoResult result =
mojo::LoadAndInitializeCoreLibrary(GetMojoCoreSharedLibraryPath(), flags);
CHECK_EQ(MOJO_RESULT_OK, result);
}
std::vector<service_manager::Manifest>

@ -29,7 +29,7 @@ class ContentServiceManagerMainDelegate : public service_manager::MainDelegate {
int RunEmbedderProcess() override;
void ShutDownEmbedderProcess() override;
service_manager::ProcessType OverrideProcessType() override;
void OverrideMojoConfiguration(mojo::core::Configuration* config) override;
void InitializeMojo(mojo::core::Configuration* config) override;
std::vector<service_manager::Manifest> GetServiceManifests() override;
bool ShouldLaunchAsServiceProcess(
const service_manager::Identity& identity) override;

@ -6,10 +6,12 @@
#include <utility>
#include "base/command_line.h"
#include "build/build_config.h"
#include "content/browser/browser_process_sub_thread.h"
#include "content/browser/service_manager/service_manager_context.h"
#include "content/browser/startup_data_impl.h"
#include "content/common/mojo_core_library_support.h"
#include "content/public/common/content_features.h"
#include "content/public/common/service_manager_connection.h"
#include "mojo/core/embedder/embedder.h"
@ -22,13 +24,18 @@ ServiceManagerEnvironment::ServiceManagerEnvironment(
: io_thread_(std::move(io_thread)) {
scoped_refptr<base::SingleThreadTaskRunner> mojo_ipc_task_runner =
io_thread_->task_runner();
if (base::FeatureList::IsEnabled(features::kMojoDedicatedThread)) {
mojo_ipc_thread_.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0));
mojo_ipc_task_runner = mojo_ipc_thread_.task_runner();
if (!IsMojoCoreSharedLibraryEnabled()) {
// NOTE: If Mojo Core was loaded via shared library, IPC support is already
// initialized.
if (base::FeatureList::IsEnabled(features::kMojoDedicatedThread)) {
mojo_ipc_thread_.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0));
mojo_ipc_task_runner = mojo_ipc_thread_.task_runner();
}
mojo_ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>(
mojo_ipc_task_runner,
mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST);
}
mojo_ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>(
mojo_ipc_task_runner, mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST);
service_manager_context_ =
std::make_unique<ServiceManagerContext>(io_thread_->task_runner());
ServiceManagerConnection::GetForProcess()->Start();

@ -338,6 +338,7 @@ void BrowserChildProcessHostImpl::LaunchWithoutExtraCommandLineSwitches(
switches::kLogBestEffortTasks,
switches::kLogFile,
switches::kLoggingLevel,
switches::kMojoCoreLibraryPath,
switches::kPerfettoDisableInterning,
switches::kTraceToConsole,
switches::kV,

@ -7,8 +7,10 @@
#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"
@ -26,6 +28,7 @@ namespace {
const char kShellExecutableName[] = "content_shell.exe";
#else
const char kShellExecutableName[] = "content_shell";
const char kMojoCoreLibraryName[] = "libmojo_core.so";
#endif
base::FilePath GetCurrentDirectory() {
@ -77,6 +80,12 @@ class LaunchAsMojoClientBrowserTest : public ContentBrowserTest {
return controller;
}
#if defined(OS_LINUX)
base::FilePath GetMojoCoreLibraryPath() {
return GetFilePathNextToCurrentExecutable(kMojoCoreLibraryName);
}
#endif
private:
base::FilePath GetFilePathNextToCurrentExecutable(
const std::string& filename) {
@ -125,5 +134,44 @@ IN_PROC_BROWSER_TEST_F(LaunchAsMojoClientBrowserTest, LaunchAndBindInterface) {
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(MEMORY_SANITIZER)
IN_PROC_BROWSER_TEST_F(LaunchAsMojoClientBrowserTest, 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<mojom::ShellController> 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(MEMORY_SANITIZER)
} // namespace
} // namespace content

@ -3284,6 +3284,7 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer(
switches::kDefaultTileWidth,
switches::kDefaultTileHeight,
switches::kMinHeightForGpuRasterTile,
switches::kMojoCoreLibraryPath,
switches::kDisable2dCanvasImageChromium,
switches::kDisableYUVImageDecoding,
switches::kDisableAcceleratedVideoDecode,

@ -17,6 +17,9 @@
#include "build/build_config.h"
#include "content/child/child_thread_impl.h"
#include "content/common/android/cpu_time_metrics.h"
#include "content/common/mojo_core_library_support.h"
#include "mojo/public/cpp/system/dynamic_library_support.h"
#include "services/service_manager/sandbox/sandbox_type.h"
#include "services/tracing/public/cpp/trace_startup.h"
#include "third_party/blink/public/common/features.h"
@ -38,6 +41,23 @@ ChildProcess::ChildProcess(base::ThreadPriority io_thread_priority,
DCHECK(!g_lazy_child_process_tls.Pointer()->Get());
g_lazy_child_process_tls.Pointer()->Set(this);
#if defined(OS_LINUX)
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
if (IsMojoCoreSharedLibraryEnabled()) {
// If we're in a child process on Linux and dynamic Mojo Core is in use, we
// expect early process startup code (see ContentMainRunnerImpl::Run()) to
// have already loaded the library via |mojo::LoadCoreLibrary()|, rendering
// this call safe even from within a strict sandbox.
MojoInitializeFlags flags = MOJO_INITIALIZE_FLAG_NONE;
if (service_manager::IsUnsandboxedSandboxType(
service_manager::SandboxTypeFromCommandLine(command_line))) {
flags |= MOJO_INITIALIZE_FLAG_FORCE_DIRECT_SHARED_MEMORY_ALLOCATION;
}
CHECK_EQ(MOJO_RESULT_OK, mojo::InitializeCoreLibrary(flags));
}
#endif
// Initialize ThreadPoolInstance if not already done. A ThreadPoolInstance may
// already exist when ChildProcess is instantiated in the browser process or
// in a test process.

@ -5,6 +5,8 @@
#include "content/child/child_thread_impl.h"
#include <signal.h>
#include <memory>
#include <string>
#include <utility>
@ -47,6 +49,7 @@
#include "content/common/child_process.mojom.h"
#include "content/common/field_trial_recorder.mojom.h"
#include "content/common/in_process_child_thread_params.h"
#include "content/common/mojo_core_library_support.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
@ -570,16 +573,19 @@ void ChildThreadImpl::Init(const Options& options) {
// IPC mode.
mojo::ScopedMessagePipeHandle child_process_pipe;
if (!IsInBrowserProcess()) {
scoped_refptr<base::SingleThreadTaskRunner> mojo_ipc_task_runner =
GetIOTaskRunner();
if (base::FeatureList::IsEnabled(features::kMojoDedicatedThread)) {
mojo_ipc_thread_.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0));
mojo_ipc_task_runner = mojo_ipc_thread_.task_runner();
// If using a shared Mojo Core library, IPC support is already initialized.
if (!IsMojoCoreSharedLibraryEnabled()) {
scoped_refptr<base::SingleThreadTaskRunner> mojo_ipc_task_runner =
GetIOTaskRunner();
if (base::FeatureList::IsEnabled(features::kMojoDedicatedThread)) {
mojo_ipc_thread_.StartWithOptions(
base::Thread::Options(base::MessagePumpType::IO, 0));
mojo_ipc_task_runner = mojo_ipc_thread_.task_runner();
}
mojo_ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>(
mojo_ipc_task_runner,
mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST);
}
mojo_ipc_support_.reset(new mojo::core::ScopedIPCSupport(
mojo_ipc_task_runner,
mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST));
mojo::IncomingInvitation invitation = InitializeMojoIPCChannel();
child_process_pipe = invitation.ExtractMessagePipe(0);
} else {

@ -153,6 +153,8 @@ source_set("common") {
"mac/font_loader.mm",
"media/cdm_info.cc",
"media/media_player_delegate_messages.h",
"mojo_core_library_support.cc",
"mojo_core_library_support.h",
"navigation_gesture.h",
"navigation_params.cc",
"navigation_params.h",

@ -0,0 +1,31 @@
// 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 "content/common/mojo_core_library_support.h"
#include "base/command_line.h"
#include "build/build_config.h"
#include "content/public/common/content_switches.h"
namespace content {
bool IsMojoCoreSharedLibraryEnabled() {
return GetMojoCoreSharedLibraryPath() != base::nullopt;
}
base::Optional<base::FilePath> GetMojoCoreSharedLibraryPath() {
#if defined(OS_LINUX)
const base::CommandLine& command_line =
*base::CommandLine::ForCurrentProcess();
if (!command_line.HasSwitch(switches::kMojoCoreLibraryPath))
return base::nullopt;
return command_line.GetSwitchValuePath(switches::kMojoCoreLibraryPath);
#else
// Content does not yet properly support dynamic Mojo Core on platforms other
// than Linux and Chrome OS.
return base::nullopt;
#endif
}
} // namespace content

@ -0,0 +1,26 @@
// 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.
#ifndef CONTENT_COMMON_MOJO_CORE_LIBRARY_SUPPORT_H_
#define CONTENT_COMMON_MOJO_CORE_LIBRARY_SUPPORT_H_
#include "base/files/file_path.h"
#include "base/optional.h"
#include "content/common/content_export.h"
namespace content {
// Indicates whether the calling process was launched with the option to
// initialize Mojo Core from a shared library rather than the statically linked
// implementation.
CONTENT_EXPORT bool IsMojoCoreSharedLibraryEnabled();
// Returns the path to the Mojo Core shared library passed in on the command
// line for the calling process, or null if the process was launched without a
// Mojo Core library path on the command line.
CONTENT_EXPORT base::Optional<base::FilePath> GetMojoCoreSharedLibraryPath();
} // namespace content
#endif // CONTENT_COMMON_MOJO_CORE_LIBRARY_SUPPORT_H_

@ -620,6 +620,10 @@ const char kMessageLoopTypeUi[] = "message-loop-type-ui";
const char kMockCertVerifierDefaultResultForTesting[] =
"mock-cert-verifier-default-result-for-testing";
// Initializes Mojo Core from a shared library at the specified path, rather
// than using the version of Mojo Core embedded within the Content executable.
const char kMojoCoreLibraryPath[] = "mojo-core-library-path";
// Use a Mojo-based LocalStorage implementation.
const char kMojoLocalStorage[] = "mojo-local-storage";

@ -183,6 +183,7 @@ CONTENT_EXPORT extern const char kMHTMLGeneratorOption[];
CONTENT_EXPORT extern const char kMHTMLSkipNostoreMain[];
CONTENT_EXPORT extern const char kMHTMLSkipNostoreAll[];
CONTENT_EXPORT extern const char kMockCertVerifierDefaultResultForTesting[];
CONTENT_EXPORT extern const char kMojoCoreLibraryPath[];
CONTENT_EXPORT extern const char kMojoLocalStorage[];
CONTENT_EXPORT extern const char kNetworkQuietTimeout[];
CONTENT_EXPORT extern const char kNoZygote[];

@ -1091,4 +1091,5 @@ group("content_shell_crash_test") {
mojom("shell_controller_mojom") {
testonly = true
sources = [ "common/shell_controller.test-mojom" ]
public_deps = [ "//mojo/public/mojom/base" ]
}

@ -110,6 +110,14 @@ class ShellControllerImpl : public mojom::ShellController {
std::move(callback).Run(base::nullopt);
}
void ExecuteJavaScript(const base::string16& script,
ExecuteJavaScriptCallback callback) override {
CHECK(!Shell::windows().empty());
WebContents* contents = Shell::windows()[0]->web_contents();
contents->GetMainFrame()->ExecuteJavaScriptForTests(script,
std::move(callback));
}
void ShutDown() override { Shell::CloseAllWindows(); }
};

@ -4,6 +4,9 @@
module content.mojom;
import "mojo/public/mojom/base/string16.mojom";
import "mojo/public/mojom/base/values.mojom";
// A control interface which can be accessed by clients launching Content Shell
// with a Mojo invitation on the command line. This is used by
// LaunchAsMojoClientBrowserTest to exercise that mode of execution.
@ -12,6 +15,11 @@ interface ShellController {
// running Content Shell process which receives this message.
GetSwitchValue(string name) => (string? value);
// Asks the shell to execute the JavaScript code in |script| within the first
// available window, and returns the resulting value.
ExecuteJavaScript(mojo_base.mojom.String16 script)
=> (mojo_base.mojom.Value value);
// Tells Content Shell to close all windows and shut down. After sending this
// message, the Content Shell browser process should be expected to terminate
// soon.

@ -1525,7 +1525,10 @@ test("content_browsertests") {
"//mojo/public/cpp/platform",
"//mojo/public/mojom/base",
]
data_deps += [ "//content/shell:content_shell" ]
data_deps += [
"//content/shell:content_shell",
"//mojo/core:shared_library",
]
}
if (!is_chrome_branded) {

@ -361,8 +361,7 @@ int Main(const MainParams& params) {
mojo_config.is_broker_process = true;
}
mojo_config.max_message_num_bytes = kMaximumMojoMessageSize;
delegate->OverrideMojoConfiguration(&mojo_config);
mojo::core::Init(mojo_config);
delegate->InitializeMojo(&mojo_config);
ui::RegisterPathProvider();

@ -24,8 +24,7 @@ ProcessType MainDelegate::OverrideProcessType() {
return ProcessType::kDefault;
}
void MainDelegate::OverrideMojoConfiguration(
mojo::core::Configuration* config) {}
void MainDelegate::InitializeMojo(mojo::core::Configuration* config) {}
std::vector<Manifest> MainDelegate::GetServiceManifests() {
return std::vector<Manifest>();

@ -71,8 +71,9 @@ class COMPONENT_EXPORT(SERVICE_MANAGER_EMBEDDER) MainDelegate {
// return |ProcessType::kDefault| to avoid overriding.
virtual ProcessType OverrideProcessType();
// Allows the embedder to override the process-wide Mojop configuration.
virtual void OverrideMojoConfiguration(mojo::core::Configuration* config);
// Allows the embedder to override the process-wide Mojo configuration and
// initialization.
virtual void InitializeMojo(mojo::core::Configuration* config);
// Gets the list of service manifests with which to initialize the Service
// Manager. This list must describe the complete set of usable services in