tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

GMPService.cpp (17937B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "GMPService.h"
      7 
      8 #include "ChromiumCDMParent.h"
      9 #include "GMPLog.h"
     10 #include "GMPParent.h"
     11 #include "GMPProcessParent.h"
     12 #include "GMPServiceChild.h"
     13 #include "GMPServiceParent.h"
     14 #include "GMPVideoDecoderParent.h"
     15 #include "mozilla/ClearOnShutdown.h"
     16 #include "mozilla/EventDispatcher.h"
     17 #include "mozilla/dom/Document.h"
     18 #include "mozilla/dom/PluginCrashedEvent.h"
     19 #include "mozilla/ipc/GeckoChildProcessHost.h"
     20 #include "nsThreadUtils.h"
     21 #if defined(XP_LINUX) && defined(MOZ_SANDBOX)
     22 #  include "mozilla/SandboxInfo.h"
     23 #endif
     24 #include "VideoUtils.h"
     25 #include "mozilla/Services.h"
     26 #include "mozilla/SyncRunnable.h"
     27 #include "nsAppDirectoryServiceDefs.h"
     28 #include "nsComponentManagerUtils.h"
     29 #include "nsDirectoryServiceDefs.h"
     30 #include "nsDirectoryServiceUtils.h"
     31 #include "nsGlobalWindowInner.h"
     32 #include "nsHashKeys.h"
     33 #include "nsIObserverService.h"
     34 #include "nsIXULAppInfo.h"
     35 #include "nsNativeCharsetUtils.h"
     36 #include "nsXPCOMPrivate.h"
     37 #include "prio.h"
     38 #include "runnable_utils.h"
     39 
     40 namespace mozilla {
     41 
     42 LogModule* GetGMPLog() {
     43  static LazyLogModule sLog("GMP");
     44  return sLog;
     45 }
     46 
     47 LogModule* GetGMPLibraryLog() {
     48  static LazyLogModule sLog("GMPLibrary");
     49  return sLog;
     50 }
     51 
     52 GMPLogLevel GetGMPLibraryLogLevel() {
     53  switch (GetGMPLibraryLog()->Level()) {
     54    case LogLevel::Disabled:
     55      return kGMPLogQuiet;
     56    case LogLevel::Error:
     57      return kGMPLogError;
     58    case LogLevel::Warning:
     59      return kGMPLogWarning;
     60    case LogLevel::Info:
     61      return kGMPLogInfo;
     62    case LogLevel::Debug:
     63      return kGMPLogDebug;
     64    case LogLevel::Verbose:
     65      return kGMPLogDetail;
     66  }
     67  return kGMPLogInvalid;
     68 }
     69 
     70 #ifdef __CLASS__
     71 #  undef __CLASS__
     72 #endif
     73 #define __CLASS__ "GMPService"
     74 
     75 namespace gmp {
     76 
     77 static StaticRefPtr<GeckoMediaPluginService> sSingletonService;
     78 
     79 class GMPServiceCreateHelper final : public mozilla::Runnable {
     80  RefPtr<GeckoMediaPluginService> mService;
     81 
     82 public:
     83  static already_AddRefed<GeckoMediaPluginService> GetOrCreate() {
     84    RefPtr<GeckoMediaPluginService> service;
     85 
     86    if (NS_IsMainThread()) {
     87      service = GetOrCreateOnMainThread();
     88    } else {
     89      RefPtr<GMPServiceCreateHelper> createHelper =
     90          new GMPServiceCreateHelper();
     91 
     92      mozilla::SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(),
     93                                              createHelper, true);
     94 
     95      service = std::move(createHelper->mService);
     96    }
     97 
     98    return service.forget();
     99  }
    100 
    101 private:
    102  GMPServiceCreateHelper() : Runnable("GMPServiceCreateHelper") {}
    103 
    104  ~GMPServiceCreateHelper() { MOZ_ASSERT(!mService); }
    105 
    106  static already_AddRefed<GeckoMediaPluginService> GetOrCreateOnMainThread() {
    107    MOZ_ASSERT(NS_IsMainThread());
    108 
    109    if (!sSingletonService) {
    110      if (XRE_IsParentProcess()) {
    111        RefPtr<GeckoMediaPluginServiceParent> service =
    112            new GeckoMediaPluginServiceParent();
    113        if (NS_WARN_IF(NS_FAILED(service->Init()))) {
    114          return nullptr;
    115        }
    116        sSingletonService = service;
    117 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
    118        // GMPProcessParent should only be instantiated in the parent
    119        // so initialization only needs to be done in the parent.
    120        GMPProcessParent::InitStaticMainThread();
    121 #endif
    122      } else {
    123        RefPtr<GeckoMediaPluginServiceChild> service =
    124            new GeckoMediaPluginServiceChild();
    125        if (NS_WARN_IF(NS_FAILED(service->Init()))) {
    126          return nullptr;
    127        }
    128        sSingletonService = service;
    129      }
    130      ClearOnShutdown(&sSingletonService);
    131    }
    132 
    133    RefPtr<GeckoMediaPluginService> service = sSingletonService.get();
    134    return service.forget();
    135  }
    136 
    137  NS_IMETHOD
    138  Run() override {
    139    MOZ_ASSERT(NS_IsMainThread());
    140 
    141    mService = GetOrCreateOnMainThread();
    142    return NS_OK;
    143  }
    144 };
    145 
    146 already_AddRefed<GeckoMediaPluginService>
    147 GeckoMediaPluginService::GetGeckoMediaPluginService() {
    148  return GMPServiceCreateHelper::GetOrCreate();
    149 }
    150 
    151 NS_IMPL_ISUPPORTS(GeckoMediaPluginService, mozIGeckoMediaPluginService,
    152                  nsIObserver)
    153 
    154 GeckoMediaPluginService::GeckoMediaPluginService()
    155    : mMutex("GeckoMediaPluginService::mMutex"),
    156      mMainThread(GetMainThreadSerialEventTarget()),
    157      mGMPThreadShutdown(false),
    158      mShuttingDownOnGMPThread(false),
    159      mXPCOMWillShutdown(false) {
    160  MOZ_ASSERT(NS_IsMainThread());
    161 
    162  nsCOMPtr<nsIXULAppInfo> appInfo =
    163      do_GetService("@mozilla.org/xre/app-info;1");
    164  if (appInfo) {
    165    nsAutoCString version;
    166    nsAutoCString buildID;
    167    if (NS_SUCCEEDED(appInfo->GetVersion(version)) &&
    168        NS_SUCCEEDED(appInfo->GetAppBuildID(buildID))) {
    169      GMP_LOG_DEBUG(
    170          "GeckoMediaPluginService created; Gecko version=%s buildID=%s",
    171          version.get(), buildID.get());
    172    }
    173  }
    174 }
    175 
    176 GeckoMediaPluginService::~GeckoMediaPluginService() = default;
    177 
    178 NS_IMETHODIMP
    179 GeckoMediaPluginService::RunPluginCrashCallbacks(
    180    uint32_t aPluginId, const nsACString& aPluginName) {
    181  MOZ_ASSERT(NS_IsMainThread());
    182  GMP_LOG_DEBUG("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId);
    183 
    184  mozilla::UniquePtr<nsTArray<RefPtr<GMPCrashHelper>>> helpers;
    185  {
    186    MutexAutoLock lock(mMutex);
    187    mPluginCrashHelpers.Remove(aPluginId, &helpers);
    188  }
    189  if (!helpers) {
    190    GMP_LOG_DEBUG("%s::%s(%i) No crash helpers, not handling crash.", __CLASS__,
    191                  __FUNCTION__, aPluginId);
    192    return NS_OK;
    193  }
    194 
    195  for (const auto& helper : *helpers) {
    196    nsCOMPtr<nsPIDOMWindowInner> window = helper->GetPluginCrashedEventTarget();
    197    if (NS_WARN_IF(!window)) {
    198      continue;
    199    }
    200    RefPtr<dom::Document> document(window->GetExtantDoc());
    201    if (NS_WARN_IF(!document)) {
    202      continue;
    203    }
    204 
    205    dom::PluginCrashedEventInit init;
    206    init.mPluginID = aPluginId;
    207    init.mBubbles = true;
    208    init.mCancelable = true;
    209    init.mGmpPlugin = true;
    210    CopyUTF8toUTF16(aPluginName, init.mPluginName);
    211    init.mSubmittedCrashReport = false;
    212    RefPtr<dom::PluginCrashedEvent> event =
    213        dom::PluginCrashedEvent::Constructor(document, u"PluginCrashed"_ns,
    214                                             init);
    215    event->SetTrusted(true);
    216    event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
    217 
    218    // MOZ_KnownLive due to bug 1506441
    219    EventDispatcher::DispatchDOMEvent(
    220        MOZ_KnownLive(nsGlobalWindowInner::Cast(window)), nullptr, event,
    221        nullptr, nullptr);
    222  }
    223 
    224  return NS_OK;
    225 }
    226 
    227 nsresult GeckoMediaPluginService::Init() {
    228  MOZ_ASSERT(NS_IsMainThread());
    229 
    230  nsCOMPtr<nsIObserverService> obsService =
    231      mozilla::services::GetObserverService();
    232  MOZ_ASSERT(obsService);
    233  MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(
    234      this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false));
    235 
    236  // Kick off scanning for plugins
    237  nsCOMPtr<nsIThread> thread;
    238  return GetThread(getter_AddRefs(thread));
    239 }
    240 
    241 RefPtr<GetCDMParentPromise> GeckoMediaPluginService::GetCDM(
    242    const NodeIdParts& aNodeIdParts, const nsACString& aKeySystem,
    243    GMPCrashHelper* aHelper) {
    244  AssertOnGMPThread();
    245 
    246  if (mShuttingDownOnGMPThread || aKeySystem.IsEmpty()) {
    247    nsPrintfCString reason(
    248        "%s::%s failed, aKeySystem.IsEmpty() = %d, mShuttingDownOnGMPThread = "
    249        "%d.",
    250        __CLASS__, __FUNCTION__, aKeySystem.IsEmpty(),
    251        mShuttingDownOnGMPThread);
    252    return GetCDMParentPromise::CreateAndReject(
    253        MediaResult(NS_ERROR_FAILURE, reason.get()), __func__);
    254  }
    255 
    256  typedef MozPromiseHolder<GetCDMParentPromise> PromiseHolder;
    257  PromiseHolder* rawHolder(new PromiseHolder());
    258  RefPtr<GetCDMParentPromise> promise = rawHolder->Ensure(__func__);
    259  nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
    260  RefPtr<GMPCrashHelper> helper(aHelper);
    261  nsTArray<nsCString> tags{nsCString{aKeySystem}};
    262  GetContentParent(aHelper, NodeIdVariant{aNodeIdParts},
    263                   nsLiteralCString(CHROMIUM_CDM_API), tags)
    264      ->Then(
    265          thread, __func__,
    266          [rawHolder, helper, keySystem = nsCString{aKeySystem}](
    267              const RefPtr<GMPContentParentCloseBlocker>& wrapper) {
    268            RefPtr<GMPContentParent> parent = wrapper->mParent;
    269            MOZ_ASSERT(
    270                parent,
    271                "Wrapper should wrap a valid parent if we're in this path.");
    272            UniquePtr<PromiseHolder> holder(rawHolder);
    273            RefPtr<ChromiumCDMParent> cdm = parent->GetChromiumCDM(keySystem);
    274            if (!cdm) {
    275              nsPrintfCString reason(
    276                  "%s::%s failed since GetChromiumCDM returns nullptr.",
    277                  __CLASS__, __FUNCTION__);
    278              holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()),
    279                             __func__);
    280              return;
    281            }
    282            if (helper) {
    283              cdm->SetCrashHelper(helper);
    284            }
    285            holder->Resolve(cdm, __func__);
    286          },
    287          [rawHolder](MediaResult result) {
    288            nsPrintfCString reason(
    289                "%s::%s failed since GetContentParent rejects the promise with "
    290                "reason %s.",
    291                __CLASS__, __FUNCTION__, result.Description().get());
    292            UniquePtr<PromiseHolder> holder(rawHolder);
    293            holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()),
    294                           __func__);
    295          });
    296 
    297  return promise;
    298 }
    299 
    300 #if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS)
    301 RefPtr<GetGMPContentParentPromise>
    302 GeckoMediaPluginService::GetContentParentForTest() {
    303  AssertOnGMPThread();
    304 
    305  nsTArray<nsCString> tags;
    306  tags.AppendElement("fake"_ns);
    307 
    308  const nsString origin1 = u"http://example1.com"_ns;
    309  const nsString origin2 = u"http://example2.org"_ns;
    310  const nsString gmpName = u"gmp-fake"_ns;
    311 
    312  NodeIdParts nodeIdParts = NodeIdParts{origin1, origin2, gmpName};
    313 
    314  if (mShuttingDownOnGMPThread) {
    315    nsPrintfCString reason("%s::%s failed, mShuttingDownOnGMPThread = %d.",
    316                           __CLASS__, __FUNCTION__, mShuttingDownOnGMPThread);
    317    return GetGMPContentParentPromise::CreateAndReject(
    318        MediaResult(NS_ERROR_FAILURE, reason.get()), __func__);
    319  }
    320 
    321  using PromiseHolder = MozPromiseHolder<GetGMPContentParentPromise>;
    322  PromiseHolder* rawHolder(new PromiseHolder());
    323  RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
    324  nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
    325  GetContentParent(nullptr, NodeIdVariant{nodeIdParts},
    326                   nsLiteralCString(CHROMIUM_CDM_API), tags)
    327      ->Then(
    328          thread, __func__,
    329          [rawHolder](const RefPtr<GMPContentParentCloseBlocker>& wrapper) {
    330            RefPtr<GMPContentParent> parent = wrapper->mParent;
    331            MOZ_ASSERT(
    332                parent,
    333                "Wrapper should wrap a valid parent if we're in this path.");
    334            UniquePtr<PromiseHolder> holder(rawHolder);
    335            if (!parent) {
    336              nsPrintfCString reason("%s::%s failed since no GMPContentParent.",
    337                                     __CLASS__, __FUNCTION__);
    338              holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()),
    339                             __func__);
    340              return;
    341            }
    342            holder->Resolve(wrapper, __func__);
    343          },
    344          [rawHolder](const MediaResult& result) {
    345            nsPrintfCString reason(
    346                "%s::%s failed since GetContentParent rejects the promise with "
    347                "reason %s.",
    348                __CLASS__, __FUNCTION__, result.Description().get());
    349            UniquePtr<PromiseHolder> holder(rawHolder);
    350            holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()),
    351                           __func__);
    352          });
    353 
    354  return promise;
    355 }
    356 #endif
    357 
    358 void GeckoMediaPluginService::ShutdownGMPThread() {
    359  GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__);
    360  nsCOMPtr<nsIThread> gmpThread;
    361  {
    362    MutexAutoLock lock(mMutex);
    363    mGMPThreadShutdown = true;
    364    mGMPThread.swap(gmpThread);
    365  }
    366 
    367  if (gmpThread) {
    368    gmpThread->Shutdown();
    369  }
    370 }
    371 
    372 nsresult GeckoMediaPluginService::GMPDispatch(
    373    nsIRunnable* event, nsIEventTarget::DispatchFlags flags) {
    374  return GMPDispatch(do_AddRef(event), flags);
    375 }
    376 
    377 nsresult GeckoMediaPluginService::GMPDispatch(
    378    already_AddRefed<nsIRunnable> event, nsIEventTarget::DispatchFlags flags) {
    379  // NOTE: This method always releases `event` on failure, rather than leaking
    380  // it, even if `NS_DISPATCH_FALLIBLE` is not specified.
    381  nsCOMPtr<nsIRunnable> r(event);
    382  nsCOMPtr<nsIThread> thread;
    383  nsresult rv = GetThread(getter_AddRefs(thread));
    384  if (NS_WARN_IF(NS_FAILED(rv))) {
    385    return rv;
    386  }
    387  return thread->Dispatch(r.forget(), flags | NS_DISPATCH_FALLIBLE);
    388 }
    389 
    390 // always call with getter_AddRefs, because it does
    391 NS_IMETHODIMP
    392 GeckoMediaPluginService::GetThread(nsIThread** aThread) {
    393  MOZ_ASSERT(aThread);
    394 
    395  // This can be called from any thread.
    396  MutexAutoLock lock(mMutex);
    397 
    398  return GetThreadLocked(aThread);
    399 }
    400 
    401 // always call with getter_AddRefs, because it does
    402 nsresult GeckoMediaPluginService::GetThreadLocked(nsIThread** aThread) {
    403  MOZ_ASSERT(aThread);
    404 
    405  mMutex.AssertCurrentThreadOwns();
    406 
    407  if (!mGMPThread) {
    408    // Don't allow the thread to be created after shutdown has started.
    409    if (mGMPThreadShutdown) {
    410      return NS_ERROR_FAILURE;
    411    }
    412 
    413    nsresult rv = NS_NewNamedThread("GMPThread", getter_AddRefs(mGMPThread));
    414    if (NS_WARN_IF(NS_FAILED(rv))) {
    415      return rv;
    416    }
    417 
    418    // Tell the thread to initialize plugins
    419    InitializePlugins(mGMPThread);
    420  }
    421 
    422  nsCOMPtr<nsIThread> copy = mGMPThread;
    423  copy.forget(aThread);
    424 
    425  return NS_OK;
    426 }
    427 
    428 already_AddRefed<nsISerialEventTarget> GeckoMediaPluginService::GetGMPThread() {
    429  nsCOMPtr<nsISerialEventTarget> thread;
    430  {
    431    MutexAutoLock lock(mMutex);
    432    thread = mGMPThread;
    433  }
    434  return thread.forget();
    435 }
    436 
    437 NS_IMETHODIMP
    438 GeckoMediaPluginService::GetGMPVideoDecoder(
    439    GMPCrashHelper* aHelper, nsTArray<nsCString>* aTags,
    440    const nsACString& aNodeId,
    441    UniquePtr<GetGMPVideoDecoderCallback>&& aCallback) {
    442  AssertOnGMPThread();
    443  NS_ENSURE_ARG(aTags && aTags->Length() > 0);
    444  NS_ENSURE_ARG(aCallback);
    445 
    446  if (mShuttingDownOnGMPThread) {
    447    return NS_ERROR_FAILURE;
    448  }
    449 
    450  GetGMPVideoDecoderCallback* rawCallback = aCallback.release();
    451  nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
    452  RefPtr<GMPCrashHelper> helper(aHelper);
    453  GetContentParent(aHelper, NodeIdVariant{nsCString(aNodeId)},
    454                   nsLiteralCString(GMP_API_VIDEO_DECODER), *aTags)
    455      ->Then(
    456          thread, __func__,
    457          [rawCallback,
    458           helper](const RefPtr<GMPContentParentCloseBlocker>& wrapper) {
    459            RefPtr<GMPContentParent> parent = wrapper->mParent;
    460            UniquePtr<GetGMPVideoDecoderCallback> callback(rawCallback);
    461            GMPVideoDecoderParent* actor = nullptr;
    462            GMPVideoHostImpl* host = nullptr;
    463            if (parent && NS_SUCCEEDED(parent->GetGMPVideoDecoder(&actor))) {
    464              host = &(actor->Host());
    465              actor->SetCrashHelper(helper);
    466            }
    467            callback->Done(actor, host);
    468          },
    469          [rawCallback] {
    470            UniquePtr<GetGMPVideoDecoderCallback> callback(rawCallback);
    471            callback->Done(nullptr, nullptr);
    472          });
    473 
    474  return NS_OK;
    475 }
    476 
    477 NS_IMETHODIMP
    478 GeckoMediaPluginService::GetGMPVideoEncoder(
    479    GMPCrashHelper* aHelper, nsTArray<nsCString>* aTags,
    480    const nsACString& aNodeId,
    481    UniquePtr<GetGMPVideoEncoderCallback>&& aCallback) {
    482  AssertOnGMPThread();
    483  NS_ENSURE_ARG(aTags && aTags->Length() > 0);
    484  NS_ENSURE_ARG(aCallback);
    485 
    486  if (mShuttingDownOnGMPThread) {
    487    return NS_ERROR_FAILURE;
    488  }
    489 
    490  GetGMPVideoEncoderCallback* rawCallback = aCallback.release();
    491  nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread());
    492  RefPtr<GMPCrashHelper> helper(aHelper);
    493  GetContentParent(aHelper, NodeIdVariant{nsCString(aNodeId)},
    494                   nsLiteralCString(GMP_API_VIDEO_ENCODER), *aTags)
    495      ->Then(
    496          thread, __func__,
    497          [rawCallback,
    498           helper](const RefPtr<GMPContentParentCloseBlocker>& wrapper) {
    499            RefPtr<GMPContentParent> parent = wrapper->mParent;
    500            UniquePtr<GetGMPVideoEncoderCallback> callback(rawCallback);
    501            GMPVideoEncoderParent* actor = nullptr;
    502            GMPVideoHostImpl* host = nullptr;
    503            if (parent && NS_SUCCEEDED(parent->GetGMPVideoEncoder(&actor))) {
    504              host = &(actor->Host());
    505              actor->SetCrashHelper(helper);
    506            }
    507            callback->Done(actor, host);
    508          },
    509          [rawCallback] {
    510            UniquePtr<GetGMPVideoEncoderCallback> callback(rawCallback);
    511            callback->Done(nullptr, nullptr);
    512          });
    513 
    514  return NS_OK;
    515 }
    516 
    517 void GeckoMediaPluginService::ConnectCrashHelper(uint32_t aPluginId,
    518                                                 GMPCrashHelper* aHelper) {
    519  if (!aHelper) {
    520    return;
    521  }
    522 
    523  MutexAutoLock lock(mMutex);
    524  mPluginCrashHelpers.WithEntryHandle(aPluginId, [&](auto&& entry) {
    525    if (!entry) {
    526      entry.Insert(MakeUnique<nsTArray<RefPtr<GMPCrashHelper>>>());
    527    } else if (entry.Data()->Contains(aHelper)) {
    528      return;
    529    }
    530    entry.Data()->AppendElement(aHelper);
    531  });
    532 }
    533 
    534 void GeckoMediaPluginService::DisconnectCrashHelper(GMPCrashHelper* aHelper) {
    535  if (!aHelper) {
    536    return;
    537  }
    538  MutexAutoLock lock(mMutex);
    539  for (auto iter = mPluginCrashHelpers.Iter(); !iter.Done(); iter.Next()) {
    540    nsTArray<RefPtr<GMPCrashHelper>>* helpers = iter.UserData();
    541    if (!helpers->Contains(aHelper)) {
    542      continue;
    543    }
    544    helpers->RemoveElement(aHelper);
    545    MOZ_ASSERT(!helpers->Contains(aHelper));  // Ensure there aren't duplicates.
    546    if (helpers->IsEmpty()) {
    547      iter.Remove();
    548    }
    549  }
    550 }
    551 
    552 }  // namespace gmp
    553 }  // namespace mozilla
    554 
    555 #undef __CLASS__