Bug 1845946 - Part 3. Use NS_InitMinimalXPCOM with the GMP process. r=media-playback-reviewers,alwu

This patch brings up the basic plumbing necessary in the GMP process to
get benefits such as logging integration via the pref updates used by
about:support. Given the priority to get logging specifically working,
this patch leaves a few things unfinished for a future bug. Specifically
DLL services (for blocking antivirus on Windows) and BGHM.

Differential Revision: https://phabricator.services.mozilla.com/D185337
This commit is contained in:
Andrew Osmond 2023-08-18 22:01:09 +00:00
parent 00188ff292
commit f99d50b108
15 changed files with 282 additions and 29 deletions

View File

@ -9,6 +9,7 @@
#include "base/task.h"
#include "ChildProfilerController.h"
#include "ChromiumCDMAdapter.h"
#include "GeckoProfiler.h"
#ifdef XP_LINUX
# include "dlfcn.h"
#endif
@ -25,6 +26,7 @@
#include "GMPVideoEncoderChild.h"
#include "GMPVideoHost.h"
#include "mozilla/Algorithm.h"
#include "mozilla/BackgroundHangMonitor.h"
#include "mozilla/FOGIPC.h"
#include "mozilla/glean/GleanMetrics.h"
#include "mozilla/ipc/CrashReporterClient.h"
@ -38,6 +40,7 @@
#include "nsThreadManager.h"
#include "nsXULAppAPI.h"
#include "nsIXULRuntime.h"
#include "nsXPCOM.h"
#include "prio.h"
#ifdef XP_WIN
# include <stdlib.h> // for _exit()
@ -74,10 +77,12 @@ GMPChild::~GMPChild() {
#endif
}
bool GMPChild::Init(const nsAString& aPluginPath,
bool GMPChild::Init(const nsAString& aPluginPath, const char* aParentBuildID,
mozilla::ipc::UntypedEndpoint&& aEndpoint) {
GMP_CHILD_LOG_DEBUG("%s pluginPath=%s", __FUNCTION__,
NS_ConvertUTF16toUTF8(aPluginPath).get());
GMP_CHILD_LOG_DEBUG("%s pluginPath=%s useXpcom=%d, useNativeEvent=%d",
__FUNCTION__, NS_ConvertUTF16toUTF8(aPluginPath).get(),
GMPProcessChild::UseXPCOM(),
GMPProcessChild::UseNativeEventProcessing());
// GMPChild needs nsThreadManager to create the ProfilerChild thread.
// It is also used on debug builds for the sandbox tests.
@ -89,13 +94,50 @@ bool GMPChild::Init(const nsAString& aPluginPath,
return false;
}
// This must be checked before any IPDL message, which may hit sentinel
// errors due to parent and content processes having different
// versions.
MessageChannel* channel = GetIPCChannel();
if (channel && !channel->SendBuildIDsMatchMessage(aParentBuildID)) {
// We need to quit this process if the buildID doesn't match the parent's.
// This can occur when an update occurred in the background.
ipc::ProcessChild::QuickExit();
}
CrashReporterClient::InitSingleton(this);
if (GMPProcessChild::UseXPCOM()) {
if (NS_WARN_IF(NS_FAILED(NS_InitMinimalXPCOM()))) {
return false;
}
} else {
BackgroundHangMonitor::Startup();
}
mPluginPath = aPluginPath;
nsAutoCString processName("GMPlugin Process");
nsAutoCString pluginName;
if (GetPluginName(pluginName)) {
processName.AppendLiteral(" (");
processName.Append(pluginName);
processName.AppendLiteral(")");
}
profiler_set_process_name(processName);
return true;
}
void GMPChild::Shutdown() {
if (GMPProcessChild::UseXPCOM()) {
NS_ShutdownXPCOM(nullptr);
} else {
BackgroundHangMonitor::Shutdown();
}
}
mozilla::ipc::IPCResult GMPChild::RecvProvideStorageId(
const nsCString& aStorageId) {
GMP_CHILD_LOG_DEBUG("%s", __FUNCTION__);
@ -258,6 +300,24 @@ bool GMPChild::GetUTF8LibPath(nsACString& aOutLibPath) {
return true;
}
bool GMPChild::GetPluginName(nsACString& aPluginName) const {
// Extract the plugin directory name if possible.
nsCOMPtr<nsIFile> libFile;
nsresult rv = NS_NewLocalFile(mPluginPath, true, getter_AddRefs(libFile));
NS_ENSURE_SUCCESS(rv, false);
nsCOMPtr<nsIFile> parent;
rv = libFile->GetParent(getter_AddRefs(parent));
NS_ENSURE_SUCCESS(rv, false);
nsAutoString parentLeafName;
rv = parent->GetLeafName(parentLeafName);
NS_ENSURE_SUCCESS(rv, false);
aPluginName.Assign(NS_ConvertUTF16toUTF8(parentLeafName));
return true;
}
static nsCOMPtr<nsIFile> AppendFile(nsCOMPtr<nsIFile>&& aFile,
const nsString& aStr) {
return (aFile && NS_SUCCEEDED(aFile->Append(aStr))) ? aFile : nullptr;
@ -652,6 +712,11 @@ mozilla::ipc::IPCResult GMPChild::RecvInitProfiler(
return IPC_OK();
}
mozilla::ipc::IPCResult GMPChild::RecvPreferenceUpdate(const Pref& aPref) {
Preferences::SetPreference(aPref);
return IPC_OK();
}
} // namespace gmp
} // namespace mozilla

View File

@ -29,8 +29,9 @@ class GMPChild : public PGMPChild {
GMPChild();
bool Init(const nsAString& aPluginPath,
bool Init(const nsAString& aPluginPath, const char* aParentBuildID,
mozilla::ipc::UntypedEndpoint&& aEndpoint);
void Shutdown();
MessageLoop* GMPMessageLoop();
// Main thread only.
@ -48,6 +49,8 @@ class GMPChild : public PGMPChild {
bool GetUTF8LibPath(nsACString& aOutLibPath);
bool GetPluginName(nsACString& aPluginName) const;
mozilla::ipc::IPCResult RecvProvideStorageId(const nsCString& aStorageId);
mozilla::ipc::IPCResult RecvStartPlugin(const nsString& aAdapter);
@ -75,6 +78,8 @@ class GMPChild : public PGMPChild {
mozilla::ipc::IPCResult RecvInitProfiler(
Endpoint<mozilla::PProfilerChild>&& aEndpoint);
mozilla::ipc::IPCResult RecvPreferenceUpdate(const Pref& aPref);
void ActorDestroy(ActorDestroyReason aWhy) override;
void ProcessingError(Result aCode, const char* aReason) override;

View File

@ -405,6 +405,17 @@ nsresult GMPParent::LoadProcess() {
return NS_OK;
}
void GMPParent::OnPreferenceChange(const mozilla::dom::Pref& aPref) {
MOZ_ASSERT(GMPEventTarget()->IsOnCurrentThread());
GMP_PARENT_LOG_DEBUG("%s", __FUNCTION__);
if (!mProcess || !mProcess->UseXPCOM()) {
return;
}
Unused << SendPreferenceUpdate(aPref);
}
mozilla::ipc::IPCResult GMPParent::RecvPGMPContentChildDestroyed() {
--mGMPContentChildCount;
if (!IsUsed()) {

View File

@ -87,6 +87,8 @@ class GMPParent final
GMPState State() const;
nsCOMPtr<nsISerialEventTarget> GMPEventTarget();
void OnPreferenceChange(const mozilla::dom::Pref& aPref);
// A GMP can either be a single instance shared across all NodeIds (like
// in the OpenH264 case), or we can require a new plugin instance for every
// NodeIds running the plugin (as in the EME plugin case).

View File

@ -8,38 +8,49 @@
#include "base/command_line.h"
#include "base/string_util.h"
#include "mozilla/ipc/IOThreadChild.h"
#include "mozilla/BackgroundHangMonitor.h"
#include "mozilla/GeckoArgs.h"
using mozilla::ipc::IOThreadChild;
namespace mozilla::gmp {
/* static */ bool GMPProcessChild::sUseXpcom = false;
/* static */ bool GMPProcessChild::sUseNativeEventProcessing = true;
void GMPProcessChild::InitStatics(int aArgc, char* aArgv[]) {
Maybe<bool> nativeEvent = geckoargs::sPluginNativeEvent.Get(aArgc, aArgv);
sUseNativeEventProcessing = nativeEvent.isSome() && *nativeEvent;
Maybe<uint64_t> prefsLen =
geckoargs::sPrefsLen.Get(aArgc, aArgv, CheckArgFlag::None);
Maybe<uint64_t> prefMapSize =
geckoargs::sPrefMapSize.Get(aArgc, aArgv, CheckArgFlag::None);
sUseXpcom = prefsLen.isSome() && prefMapSize.isSome();
}
GMPProcessChild::~GMPProcessChild() = default;
bool GMPProcessChild::Init(int aArgc, char* aArgv[]) {
nsAutoString pluginFilename;
Maybe<const char*> parentBuildID =
geckoargs::sParentBuildID.Get(aArgc, aArgv);
if (NS_WARN_IF(parentBuildID.isNothing())) {
return false;
}
#if defined(XP_UNIX)
// NB: need to be very careful in ensuring that the first arg
// (after the binary name) here is indeed the plugin module path.
// Keep in sync with dom/plugins/PluginModuleParent.
std::vector<std::string> values = CommandLine::ForCurrentProcess()->argv();
MOZ_ASSERT(values.size() >= 2, "not enough args");
CopyUTF8toUTF16(nsDependentCString(values[1].c_str()), pluginFilename);
#elif defined(XP_WIN)
std::vector<std::wstring> values =
CommandLine::ForCurrentProcess()->GetLooseValues();
MOZ_ASSERT(values.size() >= 1, "not enough loose args");
pluginFilename = nsDependentString(values[0].c_str());
#else
# error Not implemented
#endif
Maybe<const char*> pluginPath = geckoargs::sPluginPath.Get(aArgc, aArgv);
if (NS_WARN_IF(pluginPath.isNothing())) {
return false;
}
BackgroundHangMonitor::Startup();
NS_ConvertUTF8toUTF16 widePluginPath(*pluginPath);
return mPlugin->Init(pluginFilename, TakeInitialEndpoint());
if (sUseXpcom && NS_WARN_IF(!ProcessChild::InitPrefs(aArgc, aArgv))) {
return false;
}
return mPlugin->Init(widePluginPath, *parentBuildID, TakeInitialEndpoint());
}
void GMPProcessChild::CleanUp() { BackgroundHangMonitor::Shutdown(); }
void GMPProcessChild::CleanUp() { mPlugin->Shutdown(); }
} // namespace mozilla::gmp

View File

@ -21,11 +21,18 @@ class GMPProcessChild final : public mozilla::ipc::ProcessChild {
using ProcessChild::ProcessChild;
~GMPProcessChild();
static void InitStatics(int aArgc, char* aArgv[]);
static bool UseNativeEventProcessing() { return sUseNativeEventProcessing; }
static bool UseXPCOM() { return sUseXpcom; }
bool Init(int aArgc, char* aArgv[]) override;
void CleanUp() override;
private:
const RefPtr<GMPChild> mPlugin = new GMPChild;
static bool sUseXpcom;
static bool sUseNativeEventProcessing;
};
} // namespace mozilla::gmp

View File

@ -11,6 +11,10 @@
# include "WinUtils.h"
#endif
#include "GMPLog.h"
#include "mozilla/GeckoArgs.h"
#include "mozilla/ipc/ProcessChild.h"
#include "mozilla/ipc/ProcessUtils.h"
#include "mozilla/StaticPrefs_media.h"
#include "base/string_util.h"
#include "base/process_util.h"
@ -64,7 +68,8 @@ void GMPProcessParent::InitStaticMainThread() {
GMPProcessParent::GMPProcessParent(const std::string& aGMPPath)
: GeckoChildProcessHost(GeckoProcessType_GMPlugin),
mGMPPath(aGMPPath)
mGMPPath(aGMPPath),
mUseXpcom(StaticPrefs::media_gmp_use_minimal_xpcom())
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
,
mRequiresWindowServer(false)
@ -80,7 +85,82 @@ GMPProcessParent::GMPProcessParent(const std::string& aGMPPath)
GMPProcessParent::~GMPProcessParent() { MOZ_COUNT_DTOR(GMPProcessParent); }
bool GMPProcessParent::Launch(int32_t aTimeoutMs) {
class PrefSerializerRunnable final : public Runnable {
public:
PrefSerializerRunnable()
: Runnable("GMPProcessParent::PrefSerializerRunnable"),
mMonitor("GMPProcessParent::PrefSerializerRunnable::mMonitor") {}
NS_IMETHOD Run() override {
auto prefSerializer = MakeUnique<ipc::SharedPreferenceSerializer>();
bool success =
prefSerializer->SerializeToSharedMemory(GeckoProcessType_GMPlugin,
/* remoteType */ ""_ns);
MonitorAutoLock lock(mMonitor);
MOZ_ASSERT(!mComplete);
if (success) {
mPrefSerializer = std::move(prefSerializer);
}
mComplete = true;
lock.Notify();
return NS_OK;
}
void Wait(int32_t aTimeoutMs,
UniquePtr<ipc::SharedPreferenceSerializer>& aOut) {
MonitorAutoLock lock(mMonitor);
TimeDuration timeout = TimeDuration::FromMilliseconds(aTimeoutMs);
while (!mComplete) {
if (lock.Wait(timeout) == CVStatus::Timeout) {
return;
}
}
aOut = std::move(mPrefSerializer);
}
private:
Monitor mMonitor;
UniquePtr<ipc::SharedPreferenceSerializer> mPrefSerializer
MOZ_GUARDED_BY(mMonitor);
bool mComplete MOZ_GUARDED_BY(mMonitor) = false;
};
nsresult rv;
vector<string> args;
UniquePtr<ipc::SharedPreferenceSerializer> prefSerializer;
ipc::ProcessChild::AddPlatformBuildID(args);
if (mUseXpcom) {
// Dispatch our runnable to the main thread to grab the serialized prefs. We
// can only do this on the main thread, and unfortunately we are the only
// process that launches from the non-main thread.
auto prefTask = MakeRefPtr<PrefSerializerRunnable>();
rv = NS_DispatchToMainThread(prefTask);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
// We don't want to release our thread context while we wait for the main
// thread to process the prefs. We already block when waiting for the launch
// of the process itself to finish, and the state machine assumes this call
// is blocking. This is also important for the buffering of pref updates,
// since we know any tasks dispatched with updates won't run until we launch
// (or fail to launch) the process.
prefTask->Wait(aTimeoutMs, prefSerializer);
if (NS_WARN_IF(!prefSerializer)) {
return false;
}
prefSerializer->AddSharedPrefCmdLineArgs(*this, args);
}
if (StaticPrefs::media_gmp_use_native_event_processing()) {
geckoargs::sPluginNativeEvent.Put(args);
}
#ifdef ALLOW_GECKO_CHILD_PROCESS_ARCH
GMP_LOG_DEBUG("GMPProcessParent::Launch() mLaunchArch: %d", mLaunchArch);
@ -100,7 +180,7 @@ bool GMPProcessParent::Launch(int32_t aTimeoutMs) {
#else
nsAutoCString normalizedPath;
#endif
nsresult rv = NormalizePath(mGMPPath.c_str(), normalizedPath);
rv = NormalizePath(mGMPPath.c_str(), normalizedPath);
if (NS_WARN_IF(NS_FAILED(rv))) {
GMP_LOG_DEBUG(
"GMPProcessParent::Launch: "
@ -144,12 +224,13 @@ bool GMPProcessParent::Launch(int32_t aTimeoutMs) {
}
# endif
args.push_back(WideToUTF8(wGMPPath));
std::string gmpPath = WideToUTF8(wGMPPath);
geckoargs::sPluginPath.Put(gmpPath.c_str(), args);
#else
if (NS_SUCCEEDED(rv)) {
args.push_back(normalizedPath.get());
geckoargs::sPluginPath.Put(normalizedPath.get(), args);
} else {
args.push_back(mGMPPath);
geckoargs::sPluginPath.Put(mGMPPath.c_str(), args);
}
#endif
@ -159,6 +240,10 @@ bool GMPProcessParent::Launch(int32_t aTimeoutMs) {
AddFdToRemap(kInvalidFd, kInvalidFd);
AddFdToRemap(kInvalidFd, kInvalidFd);
#endif
// We need to wait until OnChannelConnected to clear the pref serializer, but
// SyncLaunch will block until that is called, so we don't actually need to do
// any overriding, and it only lives on the stack.
return SyncLaunch(args, aTimeoutMs);
}

View File

@ -30,6 +30,7 @@ class GMPProcessParent final : public mozilla::ipc::GeckoChildProcessHost {
bool CanShutdown() override { return true; }
const std::string& GetPluginFilePath() { return mGMPPath; }
bool UseXPCOM() const { return mUseXpcom; }
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
// Init static members on the main thread
@ -63,6 +64,9 @@ class GMPProcessParent final : public mozilla::ipc::GeckoChildProcessHost {
std::string mGMPPath;
nsCOMPtr<nsIRunnable> mDeletedCallback;
// Whether or not XPCOM is enabled in the GMP process.
bool mUseXpcom;
#if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
// Indicates whether we'll start the Mac GMP sandbox during
// process launch (earlyinit) which is the new preferred method

View File

@ -111,6 +111,7 @@ nsresult GeckoMediaPluginServiceParent::Init() {
obsService->AddObserver(this, "browser:purge-session-history", false));
MOZ_ALWAYS_SUCCEEDS(
obsService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false));
MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "nsPref:changed", false));
#ifdef DEBUG
MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(
@ -328,11 +329,45 @@ GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject,
"gmp::GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread",
this, &GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread,
t));
} else if (!strcmp("nsPref:changed", aTopic)) {
bool hasProcesses = false;
{
MutexAutoLock lock(mMutex);
for (const auto& plugin : mPlugins) {
if (plugin->State() == GMPState::Loaded) {
hasProcesses = true;
break;
}
}
}
if (hasProcesses) {
// We know prefs are ASCII here.
NS_LossyConvertUTF16toASCII strData(aSomeData);
mozilla::dom::Pref pref(strData, /* isLocked */ false,
/* isSanitized */ false, Nothing(), Nothing());
Preferences::GetPreference(&pref, GeckoProcessType_GMPlugin,
/* remoteType */ ""_ns);
return GMPDispatch(NewRunnableMethod<mozilla::dom::Pref&&>(
"gmp::GeckoMediaPluginServiceParent::OnPreferenceChanged", this,
&GeckoMediaPluginServiceParent::OnPreferenceChanged,
std::move(pref)));
}
}
return NS_OK;
}
void GeckoMediaPluginServiceParent::OnPreferenceChanged(
mozilla::dom::Pref&& aPref) {
AssertOnGMPThread();
MutexAutoLock lock(mMutex);
for (const auto& plugin : mPlugins) {
plugin->OnPreferenceChange(aPref);
}
}
RefPtr<GenericPromise> GeckoMediaPluginServiceParent::EnsureInitialized() {
MonitorAutoLock lock(mInitPromiseMonitor);
if (mLoadPluginsFromDiskComplete) {

View File

@ -91,6 +91,7 @@ class GeckoMediaPluginServiceParent final
private:
friend class GMPServiceParent;
class Observer;
virtual ~GeckoMediaPluginServiceParent();
@ -126,6 +127,7 @@ class GeckoMediaPluginServiceParent final
const mozilla::OriginAttributesPattern& aPattern);
void ForgetThisBaseDomainOnGMPThread(const nsACString& aBaseDomain);
void ClearRecentHistoryOnGMPThread(PRTime aSince);
void OnPreferenceChanged(mozilla::dom::Pref&& aPref);
already_AddRefed<GMPParent> GetById(uint32_t aPluginId);

View File

@ -3,6 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
include PrefsTypes;
include protocol PGMPContent;
include protocol PGMPTimer;
include protocol PGMPStorage;
@ -43,6 +44,7 @@ child:
async CloseActive();
async InitGMPContentChild(Endpoint<PGMPContentChild> endpoint);
async InitProfiler(Endpoint<PProfilerChild> endpoint);
async PreferenceUpdate(Pref pref);
// Tells the GMP process to flush any pending telemetry.
// Used in tests and ping assembly. Buffer contains bincoded Rust structs.

View File

@ -9668,6 +9668,18 @@
value: false
mirror: always
# Does the GMPlugin process initialize minimal XPCOM
- name: media.gmp.use-minimal-xpcom
type: RelaxedAtomicBool
value: false
mirror: always
# Does the GMPlugin process initialize minimal XPCOM
- name: media.gmp.use-native-event-processing
type: RelaxedAtomicBool
value: true
mirror: always
#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
# Whether to allow, on a Linux system that doesn't support the necessary
# sandboxing features, loading Gecko Media Plugins unsandboxed. However, EME

View File

@ -131,6 +131,10 @@ static CommandLineArg<bool> sSafeMode{"-safeMode", "safemode"};
static CommandLineArg<bool> sIsForBrowser{"-isForBrowser", "isforbrowser"};
static CommandLineArg<bool> sNotForBrowser{"-notForBrowser", "notforbrowser"};
static CommandLineArg<const char*> sPluginPath{"-pluginPath", "pluginpath"};
static CommandLineArg<bool> sPluginNativeEvent{"-pluginNativeEvent",
"pluginnativeevent"};
#if defined(XP_WIN)
# if defined(MOZ_SANDBOX)
static CommandLineArg<bool> sWin32kLockedDown{"-win32kLockedDown",

View File

@ -247,6 +247,7 @@
# include "mozilla/CodeCoverageHandler.h"
#endif
#include "GMPProcessChild.h"
#include "SafeMode.h"
#ifdef MOZ_BACKGROUNDTASKS
@ -6036,6 +6037,8 @@ bool XRE_UseNativeEventProcessing() {
# endif // defined(XP_WIN)
}
#endif // defined(XP_MACOSX) || defined(XP_WIN)
case GeckoProcessType_GMPlugin:
return mozilla::gmp::GMPProcessChild::UseNativeEventProcessing();
case GeckoProcessType_Content:
return StaticPrefs::dom_ipc_useNativeEventProcessing_content();
default:

View File

@ -518,6 +518,11 @@ nsresult XRE_InitChildProcess(int aArgc, char* aArgv[],
uiLoopType = MessageLoop::TYPE_MOZILLA_CHILD;
break;
case GeckoProcessType_GMPlugin:
gmp::GMPProcessChild::InitStatics(aArgc, aArgv);
uiLoopType = gmp::GMPProcessChild::UseXPCOM()
? MessageLoop::TYPE_MOZILLA_CHILD
: MessageLoop::TYPE_DEFAULT;
break;
case GeckoProcessType_RemoteSandboxBroker:
uiLoopType = MessageLoop::TYPE_DEFAULT;
break;