2017-10-28 02:10:06 +03:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
2016-06-11 05:27:24 +03:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* 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 "GPUChild.h"
|
2021-01-06 12:05:31 +03:00
|
|
|
|
2016-06-11 05:37:03 +03:00
|
|
|
#include "GPUProcessHost.h"
|
2016-11-06 22:01:52 +03:00
|
|
|
#include "GPUProcessManager.h"
|
2021-01-06 12:05:31 +03:00
|
|
|
#include "GfxInfoBase.h"
|
2018-08-07 08:47:18 +03:00
|
|
|
#include "VRProcessManager.h"
|
2021-01-06 12:05:31 +03:00
|
|
|
#include "gfxConfig.h"
|
|
|
|
#include "gfxPlatform.h"
|
2021-01-04 17:20:17 +03:00
|
|
|
#include "mozilla/Services.h"
|
2019-07-26 04:10:23 +03:00
|
|
|
#include "mozilla/StaticPrefs_dom.h"
|
2016-10-20 18:33:40 +03:00
|
|
|
#include "mozilla/Telemetry.h"
|
2017-02-15 18:25:40 +03:00
|
|
|
#include "mozilla/TelemetryIPC.h"
|
2016-09-22 08:38:44 +03:00
|
|
|
#include "mozilla/dom/CheckerboardReportService.h"
|
2021-01-06 12:05:31 +03:00
|
|
|
#include "mozilla/dom/ContentParent.h"
|
2017-01-27 03:35:54 +03:00
|
|
|
#include "mozilla/dom/MemoryReportRequest.h"
|
2021-01-06 12:05:31 +03:00
|
|
|
#include "mozilla/gfx/Logging.h"
|
2016-08-04 21:33:42 +03:00
|
|
|
#include "mozilla/gfx/gfxVars.h"
|
2016-08-21 06:59:11 +03:00
|
|
|
#if defined(XP_WIN)
|
2016-08-24 01:18:55 +03:00
|
|
|
# include "mozilla/gfx/DeviceManagerDx.h"
|
2016-08-21 06:59:11 +03:00
|
|
|
#endif
|
2021-01-06 12:05:31 +03:00
|
|
|
#include "mozilla/HangDetails.h"
|
|
|
|
#include "mozilla/RemoteDecoderManagerChild.h" // For RemoteDecodeIn
|
|
|
|
#include "mozilla/Unused.h"
|
2020-11-23 19:06:42 +03:00
|
|
|
#include "mozilla/ipc/Endpoint.h"
|
2018-03-16 23:28:19 +03:00
|
|
|
#include "mozilla/layers/APZInputBridgeChild.h"
|
2017-04-04 01:13:38 +03:00
|
|
|
#include "mozilla/layers/LayerTreeOwnerTracker.h"
|
2020-08-08 00:31:53 +03:00
|
|
|
#include "nsIGfxInfo.h"
|
2017-07-20 19:49:28 +03:00
|
|
|
#include "nsIObserverService.h"
|
2017-06-02 23:03:39 +03:00
|
|
|
|
|
|
|
#ifdef MOZ_GECKO_PROFILER
|
2017-05-30 22:06:14 +03:00
|
|
|
# include "ProfilerParent.h"
|
2017-06-02 23:03:39 +03:00
|
|
|
#endif
|
2016-06-11 05:27:24 +03:00
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace gfx {
|
|
|
|
|
2017-04-04 01:13:38 +03:00
|
|
|
using namespace layers;
|
|
|
|
|
2016-06-11 05:37:03 +03:00
|
|
|
GPUChild::GPUChild(GPUProcessHost* aHost) : mHost(aHost), mGPUReady(false) {
|
2016-06-11 05:27:24 +03:00
|
|
|
MOZ_COUNT_CTOR(GPUChild);
|
|
|
|
}
|
|
|
|
|
|
|
|
GPUChild::~GPUChild() { MOZ_COUNT_DTOR(GPUChild); }
|
|
|
|
|
2016-06-27 09:33:20 +03:00
|
|
|
void GPUChild::Init() {
|
2016-08-04 21:33:42 +03:00
|
|
|
nsTArray<GfxVarUpdate> updates = gfxVars::FetchNonDefaultVars();
|
2016-08-21 06:59:10 +03:00
|
|
|
|
|
|
|
DevicePrefs devicePrefs;
|
|
|
|
devicePrefs.hwCompositing() = gfxConfig::GetValue(Feature::HW_COMPOSITING);
|
|
|
|
devicePrefs.d3d11Compositing() =
|
|
|
|
gfxConfig::GetValue(Feature::D3D11_COMPOSITING);
|
|
|
|
devicePrefs.oglCompositing() =
|
|
|
|
gfxConfig::GetValue(Feature::OPENGL_COMPOSITING);
|
|
|
|
devicePrefs.useD2D1() = gfxConfig::GetValue(Feature::DIRECT2D);
|
2019-10-16 00:57:37 +03:00
|
|
|
devicePrefs.webGPU() = gfxConfig::GetValue(Feature::WEBGPU);
|
2020-07-22 01:56:52 +03:00
|
|
|
devicePrefs.d3d11HwAngle() = gfxConfig::GetValue(Feature::D3D11_HW_ANGLE);
|
2016-08-21 06:59:10 +03:00
|
|
|
|
2017-04-04 01:13:38 +03:00
|
|
|
nsTArray<LayerTreeIdMapping> mappings;
|
2018-03-25 02:06:01 +03:00
|
|
|
LayerTreeOwnerTracker::Get()->Iterate(
|
|
|
|
[&](LayersId aLayersId, base::ProcessId aProcessId) {
|
2017-04-04 01:13:38 +03:00
|
|
|
mappings.AppendElement(LayerTreeIdMapping(aLayersId, aProcessId));
|
|
|
|
});
|
|
|
|
|
2020-08-08 00:31:53 +03:00
|
|
|
nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
|
|
|
|
nsTArray<GfxInfoFeatureStatus> features;
|
|
|
|
if (gfxInfo) {
|
|
|
|
auto* gfxInfoRaw = static_cast<widget::GfxInfoBase*>(gfxInfo.get());
|
|
|
|
features = gfxInfoRaw->GetAllFeatures();
|
|
|
|
}
|
|
|
|
|
|
|
|
SendInit(updates, devicePrefs, mappings, features);
|
2016-08-04 21:33:42 +03:00
|
|
|
|
|
|
|
gfxVars::AddReceiver(this);
|
2017-05-23 05:45:35 +03:00
|
|
|
|
|
|
|
#ifdef MOZ_GECKO_PROFILER
|
2017-05-30 22:06:14 +03:00
|
|
|
Unused << SendInitProfiler(ProfilerParent::CreateForProcess(OtherPid()));
|
2017-05-23 05:45:35 +03:00
|
|
|
#endif
|
2016-08-04 21:33:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void GPUChild::OnVarChanged(const GfxVarUpdate& aVar) { SendUpdateVar(aVar); }
|
2016-06-27 09:33:20 +03:00
|
|
|
|
2016-08-21 06:59:11 +03:00
|
|
|
bool GPUChild::EnsureGPUReady() {
|
|
|
|
if (mGPUReady) {
|
2017-03-30 04:23:24 +03:00
|
|
|
return true;
|
2016-08-21 06:59:11 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
GPUDeviceData data;
|
2017-03-30 04:23:24 +03:00
|
|
|
if (!SendGetDeviceStatus(&data)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-08-21 06:59:11 +03:00
|
|
|
|
|
|
|
gfxPlatform::GetPlatform()->ImportGPUDeviceData(data);
|
2016-12-02 22:59:03 +03:00
|
|
|
Telemetry::AccumulateTimeDelta(Telemetry::GPU_PROCESS_LAUNCH_TIME_MS_2,
|
|
|
|
mHost->GetLaunchTime());
|
2016-08-21 06:59:11 +03:00
|
|
|
mGPUReady = true;
|
2017-03-30 04:23:24 +03:00
|
|
|
return true;
|
2016-08-21 06:59:11 +03:00
|
|
|
}
|
|
|
|
|
2018-08-07 08:47:18 +03:00
|
|
|
base::ProcessHandle GPUChild::GetChildProcessHandle() {
|
|
|
|
return mHost->GetChildProcessHandle();
|
|
|
|
}
|
|
|
|
|
2016-08-21 06:59:11 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvInitComplete(const GPUDeviceData& aData) {
|
|
|
|
// We synchronously requested GPU parameters before this arrived.
|
|
|
|
if (mGPUReady) {
|
2016-11-15 06:26:00 +03:00
|
|
|
return IPC_OK();
|
2016-08-21 06:59:11 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
gfxPlatform::GetPlatform()->ImportGPUDeviceData(aData);
|
2016-12-02 22:59:03 +03:00
|
|
|
Telemetry::AccumulateTimeDelta(Telemetry::GPU_PROCESS_LAUNCH_TIME_MS_2,
|
|
|
|
mHost->GetLaunchTime());
|
2016-08-21 06:59:11 +03:00
|
|
|
mGPUReady = true;
|
2016-11-15 06:26:00 +03:00
|
|
|
return IPC_OK();
|
2016-08-21 06:59:11 +03:00
|
|
|
}
|
|
|
|
|
2016-09-22 08:38:44 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvReportCheckerboard(
|
|
|
|
const uint32_t& aSeverity, const nsCString& aLog) {
|
|
|
|
layers::CheckerboardEventStorage::Report(aSeverity, std::string(aLog.get()));
|
2016-11-15 06:26:00 +03:00
|
|
|
return IPC_OK();
|
2016-09-22 08:38:44 +03:00
|
|
|
}
|
|
|
|
|
2016-10-01 00:31:06 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvGraphicsError(const nsCString& aError) {
|
|
|
|
gfx::LogForwarder* lf = gfx::Factory::GetLogForwarder();
|
|
|
|
if (lf) {
|
|
|
|
std::stringstream message;
|
|
|
|
message << "GP+" << aError.get();
|
|
|
|
lf->UpdateStringsVector(message.str());
|
|
|
|
}
|
2016-11-15 06:26:00 +03:00
|
|
|
return IPC_OK();
|
2016-10-01 00:31:06 +03:00
|
|
|
}
|
|
|
|
|
2018-08-07 08:47:18 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvCreateVRProcess() {
|
|
|
|
// Make sure create VR process at the main process
|
|
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
2019-07-22 05:10:14 +03:00
|
|
|
if (StaticPrefs::dom_vr_process_enabled_AtStartup()) {
|
2018-08-07 08:47:18 +03:00
|
|
|
VRProcessManager::Initialize();
|
|
|
|
VRProcessManager* vr = VRProcessManager::Get();
|
|
|
|
MOZ_ASSERT(vr, "VRProcessManager must be initialized first.");
|
|
|
|
|
|
|
|
if (vr) {
|
|
|
|
vr->LaunchVRProcess();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return IPC_OK();
|
|
|
|
}
|
|
|
|
|
2019-01-03 23:28:52 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvShutdownVRProcess() {
|
|
|
|
// Make sure stopping VR process at the main process
|
|
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
2019-07-22 05:10:14 +03:00
|
|
|
if (StaticPrefs::dom_vr_process_enabled_AtStartup()) {
|
2019-01-03 23:28:52 +03:00
|
|
|
VRProcessManager::Shutdown();
|
|
|
|
}
|
|
|
|
|
|
|
|
return IPC_OK();
|
|
|
|
}
|
|
|
|
|
2016-10-15 15:45:02 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvNotifyUiObservers(
|
|
|
|
const nsCString& aTopic) {
|
|
|
|
nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
|
|
|
|
MOZ_ASSERT(obsSvc);
|
|
|
|
if (obsSvc) {
|
|
|
|
obsSvc->NotifyObservers(nullptr, aTopic.get(), nullptr);
|
|
|
|
}
|
2016-11-15 06:26:00 +03:00
|
|
|
return IPC_OK();
|
2016-10-15 15:45:02 +03:00
|
|
|
}
|
|
|
|
|
2017-09-13 12:20:36 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvAccumulateChildHistograms(
|
2019-07-10 06:29:05 +03:00
|
|
|
nsTArray<HistogramAccumulation>&& aAccumulations) {
|
2017-05-23 09:47:58 +03:00
|
|
|
TelemetryIPC::AccumulateChildHistograms(Telemetry::ProcessID::Gpu,
|
|
|
|
aAccumulations);
|
2016-11-15 06:26:00 +03:00
|
|
|
return IPC_OK();
|
2016-10-31 08:35:57 +03:00
|
|
|
}
|
|
|
|
|
2017-09-13 12:20:36 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvAccumulateChildKeyedHistograms(
|
2019-07-10 06:29:05 +03:00
|
|
|
nsTArray<KeyedHistogramAccumulation>&& aAccumulations) {
|
2017-05-23 09:47:58 +03:00
|
|
|
TelemetryIPC::AccumulateChildKeyedHistograms(Telemetry::ProcessID::Gpu,
|
|
|
|
aAccumulations);
|
2016-11-15 06:26:00 +03:00
|
|
|
return IPC_OK();
|
2016-10-31 08:35:57 +03:00
|
|
|
}
|
|
|
|
|
2017-01-16 07:12:00 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvUpdateChildScalars(
|
2019-07-10 06:29:05 +03:00
|
|
|
nsTArray<ScalarAction>&& aScalarActions) {
|
2017-05-23 09:47:58 +03:00
|
|
|
TelemetryIPC::UpdateChildScalars(Telemetry::ProcessID::Gpu, aScalarActions);
|
2017-01-16 07:12:00 +03:00
|
|
|
return IPC_OK();
|
|
|
|
}
|
|
|
|
|
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvUpdateChildKeyedScalars(
|
2019-07-10 06:29:05 +03:00
|
|
|
nsTArray<KeyedScalarAction>&& aScalarActions) {
|
2017-05-23 09:47:58 +03:00
|
|
|
TelemetryIPC::UpdateChildKeyedScalars(Telemetry::ProcessID::Gpu,
|
|
|
|
aScalarActions);
|
2017-01-16 07:12:00 +03:00
|
|
|
return IPC_OK();
|
|
|
|
}
|
|
|
|
|
2017-02-14 16:43:51 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvRecordChildEvents(
|
|
|
|
nsTArray<mozilla::Telemetry::ChildEventData>&& aEvents) {
|
2017-05-23 09:47:58 +03:00
|
|
|
TelemetryIPC::RecordChildEvents(Telemetry::ProcessID::Gpu, aEvents);
|
2017-02-14 16:43:51 +03:00
|
|
|
return IPC_OK();
|
|
|
|
}
|
|
|
|
|
2017-06-09 16:53:42 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvRecordDiscardedData(
|
|
|
|
const mozilla::Telemetry::DiscardedData& aDiscardedData) {
|
|
|
|
TelemetryIPC::RecordDiscardedData(Telemetry::ProcessID::Gpu, aDiscardedData);
|
|
|
|
return IPC_OK();
|
|
|
|
}
|
|
|
|
|
2017-04-05 10:17:36 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvNotifyDeviceReset(
|
|
|
|
const GPUDeviceData& aData) {
|
|
|
|
gfxPlatform::GetPlatform()->ImportGPUDeviceData(aData);
|
2017-06-15 21:34:00 +03:00
|
|
|
mHost->mListener->OnRemoteProcessDeviceReset(mHost);
|
2016-11-15 06:26:00 +03:00
|
|
|
return IPC_OK();
|
2016-11-06 22:01:52 +03:00
|
|
|
}
|
|
|
|
|
2017-01-27 03:35:54 +03:00
|
|
|
bool GPUChild::SendRequestMemoryReport(const uint32_t& aGeneration,
|
|
|
|
const bool& aAnonymize,
|
|
|
|
const bool& aMinimizeMemoryUsage,
|
2019-03-01 00:20:40 +03:00
|
|
|
const Maybe<FileDescriptor>& aDMDFile) {
|
2017-01-27 03:35:54 +03:00
|
|
|
mMemoryReportRequest = MakeUnique<MemoryReportRequestHost>(aGeneration);
|
2020-08-03 21:29:45 +03:00
|
|
|
|
|
|
|
PGPUChild::SendRequestMemoryReport(
|
|
|
|
aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile,
|
|
|
|
[&](const uint32_t& aGeneration2) {
|
|
|
|
if (GPUProcessManager* gpm = GPUProcessManager::Get()) {
|
|
|
|
if (GPUChild* child = gpm->GetGPUChild()) {
|
|
|
|
if (child->mMemoryReportRequest) {
|
|
|
|
child->mMemoryReportRequest->Finish(aGeneration2);
|
|
|
|
child->mMemoryReportRequest = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[&](mozilla::ipc::ResponseRejectReason) {
|
|
|
|
if (GPUProcessManager* gpm = GPUProcessManager::Get()) {
|
|
|
|
if (GPUChild* child = gpm->GetGPUChild()) {
|
|
|
|
child->mMemoryReportRequest = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-01-27 03:35:54 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvAddMemoryReport(
|
|
|
|
const MemoryReport& aReport) {
|
|
|
|
if (mMemoryReportRequest) {
|
|
|
|
mMemoryReportRequest->RecvReport(aReport);
|
|
|
|
}
|
|
|
|
return IPC_OK();
|
|
|
|
}
|
|
|
|
|
2016-06-11 05:37:03 +03:00
|
|
|
void GPUChild::ActorDestroy(ActorDestroyReason aWhy) {
|
2016-10-12 00:25:41 +03:00
|
|
|
if (aWhy == AbnormalShutdown) {
|
2019-08-15 15:06:51 +03:00
|
|
|
GenerateCrashReport(OtherPid());
|
2017-01-27 03:35:55 +03:00
|
|
|
|
2016-10-20 18:33:40 +03:00
|
|
|
Telemetry::Accumulate(
|
|
|
|
Telemetry::SUBPROCESS_ABNORMAL_ABORT,
|
2019-11-26 01:45:31 +03:00
|
|
|
nsDependentCString(XRE_GeckoProcessTypeToString(GeckoProcessType_GPU)),
|
2017-01-27 03:35:55 +03:00
|
|
|
1);
|
2016-10-27 18:04:50 +03:00
|
|
|
|
|
|
|
// Notify the Telemetry environment so that we can refresh and do a
|
|
|
|
// subsession split
|
|
|
|
if (nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService()) {
|
|
|
|
obsvc->NotifyObservers(nullptr, "compositor:process-aborted", nullptr);
|
|
|
|
}
|
2016-10-12 00:25:41 +03:00
|
|
|
}
|
|
|
|
|
2016-08-04 21:33:42 +03:00
|
|
|
gfxVars::RemoveReceiver(this);
|
2016-06-11 05:37:03 +03:00
|
|
|
mHost->OnChannelClosed();
|
|
|
|
}
|
|
|
|
|
2017-06-24 00:23:12 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvUpdateFeature(
|
|
|
|
const Feature& aFeature, const FeatureFailure& aChange) {
|
|
|
|
gfxConfig::SetFailed(aFeature, aChange.status(), aChange.message().get(),
|
|
|
|
aChange.failureId());
|
|
|
|
return IPC_OK();
|
|
|
|
}
|
|
|
|
|
2017-07-11 10:13:26 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvUsedFallback(const Fallback& aFallback,
|
|
|
|
const nsCString& aMessage) {
|
|
|
|
gfxConfig::EnableFallback(aFallback, aMessage.get());
|
|
|
|
return IPC_OK();
|
|
|
|
}
|
|
|
|
|
2017-07-20 19:49:28 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvBHRThreadHang(
|
|
|
|
const HangDetails& aDetails) {
|
|
|
|
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
|
|
|
if (obs) {
|
|
|
|
// Copy the HangDetails recieved over the network into a nsIHangDetails, and
|
|
|
|
// then fire our own observer notification.
|
|
|
|
// XXX: We should be able to avoid this potentially expensive copy here by
|
|
|
|
// moving our deserialized argument.
|
|
|
|
nsCOMPtr<nsIHangDetails> hangDetails =
|
2019-11-22 01:41:00 +03:00
|
|
|
new nsHangDetails(HangDetails(aDetails), PersistedToDisk::No);
|
2017-07-20 19:49:28 +03:00
|
|
|
obs->NotifyObservers(hangDetails, "bhr-thread-hang", nullptr);
|
|
|
|
}
|
|
|
|
return IPC_OK();
|
|
|
|
}
|
|
|
|
|
2021-01-06 12:05:31 +03:00
|
|
|
mozilla::ipc::IPCResult GPUChild::RecvUpdateMediaCodecsSupported(
|
|
|
|
const PDMFactory::MediaCodecsSupported& aSupported) {
|
|
|
|
dom::ContentParent::BroadcastMediaCodecsSupportedUpdate(
|
|
|
|
RemoteDecodeIn::GpuProcess, aSupported);
|
|
|
|
return IPC_OK();
|
|
|
|
}
|
|
|
|
|
2016-06-11 05:37:03 +03:00
|
|
|
class DeferredDeleteGPUChild : public Runnable {
|
|
|
|
public:
|
|
|
|
explicit DeferredDeleteGPUChild(UniquePtr<GPUChild>&& aChild)
|
2017-06-12 22:34:10 +03:00
|
|
|
: Runnable("gfx::DeferredDeleteGPUChild"), mChild(std::move(aChild)) {}
|
2016-06-11 05:37:03 +03:00
|
|
|
|
|
|
|
NS_IMETHODIMP Run() override { return NS_OK; }
|
|
|
|
|
|
|
|
private:
|
|
|
|
UniquePtr<GPUChild> mChild;
|
|
|
|
};
|
|
|
|
|
2019-02-26 01:07:19 +03:00
|
|
|
/* static */
|
|
|
|
void GPUChild::Destroy(UniquePtr<GPUChild>&& aChild) {
|
2018-05-30 22:15:35 +03:00
|
|
|
NS_DispatchToMainThread(new DeferredDeleteGPUChild(std::move(aChild)));
|
2016-06-11 05:37:03 +03:00
|
|
|
}
|
|
|
|
|
2016-06-11 05:27:24 +03:00
|
|
|
} // namespace gfx
|
|
|
|
} // namespace mozilla
|