MozReview-Commit-ID: 3cGydlfeaPN
This commit is contained in:
Wes Kocher 2017-01-18 14:17:10 -08:00
Родитель 77e565ca6f 5baf0e453e
Коммит 54c460dbd6
98 изменённых файлов: 2640 добавлений и 3786 удалений

Просмотреть файл

@ -802,7 +802,7 @@ bin/libfreebl_32int64_3.so
; media
@RESPATH@/gmp-clearkey/0.1/@DLL_PREFIX@clearkey@DLL_SUFFIX@
@RESPATH@/gmp-clearkey/0.1/clearkey.info
@RESPATH@/gmp-clearkey/0.1/manifest.json
#ifdef PKG_LOCALE_MANIFEST
#include @PKG_LOCALE_MANIFEST@

Просмотреть файл

@ -797,7 +797,7 @@ bin/libfreebl_32int64_3.so
; media
@RESPATH@/gmp-clearkey/0.1/@DLL_PREFIX@clearkey@DLL_SUFFIX@
@RESPATH@/gmp-clearkey/0.1/clearkey.info
@RESPATH@/gmp-clearkey/0.1/manifest.json
; gfx
#ifdef XP_WIN

Просмотреть файл

@ -169,7 +169,10 @@ nsPrincipal::GetOriginInternal(nsACString& aOrigin)
nsCOMPtr<nsIURIWithPrincipal> uriWithPrincipal = do_QueryInterface(origin);
if (uriWithPrincipal) {
nsCOMPtr<nsIPrincipal> uriPrincipal;
if (uriWithPrincipal) {
rv = uriWithPrincipal->GetPrincipal(getter_AddRefs(uriPrincipal));
NS_ENSURE_SUCCESS(rv, rv);
if (uriPrincipal) {
return uriPrincipal->GetOriginNoSuffix(aOrigin);
}
}

Просмотреть файл

@ -268,6 +268,9 @@ function MdnDocsWidget(tooltipContainer) {
// get the localized string for the link text
this.elements.linkToMdn.textContent = L10N.getStr("docsTooltip.visitMDN");
// force using LTR because we use the en-US version of MDN
tooltipContainer.setAttribute("dir", "ltr");
// listen for clicks and open in the browser window instead
let mainWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
this.elements.linkToMdn.addEventListener("click", (e) => {

Просмотреть файл

@ -692,8 +692,7 @@ ReadDirectoryInternal(JSStructuredCloneReader* aReader,
}
nsCOMPtr<nsIFile> file;
nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
getter_AddRefs(file));
nsresult rv = NS_NewLocalFile(path, true, getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}

Просмотреть файл

@ -374,8 +374,8 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange)
// So, we shouldn't skip the empty node if the start offset is 0.
// In other words, if the offset is 1, the node should be ignored.
if (!startIsData && startIndx) {
mFirst = NextNode(startNode);
NS_WARNING_ASSERTION(mFirst, "NextNode returned null");
mFirst = GetNextSibling(startNode);
NS_WARNING_ASSERTION(mFirst, "GetNextSibling returned null");
// Does mFirst node really intersect the range? The range could be
// 'degenerate', i.e., not collapsed but still contain no content.
@ -430,8 +430,8 @@ nsContentIterator::Init(nsIDOMRange* aDOMRange)
// the last element should be the previous node (i.e., shouldn't
// include the end node in the range).
if (!endIsData && !endNode->HasChildren() && !endIndx) {
mLast = PrevNode(endNode);
NS_WARNING_ASSERTION(mLast, "PrevNode returned null");
mLast = GetPrevSibling(endNode);
NS_WARNING_ASSERTION(mLast, "GetPrevSibling returned null");
if (NS_WARN_IF(!NodeIsInTraversalRange(mLast, mPre,
startNode, startIndx,
endNode, endIndx))) {

Просмотреть файл

@ -4580,8 +4580,16 @@ nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject)
}
}
// BlockOnload() might be called before mScriptGlobalObject is set.
// We may need to add the blocker once mScriptGlobalObject is set.
bool needOnloadBlocker = !mScriptGlobalObject && aScriptGlobalObject;
mScriptGlobalObject = aScriptGlobalObject;
if (needOnloadBlocker) {
EnsureOnloadBlocker();
}
if (aScriptGlobalObject) {
// Go back to using the docshell for the layout history state
mLayoutHistoryState = nullptr;

48
dom/cache/ReadStream.cpp поставляемый
Просмотреть файл

@ -99,8 +99,6 @@ private:
StreamControl* mControl;
const nsID mId;
nsCOMPtr<nsIInputStream> mStream;
nsCOMPtr<nsIInputStream> mSnappyStream;
nsCOMPtr<nsIThread> mOwningThread;
enum State
@ -112,6 +110,15 @@ private:
Atomic<State> mState;
Atomic<bool> mHasEverBeenRead;
// The wrapped stream objects may not be threadsafe. We need to be able
// to close a stream on our owning thread while an IO thread is simultaneously
// reading the same stream. Therefore, protect all access to these stream
// objects with a mutex.
Mutex mMutex;
nsCOMPtr<nsIInputStream> mStream;
nsCOMPtr<nsIInputStream> mSnappyStream;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::ReadStream::Inner, override)
};
@ -190,10 +197,12 @@ ReadStream::Inner::Inner(StreamControl* aControl, const nsID& aId,
nsIInputStream* aStream)
: mControl(aControl)
, mId(aId)
, mStream(aStream)
, mSnappyStream(new SnappyUncompressInputStream(aStream))
, mOwningThread(NS_GetCurrentThread())
, mState(Open)
, mHasEverBeenRead(false)
, mMutex("dom::cache::ReadStream")
, mStream(aStream)
, mSnappyStream(new SnappyUncompressInputStream(aStream))
{
MOZ_DIAGNOSTIC_ASSERT(mStream);
MOZ_DIAGNOSTIC_ASSERT(mControl);
@ -228,7 +237,11 @@ ReadStream::Inner::Serialize(CacheReadStream* aReadStreamOut,
aReadStreamOut->id() = mId;
mControl->SerializeControl(aReadStreamOut);
{
MutexAutoLock lock(mMutex);
mControl->SerializeStream(aReadStreamOut, mStream, aStreamCleanupList);
}
MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut->stream().type() ==
IPCStream::TInputStreamParamsWithFds);
@ -270,7 +283,11 @@ nsresult
ReadStream::Inner::Close()
{
// stream ops can happen on any thread
nsresult rv = mStream->Close();
nsresult rv = NS_OK;
{
MutexAutoLock lock(mMutex);
rv = mSnappyStream->Close();
}
NoteClosed();
return rv;
}
@ -279,7 +296,11 @@ nsresult
ReadStream::Inner::Available(uint64_t* aNumAvailableOut)
{
// stream ops can happen on any thread
nsresult rv = mSnappyStream->Available(aNumAvailableOut);
nsresult rv = NS_OK;
{
MutexAutoLock lock(mMutex);
rv = mSnappyStream->Available(aNumAvailableOut);
}
if (NS_FAILED(rv)) {
Close();
@ -294,7 +315,11 @@ ReadStream::Inner::Read(char* aBuf, uint32_t aCount, uint32_t* aNumReadOut)
// stream ops can happen on any thread
MOZ_DIAGNOSTIC_ASSERT(aNumReadOut);
nsresult rv = mSnappyStream->Read(aBuf, aCount, aNumReadOut);
nsresult rv = NS_OK;
{
MutexAutoLock lock(mMutex);
rv = mSnappyStream->Read(aBuf, aCount, aNumReadOut);
}
if ((NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) ||
*aNumReadOut == 0) {
@ -317,8 +342,12 @@ ReadStream::Inner::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
mHasEverBeenRead = true;
}
nsresult rv = mSnappyStream->ReadSegments(aWriter, aClosure, aCount,
aNumReadOut);
nsresult rv = NS_OK;
{
MutexAutoLock lock(mMutex);
rv = mSnappyStream->ReadSegments(aWriter, aClosure, aCount, aNumReadOut);
}
if ((NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK &&
rv != NS_ERROR_NOT_IMPLEMENTED) || *aNumReadOut == 0) {
@ -340,6 +369,7 @@ nsresult
ReadStream::Inner::IsNonBlocking(bool* aNonBlockingOut)
{
// stream ops can happen on any thread
MutexAutoLock lock(mMutex);
return mSnappyStream->IsNonBlocking(aNonBlockingOut);
}

Просмотреть файл

@ -338,8 +338,9 @@ class HTMLInputElementState final : public nsISupports
MOZ_ASSERT(mBlobImplsOrDirectoryPaths[i].mType == BlobImplOrDirectoryPath::eDirectoryPath);
nsCOMPtr<nsIFile> file;
NS_ConvertUTF16toUTF8 path(mBlobImplsOrDirectoryPaths[i].mDirectoryPath);
nsresult rv = NS_NewNativeLocalFile(path, true, getter_AddRefs(file));
nsresult rv =
NS_NewLocalFile(mBlobImplsOrDirectoryPaths[i].mDirectoryPath,
true, getter_AddRefs(file));
if (NS_WARN_IF(NS_FAILED(rv))) {
continue;
}
@ -486,8 +487,7 @@ LastUsedDirectory(const OwningFileOrDirectory& aData)
}
nsCOMPtr<nsIFile> localFile;
nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
getter_AddRefs(localFile));
nsresult rv = NS_NewLocalFile(path, true, getter_AddRefs(localFile));
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
@ -2693,8 +2693,7 @@ HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
ErrorResult& aRv)
{
nsCOMPtr<nsIFile> file;
NS_ConvertUTF16toUTF8 path(aDirectoryPath);
aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(file));
aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file));
if (NS_WARN_IF(aRv.Failed())) {
return;
}

Просмотреть файл

@ -196,6 +196,7 @@
#include "mozilla/widget/PuppetBidiKeyboard.h"
#include "mozilla/RemoteSpellCheckEngineChild.h"
#include "GMPServiceChild.h"
#include "GfxInfoBase.h"
#include "gfxPlatform.h"
#include "nscore.h" // for NS_FREE_PERMANENT_DATA
#include "VRManagerChild.h"
@ -611,6 +612,10 @@ ContentChild::Init(MessageLoop* aIOLoop,
SetProcessName(NS_LITERAL_STRING("Web Content"), true);
nsTArray<mozilla::dom::GfxInfoFeatureStatus> featureStatus;
SendGetGfxInfoFeatureStatus(&featureStatus);
GfxInfoBase::SetFeatureStatus(featureStatus);
return true;
}

Просмотреть файл

@ -3763,18 +3763,21 @@ ContentParent::RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
}
mozilla::ipc::IPCResult
ContentParent::RecvGetGraphicsFeatureStatus(const int32_t& aFeature,
int32_t* aStatus,
nsCString* aFailureId,
bool* aSuccess)
ContentParent::RecvGetGfxInfoFeatureStatus(nsTArray<mozilla::dom::GfxInfoFeatureStatus>* aFS)
{
nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
if (!gfxInfo) {
*aSuccess = false;
return IPC_OK();
}
*aSuccess = NS_SUCCEEDED(gfxInfo->GetFeatureStatus(aFeature, *aFailureId, aStatus));
for (int32_t i = 1; i <= nsIGfxInfo::FEATURE_MAX_VALUE; ++i) {
int32_t status = 0;
nsAutoCString failureId;
gfxInfo->GetFeatureStatus(i, failureId, &status);
mozilla::dom::GfxInfoFeatureStatus fs(i, status, failureId);
aFS->AppendElement(Move(fs));
}
return IPC_OK();
}

Просмотреть файл

@ -434,6 +434,8 @@ public:
const bool& aIsAudio,
const bool& aIsVideo) override;
virtual mozilla::ipc::IPCResult RecvGetGfxInfoFeatureStatus(nsTArray<mozilla::dom::GfxInfoFeatureStatus>* aFS) override;
bool CycleCollectWithLogs(bool aDumpAllTraces,
nsICycleCollectorLogSink* aSink,
nsIDumpGCAndCCLogsCallback* aCallback);
@ -992,11 +994,6 @@ private:
virtual mozilla::ipc::IPCResult RecvDeallocateLayerTreeId(const uint64_t& aId) override;
virtual mozilla::ipc::IPCResult RecvGetGraphicsFeatureStatus(const int32_t& aFeature,
int32_t* aStatus,
nsCString* aFailureId,
bool* aSuccess) override;
virtual mozilla::ipc::IPCResult RecvGraphicsError(const nsCString& aError) override;
virtual mozilla::ipc::IPCResult

Просмотреть файл

@ -345,6 +345,13 @@ struct GMPCapabilityData
GMPAPITags[] capabilities;
};
struct GfxInfoFeatureStatus
{
int32_t feature;
int32_t status;
nsCString failureId;
};
/**
* The PContent protocol is a top-level protocol between the UI process
* and a content process. There is exactly one PContentParent/PContentChild pair
@ -934,8 +941,7 @@ parent:
bool isAudio,
bool isVideo);
sync GetGraphicsFeatureStatus(int32_t aFeature) returns (int32_t aStatus, nsCString aFailureCode,
bool aSuccess);
sync GetGfxInfoFeatureStatus() returns (GfxInfoFeatureStatus[] features);
// Graphics errors
async GraphicsError(nsCString aError);

Просмотреть файл

@ -298,12 +298,9 @@ GMPChild::RecvPreloadLibs(const nsCString& aLibs)
// loaded after the sandbox has started
// Items in this must be lowercase!
static const char *const whitelist[] = {
"d3d9.dll", // Create an `IDirect3D9` to get adapter information
"dxva2.dll", // Get monitor information
"evr.dll", // MFGetStrideForBitmapInfoHeader
"mfplat.dll", // MFCreateSample, MFCreateAlignedMemoryBuffer, MFCreateMediaType
"msauddecmft.dll", // AAC decoder (on Windows 8)
"msmpeg2adec.dll", // AAC decoder (on Windows 7)
"msmpeg2vdec.dll", // H.264 decoder
};

Просмотреть файл

@ -943,22 +943,54 @@ GMPParent::ParseChromiumManifest(const nsAString& aJSON)
mDescription = NS_ConvertUTF16toUTF8(m.mDescription);
mVersion = NS_ConvertUTF16toUTF8(m.mVersion);
nsCString kEMEKeySystem;
// We hard code a few of the settings because they can't be stored in the
// widevine manifest without making our API different to widevine's.
if (mDisplayName.EqualsASCII("clearkey")) {
kEMEKeySystem = kEMEKeySystemClearkey;
#if XP_WIN
mLibs = NS_LITERAL_CSTRING("dxva2.dll, msmpeg2vdec.dll, evr.dll, mfh264dec.dll, mfplat.dll");
#endif
} else if (mDisplayName.EqualsASCII("WidevineCdm")) {
kEMEKeySystem = kEMEKeySystemWidevine;
#if XP_WIN
mLibs = NS_LITERAL_CSTRING("dxva2.dll");
#endif
} else {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
GMPCapability video(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER));
video.mAPITags.AppendElement(NS_LITERAL_CSTRING("h264"));
video.mAPITags.AppendElement(NS_LITERAL_CSTRING("vp8"));
video.mAPITags.AppendElement(NS_LITERAL_CSTRING("vp9"));
video.mAPITags.AppendElement(kEMEKeySystemWidevine);
nsCString codecsString = NS_ConvertUTF16toUTF8(m.mX_cdm_codecs);
nsTArray<nsCString> codecs;
SplitAt(",", codecsString, codecs);
for (const nsCString& chromiumCodec : codecs) {
nsCString codec;
if (chromiumCodec.EqualsASCII("vp8")) {
codec = NS_LITERAL_CSTRING("vp8");
} else if (chromiumCodec.EqualsASCII("vp9.0")) {
codec = NS_LITERAL_CSTRING("vp9");
} else if (chromiumCodec.EqualsASCII("avc1")) {
codec = NS_LITERAL_CSTRING("h264");
} else {
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
video.mAPITags.AppendElement(codec);
}
video.mAPITags.AppendElement(kEMEKeySystem);
mCapabilities.AppendElement(Move(video));
GMPCapability decrypt(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
decrypt.mAPITags.AppendElement(kEMEKeySystemWidevine);
decrypt.mAPITags.AppendElement(kEMEKeySystem);
mCapabilities.AppendElement(Move(decrypt));
MOZ_ASSERT(mName.EqualsLiteral("widevinecdm"));
mAdapter = NS_LITERAL_STRING("widevine");
#ifdef XP_WIN
mLibs = NS_LITERAL_CSTRING("dxva2.dll");
#endif
return GenericPromise::CreateAndResolve(true, __func__);
}

Просмотреть файл

@ -388,8 +388,12 @@ GMPVideoDecoderParent::RecvDrainComplete()
msg.AppendLiteral("GMPVideoDecoderParent::RecvDrainComplete() outstanding frames=");
msg.AppendInt(mFrameCount);
LogToBrowserConsole(msg);
if (!mCallback) {
return IPC_FAIL_NO_REASON(this);
// We anticipate shutting down in the middle of a drain in the
// `UnblockResetAndDrain` method, which is called when we shutdown, so
// everything is sunny.
return IPC_OK();
}
if (!mIsAwaitingDrainComplete) {
@ -411,7 +415,10 @@ GMPVideoDecoderParent::RecvResetComplete()
CancelResetCompleteTimeout();
if (!mCallback) {
return IPC_FAIL_NO_REASON(this);
// We anticipate shutting down in the middle of a reset in the
// `UnblockResetAndDrain` method, which is called when we shutdown, so
// everything is good if we reach here.
return IPC_OK();
}
if (!mIsAwaitingResetComplete) {

Просмотреть файл

@ -64,6 +64,7 @@ EXPORTS += [
'GMPVideoHost.h',
'GMPVideoi420FrameImpl.h',
'GMPVideoPlaneImpl.h',
'widevine-adapter/content_decryption_module.h',
]
# We link GMPLoader into xul on Android and Linux as its code does not
@ -108,7 +109,7 @@ UNIFIED_SOURCES += [
'GMPVideoEncoderParent.cpp',
'GMPVideoHost.cpp',
'GMPVideoi420FrameImpl.cpp',
'GMPVideoPlaneImpl.cpp',
'GMPVideoPlaneImpl.cpp'
]
DIRS += [

Просмотреть файл

@ -7,6 +7,7 @@
#include "content_decryption_module.h"
#include "VideoUtils.h"
#include "WidevineDecryptor.h"
#include "WidevineDummyDecoder.h"
#include "WidevineUtils.h"
#include "WidevineVideoDecoder.h"
#include "gmp-api/gmp-entrypoints.h"
@ -89,7 +90,7 @@ WidevineAdapter::GMPGetAPI(const char* aAPIName,
uint32_t aDecryptorId)
{
Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p",
aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId);
aAPIName, aHostAPI, aPluginAPI, aDecryptorId, this);
if (!strcmp(aAPIName, GMP_API_DECRYPTOR)) {
if (WidevineDecryptor::GetInstance(aDecryptorId)) {
// We only support one CDM instance per PGMPDecryptor. Fail!
@ -100,7 +101,7 @@ WidevineAdapter::GMPGetAPI(const char* aAPIName,
PR_FindFunctionSymbol(mLib, "CreateCdmInstance"));
if (!create) {
Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p FAILED to find CreateCdmInstance",
aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId);
aAPIName, aHostAPI, aPluginAPI, aDecryptorId, this);
return GMPGenericErr;
}
@ -114,7 +115,7 @@ WidevineAdapter::GMPGetAPI(const char* aAPIName,
decryptor));
if (!cdm) {
Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p FAILED to create cdm",
aAPIName, aHostAPI, aPluginAPI, this, aDecryptorId);
aAPIName, aHostAPI, aPluginAPI, aDecryptorId, this);
return GMPGenericErr;
}
Log("cdm: 0x%x", cdm);
@ -124,14 +125,20 @@ WidevineAdapter::GMPGetAPI(const char* aAPIName,
} else if (!strcmp(aAPIName, GMP_API_VIDEO_DECODER)) {
RefPtr<CDMWrapper> wrapper = WidevineDecryptor::GetInstance(aDecryptorId);
// There is a possible race condition, where the decryptor will be destroyed
// before we are able to create the video decoder, so we create a dummy
// decoder to avoid crashing.
if (!wrapper) {
Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p No cdm for video decoder",
aAPIName, aHostAPI, aPluginAPI, thiss, aDecryptorId);
return GMPGenericErr;
}
Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p No cdm for video decoder. Using a DummyDecoder",
aAPIName, aHostAPI, aPluginAPI, aDecryptorId, this);
*aPluginAPI = new WidevineDummyDecoder();
} else {
*aPluginAPI = new WidevineVideoDecoder(static_cast<GMPVideoHost*>(aHostAPI),
wrapper);
}
}
return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
}

Просмотреть файл

@ -33,13 +33,13 @@ WidevineDecryptor::GetInstance(uint32_t aInstanceId)
WidevineDecryptor::WidevineDecryptor()
: mCallback(nullptr)
{
Log("WidevineDecryptor created this=%p", this);
Log("WidevineDecryptor created this=%p, instanceId=%u", this, mInstanceId);
AddRef(); // Released in DecryptingComplete().
}
WidevineDecryptor::~WidevineDecryptor()
{
Log("WidevineDecryptor destroyed this=%p", this);
Log("WidevineDecryptor destroyed this=%p, instanceId=%u", this, mInstanceId);
}
void
@ -224,7 +224,7 @@ WidevineDecryptor::Decrypt(GMPBuffer* aBuffer,
void
WidevineDecryptor::DecryptingComplete()
{
Log("WidevineDecryptor::DecryptingComplete() this=%p", this);
Log("WidevineDecryptor::DecryptingComplete() this=%p, instanceId=%u", this, mInstanceId);
// Drop our references to the CDMWrapper. When any other references
// held elsewhere are dropped (for example references held by a
// WidevineVideoDecoder, or a runnable), the CDMWrapper destroys
@ -313,6 +313,17 @@ WidevineDecryptor::OnResolveNewSessionPromise(uint32_t aPromiseId,
Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d) FAIL; !mCallback", aPromiseId);
return;
}
// This is laid out in the API. If we fail to load a session we should
// call OnResolveNewSessionPromise with nullptr as the sessionId.
// We can safely assume this means that we have failed to load a session
// as the other methods specify calling 'OnRejectPromise' when they fail.
if (!aSessionId) {
Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d) Failed to load session", aPromiseId);
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
return;
}
Log("Decryptor::OnResolveNewSessionPromise(aPromiseId=0x%d)", aPromiseId);
auto iter = mPromiseIdToNewSessionTokens.find(aPromiseId);
if (iter == mPromiseIdToNewSessionTokens.end()) {

Просмотреть файл

@ -0,0 +1,57 @@
#include "WidevineDummyDecoder.h"
#include "WidevineUtils.h"
using namespace cdm;
namespace mozilla {
WidevineDummyDecoder::WidevineDummyDecoder()
{
Log("WidevineDummyDecoder created");
}
void WidevineDummyDecoder::InitDecode(const GMPVideoCodec & aCodecSettings,
const uint8_t * aCodecSpecific,
uint32_t aCodecSpecificLength,
GMPVideoDecoderCallback * aCallback,
int32_t aCoreCount)
{
Log("WidevineDummyDecoder::InitDecode");
mCallback = aCallback;
mCallback->Error(GMPErr::GMPNotImplementedErr);
}
void WidevineDummyDecoder::Decode(GMPVideoEncodedFrame * aInputFrame,
bool aMissingFrames,
const uint8_t * aCodecSpecificInfo,
uint32_t aCodecSpecificInfoLength,
int64_t aRenderTimeMs)
{
Log("WidevineDummyDecoder::Decode");
mCallback->Error(GMPErr::GMPNotImplementedErr);
}
void WidevineDummyDecoder::Reset()
{
Log("WidevineDummyDecoder::Reset");
mCallback->Error(GMPErr::GMPNotImplementedErr);
}
void WidevineDummyDecoder::Drain()
{
Log("WidevineDummyDecoder::Drain");
mCallback->Error(GMPErr::GMPNotImplementedErr);
}
void WidevineDummyDecoder::DecodingComplete()
{
Log("WidevineDummyDecoder::DecodingComplete");
mCallback = nullptr;
delete this;
}
WidevineDummyDecoder::~WidevineDummyDecoder() {
Log("WidevineDummyDecoder destroyed");
}
}

Просмотреть файл

@ -0,0 +1,43 @@
#ifndef WidevineDummyDecoder_h_
#define WidevineDummyDecoder_h_
#include "stddef.h"
#include "content_decryption_module.h"
#include "gmp-api/gmp-video-decode.h"
#include "gmp-api/gmp-video-host.h"
#include "WidevineDecryptor.h"
#include "WidevineVideoFrame.h"
namespace mozilla {
class WidevineDummyDecoder : public GMPVideoDecoder {
public:
WidevineDummyDecoder();
void InitDecode(const GMPVideoCodec& aCodecSettings,
const uint8_t* aCodecSpecific,
uint32_t aCodecSpecificLength,
GMPVideoDecoderCallback* aCallback,
int32_t aCoreCount) override;
void Decode(GMPVideoEncodedFrame* aInputFrame,
bool aMissingFrames,
const uint8_t* aCodecSpecificInfo,
uint32_t aCodecSpecificInfoLength,
int64_t aRenderTimeMs = -1) override;
void Reset() override;
void Drain() override;
void DecodingComplete() override;
private:
~WidevineDummyDecoder();
GMPVideoDecoderCallback* mCallback;
};
}
#endif

Просмотреть файл

@ -7,6 +7,7 @@
SOURCES += [
'WidevineAdapter.cpp',
'WidevineDecryptor.cpp',
'WidevineDummyDecoder.cpp',
'WidevineFileIO.cpp',
'WidevineUtils.cpp',
'WidevineVideoDecoder.cpp',

Просмотреть файл

@ -78,9 +78,15 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(AudioContext)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioContext)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDestination)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromiseGripArray)
if (!tmp->mIsStarted) {
NS_IMPL_CYCLE_COLLECTION_UNLINK(mActiveNodes)
}
// mDecodeJobs owns the WebAudioDecodeJob objects whose lifetime is managed explicitly.
// mAllNodes is an array of weak pointers, ignore it here.
// mPannerNodes is an array of weak pointers, ignore it here.
// mBasicWaveFormCache cannot participate in cycles, ignore it here.
// Remove weak reference on the global window as the context is not usable
// without mDestination.
tmp->DisconnectFromWindow();
@ -90,11 +96,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioContext,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDestination)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListener)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromiseGripArray)
if (!tmp->mIsStarted) {
MOZ_ASSERT(tmp->mIsOffline,
"Online AudioContexts should always be started");
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActiveNodes)
}
// mDecodeJobs owns the WebAudioDecodeJob objects whose lifetime is managed explicitly.
// mAllNodes is an array of weak pointers, ignore it here.
// mPannerNodes is an array of weak pointers, ignore it here.
// mBasicWaveFormCache cannot participate in cycles, ignore it here.
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_ADDREF_INHERITED(AudioContext, DOMEventTargetHelper)
@ -633,6 +644,12 @@ AudioContext::Shutdown()
RefPtr<Promise> ignored = Close(dummy);
}
for (auto p : mPromiseGripArray) {
p->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
}
mPromiseGripArray.Clear();
// Release references to active nodes.
// Active AudioNodes don't unregister in destructors, at which point the
// Node is already unregistered.
@ -778,10 +795,16 @@ AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState)
if (aPromise) {
Promise* promise = reinterpret_cast<Promise*>(aPromise);
// It is possible for the promise to have been removed from
// mPromiseGripArray if the cycle collector has severed our connections. DO
// NOT dereference the promise pointer in that case since it may point to
// already freed memory.
if (mPromiseGripArray.Contains(promise)) {
promise->MaybeResolveWithUndefined();
DebugOnly<bool> rv = mPromiseGripArray.RemoveElement(promise);
MOZ_ASSERT(rv, "Promise wasn't in the grip array?");
}
}
if (mAudioContextState != aNewState) {
RefPtr<OnStateChangeTask> onStateChangeTask =

Просмотреть файл

@ -1633,12 +1633,10 @@ XMLHttpRequestWorker::ReleaseProxy(ReleaseType aType)
new SyncTeardownRunnable(mWorkerPrivate, mProxy);
mProxy = nullptr;
ErrorResult forAssertionsOnly;
IgnoredErrorResult forAssertionsOnly;
// This runnable _must_ be executed.
runnable->Dispatch(Killing, forAssertionsOnly);
if (forAssertionsOnly.Failed()) {
NS_ERROR("Failed to dispatch teardown runnable!");
}
runnable->Dispatch(Dead, forAssertionsOnly);
MOZ_DIAGNOSTIC_ASSERT(!forAssertionsOnly.Failed());
}
}
}

Просмотреть файл

@ -144,15 +144,6 @@ static void A8_RowProc_Blend(
}
}
// expand the steps that SkAlphaMulQ performs, but this way we can
// exand.. add.. combine
// instead of
// expand..combine add expand..combine
//
#define EXPAND0(v, m, s) ((v) & (m)) * (s)
#define EXPAND1(v, m, s) (((v) >> 8) & (m)) * (s)
#define COMBINE(e0, e1, m) ((((e0) >> 8) & (m)) | ((e1) & ~(m)))
static void A8_RowProc_Opaque(
SkPMColor* SK_RESTRICT dst, const void* maskIn, const SkPMColor* SK_RESTRICT src, int count) {
const uint8_t* SK_RESTRICT mask = static_cast<const uint8_t*>(maskIn);
@ -160,20 +151,7 @@ static void A8_RowProc_Opaque(
int m = mask[i];
if (m) {
m += (m >> 7);
#if 1
// this is slightly slower than the expand/combine version, but it
// is much closer to the old results, so we use it for now to reduce
// rebaselining.
dst[i] = SkAlphaMulQ(src[i], m) + SkAlphaMulQ(dst[i], 256 - m);
#else
uint32_t v = src[i];
uint32_t s0 = EXPAND0(v, rbmask, m);
uint32_t s1 = EXPAND1(v, rbmask, m);
v = dst[i];
uint32_t d0 = EXPAND0(v, rbmask, m);
uint32_t d1 = EXPAND1(v, rbmask, m);
dst[i] = COMBINE(s0 + d0, s1 + d1, rbmask);
#endif
dst[i] = SkPMLerp(src[i], dst[i], m);
}
}
}

Просмотреть файл

@ -416,6 +416,16 @@ struct ParamTraits<nsLiteralString> : ParamTraits<nsAString>
typedef nsLiteralString paramType;
};
#ifdef MOZILLA_INTERNAL_API
template<>
struct ParamTraits<nsAutoString> : ParamTraits<nsString>
{
typedef nsAutoString paramType;
};
#endif // MOZILLA_INTERNAL_API
// Pickle::ReadBytes and ::WriteBytes take the length in ints, so we must
// ensure there is no overflow. This returns |false| if it would overflow.
// Otherwise, it returns |true| and places the byte length in |aByteLength|.

Просмотреть файл

@ -11,6 +11,7 @@ basic/bug656261.js
basic/bug677957-2.js
basic/bug753283.js
basic/bug867946.js
basic/destructuring-iterator.js
basic/testAtomize.js
basic/testBug614653.js
basic/testBug686274.js

Просмотреть файл

@ -3,6 +3,9 @@
// Unfortunately these tests are brittle. They depend on opaque JIT heuristics
// kicking in.
// Use gczeal 0 to keep CGC from invalidating Ion code and causing test failures.
gczeal(0);
load(libdir + "jitopts.js");
if (!jitTogglesMatch(Opts_Ion2NoOffthreadCompilation))

Просмотреть файл

@ -0,0 +1,10 @@
function g(f) {
for (var j = 0; j < 999; ++j) {
f(0 / 0);
}
}
function h(x) {
x < 1 ? 0 : Math.imul(x || 0);
}
g(h);

Просмотреть файл

@ -0,0 +1,4 @@
// |jit-test| error:ReferenceError
++f();
try {} catch (e) {}

Просмотреть файл

@ -1300,7 +1300,7 @@ BacktrackingAllocator::processBundle(MIRGenerator* mir, LiveBundle* bundle)
// If that didn't work, but we have one or more non-fixed bundles
// known to be conflicting, maybe we can evict them and try again.
if (attempt < MAX_ATTEMPTS &&
if ((attempt < MAX_ATTEMPTS || minimalBundle(bundle)) &&
!fixed &&
!conflicting.empty() &&
maximumSpillWeight(conflicting) < computeSpillWeight(bundle))

Просмотреть файл

@ -18,6 +18,8 @@ using namespace js::jit;
using mozilla::Maybe;
class AutoStubFrame;
// BaselineCacheIRCompiler compiles CacheIR to BaselineIC native code.
class MOZ_RAII BaselineCacheIRCompiler : public CacheIRCompiler
{
@ -32,6 +34,11 @@ class MOZ_RAII BaselineCacheIRCompiler : public CacheIRCompiler
MOZ_MUST_USE bool callVM(MacroAssembler& masm, const VMFunction& fun);
MOZ_MUST_USE bool callTypeUpdateIC(AutoStubFrame& stubFrame, Register obj, ValueOperand val,
Register scratch, LiveGeneralRegisterSet saveRegs);
MOZ_MUST_USE bool emitStoreSlotShared(bool isFixed);
public:
friend class AutoStubFrame;
@ -65,6 +72,8 @@ class MOZ_RAII BaselineCacheIRCompiler : public CacheIRCompiler
CACHE_IR_SHARED_OPS(DEFINE_SHARED_OP)
#undef DEFINE_SHARED_OP
enum class CallCanGC { CanGC, CanNotGC };
// Instructions that have to perform a callVM require a stub frame. Use
// AutoStubFrame before allocating any registers, then call its enter() and
// leave() methods to enter/leave the stub frame.
@ -93,7 +102,7 @@ class MOZ_RAII AutoStubFrame
tail.emplace(compiler.allocator, compiler.masm, ICTailCallReg);
}
void enter(MacroAssembler& masm, Register scratch) {
void enter(MacroAssembler& masm, Register scratch, CallCanGC canGC = CallCanGC::CanGC) {
if (compiler.engine_ == ICStubEngine::Baseline) {
EmitBaselineEnterStubFrame(masm, scratch);
#ifdef DEBUG
@ -105,6 +114,7 @@ class MOZ_RAII AutoStubFrame
MOZ_ASSERT(!compiler.inStubFrame_);
compiler.inStubFrame_ = true;
if (canGC == CallCanGC::CanGC)
compiler.makesGCCalls_ = true;
}
void leave(MacroAssembler& masm, bool calledIntoIon = false) {
@ -649,6 +659,112 @@ BaselineCacheIRCompiler::emitLoadEnvironmentDynamicSlotResult()
return true;
}
bool
BaselineCacheIRCompiler::callTypeUpdateIC(AutoStubFrame& stubFrame, Register obj, ValueOperand val,
Register scratch, LiveGeneralRegisterSet saveRegs)
{
// R0 contains the value that needs to be typechecked.
MOZ_ASSERT(val == R0);
MOZ_ASSERT(scratch == R1.scratchReg());
#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
static const bool CallClobbersTailReg = false;
#else
static const bool CallClobbersTailReg = true;
#endif
// Call the first type update stub.
if (CallClobbersTailReg)
masm.push(ICTailCallReg);
masm.push(ICStubReg);
masm.loadPtr(Address(ICStubReg, ICUpdatedStub::offsetOfFirstUpdateStub()),
ICStubReg);
masm.call(Address(ICStubReg, ICStub::offsetOfStubCode()));
masm.pop(ICStubReg);
if (CallClobbersTailReg)
masm.pop(ICTailCallReg);
// The update IC will store 0 or 1 in |scratch|, R1.scratchReg(), reflecting
// if the value in R0 type-checked properly or not.
Label done;
masm.branch32(Assembler::Equal, scratch, Imm32(1), &done);
stubFrame.enter(masm, scratch, CallCanGC::CanNotGC);
masm.PushRegsInMask(saveRegs);
masm.Push(val);
masm.Push(TypedOrValueRegister(MIRType::Object, AnyRegister(obj)));
masm.Push(ICStubReg);
// Load previous frame pointer, push BaselineFrame*.
masm.loadPtr(Address(BaselineFrameReg, 0), scratch);
masm.pushBaselineFramePtr(scratch, scratch);
if (!callVM(masm, DoTypeUpdateFallbackInfo))
return false;
masm.PopRegsInMask(saveRegs);
stubFrame.leave(masm);
masm.bind(&done);
return true;
}
bool
BaselineCacheIRCompiler::emitStoreSlotShared(bool isFixed)
{
ObjOperandId objId = reader.objOperandId();
Address offsetAddr = stubAddress(reader.stubOffset());
// Allocate the fixed registers first. These need to be fixed for
// callTypeUpdateIC.
AutoStubFrame stubFrame(*this);
AutoScratchRegister scratch(allocator, masm, R1.scratchReg());
ValueOperand val = allocator.useFixedValueRegister(masm, reader.valOperandId(), R0);
Register obj = allocator.useRegister(masm, objId);
LiveGeneralRegisterSet saveRegs;
saveRegs.add(obj);
saveRegs.add(val);
if (!callTypeUpdateIC(stubFrame, obj, val, scratch, saveRegs))
return false;
masm.load32(offsetAddr, scratch);
if (isFixed) {
BaseIndex slot(obj, scratch, TimesOne);
EmitPreBarrier(masm, slot, MIRType::Value);
masm.storeValue(val, slot);
} else {
// To avoid running out of registers on x86, use ICStubReg as scratch.
// We don't need it anymore.
Register slots = ICStubReg;
masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), slots);
BaseIndex slot(slots, scratch, TimesOne);
EmitPreBarrier(masm, slot, MIRType::Value);
masm.storeValue(val, slot);
}
if (cx_->gc.nursery.exists())
BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch, LiveGeneralRegisterSet(), cx_);
return true;
}
bool
BaselineCacheIRCompiler::emitStoreFixedSlot()
{
return emitStoreSlotShared(true);
}
bool
BaselineCacheIRCompiler::emitStoreDynamicSlot()
{
return emitStoreSlotShared(false);
}
bool
BaselineCacheIRCompiler::emitTypeMonitorResult()
{
@ -748,6 +864,7 @@ BaselineCacheIRCompiler::init(CacheKind kind)
allocator.initInputLocation(0, R0);
break;
case CacheKind::GetElem:
case CacheKind::SetProp:
MOZ_ASSERT(numInputs == 2);
allocator.initInputLocation(0, R0);
allocator.initInputLocation(1, R1);
@ -786,9 +903,22 @@ jit::AttachBaselineCacheIRStub(JSContext* cx, const CacheIRWriter& writer,
// unlimited number of stubs.
MOZ_ASSERT(stub->numOptimizedStubs() < MaxOptimizedCacheIRStubs);
MOZ_ASSERT(kind == CacheKind::GetProp || kind == CacheKind::GetElem ||
kind == CacheKind::GetName, "sizeof needs to change for SetProp!");
uint32_t stubDataOffset = sizeof(ICCacheIR_Monitored);
enum class CacheIRStubKind { Monitored, Updated };
uint32_t stubDataOffset;
CacheIRStubKind stubKind;
switch (kind) {
case CacheKind::GetProp:
case CacheKind::GetElem:
case CacheKind::GetName:
stubDataOffset = sizeof(ICCacheIR_Monitored);
stubKind = CacheIRStubKind::Monitored;
break;
case CacheKind::SetProp:
stubDataOffset = sizeof(ICCacheIR_Updated);
stubKind = CacheIRStubKind::Updated;
break;
}
JitCompartment* jitCompartment = cx->compartment()->jitCompartment();
@ -822,21 +952,34 @@ jit::AttachBaselineCacheIRStub(JSContext* cx, const CacheIRWriter& writer,
MOZ_ASSERT(code);
MOZ_ASSERT(stubInfo);
MOZ_ASSERT(stub->isMonitoredFallback());
MOZ_ASSERT(stubInfo->stubDataSize() == writer.stubDataSize());
// Ensure we don't attach duplicate stubs. This can happen if a stub failed
// for some reason and the IR generator doesn't check for exactly the same
// conditions.
for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
switch (stubKind) {
case CacheIRStubKind::Monitored: {
if (!iter->isCacheIR_Monitored())
continue;
ICCacheIR_Monitored* otherStub = iter->toCacheIR_Monitored();
auto otherStub = iter->toCacheIR_Monitored();
if (otherStub->stubInfo() != stubInfo)
continue;
if (!writer.stubDataEquals(otherStub->stubDataStart()))
continue;
break;
}
case CacheIRStubKind::Updated: {
if (!iter->isCacheIR_Updated())
continue;
auto otherStub = iter->toCacheIR_Updated();
if (otherStub->stubInfo() != stubInfo)
continue;
if (!writer.stubDataEquals(otherStub->stubDataStart()))
continue;
break;
}
}
// We found a stub that's exactly the same as the stub we're about to
// attach. Just return nullptr, the caller should do nothing in this
@ -854,12 +997,28 @@ jit::AttachBaselineCacheIRStub(JSContext* cx, const CacheIRWriter& writer,
if (!newStubMem)
return nullptr;
ICStub* monitorStub = stub->toMonitoredFallbackStub()->fallbackMonitorStub()->firstMonitorStub();
switch (stubKind) {
case CacheIRStubKind::Monitored: {
ICStub* monitorStub =
stub->toMonitoredFallbackStub()->fallbackMonitorStub()->firstMonitorStub();
auto newStub = new(newStubMem) ICCacheIR_Monitored(code, monitorStub, stubInfo);
writer.copyStubData(newStub->stubDataStart());
stub->addNewStub(newStub);
return newStub;
}
case CacheIRStubKind::Updated: {
auto newStub = new(newStubMem) ICCacheIR_Updated(code, stubInfo);
if (!newStub->initUpdatingChain(cx, stubSpace)) {
cx->recoverFromOutOfMemory();
return nullptr;
}
writer.copyStubData(newStub->stubDataStart());
stub->addNewStub(newStub);
return newStub;
}
}
MOZ_CRASH("Invalid kind");
}
uint8_t*
@ -868,6 +1027,12 @@ ICCacheIR_Monitored::stubDataStart()
return reinterpret_cast<uint8_t*>(this) + stubInfo_->stubDataOffset();
}
uint8_t*
ICCacheIR_Updated::stubDataStart()
{
return reinterpret_cast<uint8_t*>(this) + stubInfo_->stubDataOffset();
}
/* static */ ICCacheIR_Monitored*
ICCacheIR_Monitored::Clone(JSContext* cx, ICStubSpace* space, ICStub* firstMonitorStub,
ICCacheIR_Monitored& other)

Просмотреть файл

@ -280,14 +280,18 @@ DoTypeUpdateFallback(JSContext* cx, BaselineFrame* frame, ICUpdatedStub* stub, H
RootedObject obj(cx, &objval.toObject());
RootedId id(cx);
switch(stub->kind()) {
switch (stub->kind()) {
case ICStub::CacheIR_Updated:
id = stub->toCacheIR_Updated()->updateStubId();
MOZ_ASSERT(id != JSID_EMPTY);
AddTypePropertyId(cx, obj, id, value);
break;
case ICStub::SetElem_DenseOrUnboxedArray:
case ICStub::SetElem_DenseOrUnboxedArrayAdd: {
id = JSID_VOID;
AddTypePropertyId(cx, obj, id, value);
break;
}
case ICStub::SetProp_Native:
case ICStub::SetProp_NativeAdd:
case ICStub::SetProp_Unboxed: {
MOZ_ASSERT(obj->isNative() || obj->is<UnboxedPlainObject>());
@ -737,23 +741,6 @@ LastPropertyForSetProp(JSObject* obj)
return nullptr;
}
static bool
IsCacheableSetPropWriteSlot(JSObject* obj, Shape* oldShape, Shape* propertyShape)
{
// Object shape must not have changed during the property set.
if (LastPropertyForSetProp(obj) != oldShape)
return false;
if (!propertyShape->hasSlot() ||
!propertyShape->hasDefaultSetter() ||
!propertyShape->writable())
{
return false;
}
return true;
}
static bool
IsCacheableSetPropAddSlot(JSContext* cx, JSObject* obj, Shape* oldShape,
jsid id, Shape* propertyShape, size_t* protoChainDepth)
@ -1531,7 +1518,7 @@ ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm)
saveRegs.add(R0);
saveRegs.addUnchecked(obj);
saveRegs.add(ICStubReg);
emitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs);
BaselineEmitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs, cx);
masm.Pop(R1);
}
@ -1739,7 +1726,7 @@ ICSetElemDenseOrUnboxedArrayAddCompiler::generateStubCode(MacroAssembler& masm)
saveRegs.add(R0);
saveRegs.addUnchecked(obj);
saveRegs.add(ICStubReg);
emitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs);
BaselineEmitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs, cx);
masm.Pop(R1);
}
@ -2636,41 +2623,6 @@ TryAttachSetValuePropStub(JSContext* cx, HandleScript script, jsbytecode* pc, IC
return true;
}
if (IsCacheableSetPropWriteSlot(obj, oldShape, shape)) {
// For some property writes, such as the initial overwrite of global
// properties, TI will not mark the property as having been
// overwritten. Don't attach a stub in this case, so that we don't
// execute another write to the property without TI seeing that write.
EnsureTrackPropertyTypes(cx, obj, id);
if (!PropertyHasBeenMarkedNonConstant(obj, id)) {
*attached = true;
return true;
}
bool isFixedSlot;
uint32_t offset;
GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset);
JitSpew(JitSpew_BaselineIC, " Generating SetProp(NativeObject.PROP) stub");
MOZ_ASSERT(LastPropertyForSetProp(obj) == oldShape,
"Should this really be a SetPropWriteSlot?");
ICSetProp_Native::Compiler compiler(cx, obj, isFixedSlot, offset);
ICSetProp_Native* newStub = compiler.getStub(compiler.getStubSpace(script));
if (!newStub)
return false;
if (!newStub->addUpdateStubForValue(cx, script, obj, id, rhs))
return false;
if (IsPreliminaryObject(obj))
newStub->notePreliminaryObject();
else
StripPreliminaryObjectStubs(cx, stub);
stub->addNewStub(newStub);
*attached = true;
return true;
}
return true;
}
@ -2935,6 +2887,30 @@ DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub_
return false;
}
if (!attached &&
stub->numOptimizedStubs() < ICSetProp_Fallback::MAX_OPTIMIZED_STUBS &&
!JitOptions.disableCacheIR)
{
RootedValue idVal(cx, StringValue(name));
SetPropIRGenerator gen(cx, pc, CacheKind::SetProp, &isTemporarilyUnoptimizable,
lhs, idVal, rhs);
if (gen.tryAttachStub()) {
ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
ICStubEngine::Baseline, frame->script(), stub);
if (newStub) {
JitSpew(JitSpew_BaselineIC, " Attached CacheIR stub");
attached = true;
newStub->toCacheIR_Updated()->updateStubId() = gen.updateStubId();
if (gen.shouldNotePreliminaryObjectStub())
newStub->toCacheIR_Updated()->notePreliminaryObject();
else if (gen.shouldUnlinkPreliminaryObjectStubs())
StripPreliminaryObjectStubs(cx, stub);
}
}
}
if (op == JSOP_INITPROP ||
op == JSOP_INITLOCKEDPROP ||
op == JSOP_INITHIDDENPROP)
@ -3107,77 +3083,6 @@ GuardGroupAndShapeMaybeUnboxedExpando(MacroAssembler& masm, JSObject* obj,
}
}
bool
ICSetProp_Native::Compiler::generateStubCode(MacroAssembler& masm)
{
MOZ_ASSERT(engine_ == Engine::Baseline);
Label failure;
// Guard input is an object.
masm.branchTestObject(Assembler::NotEqual, R0, &failure);
Register objReg = masm.extractObject(R0, ExtractTemp0);
AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
Register scratch = regs.takeAny();
GuardGroupAndShapeMaybeUnboxedExpando(masm, obj_, objReg, scratch,
ICSetProp_Native::offsetOfGroup(),
ICSetProp_Native::offsetOfShape(),
&failure);
// Stow both R0 and R1 (object and value).
EmitStowICValues(masm, 2);
// Type update stub expects the value to check in R0.
masm.moveValue(R1, R0);
// Call the type-update stub.
if (!callTypeUpdateIC(masm, sizeof(Value)))
return false;
// Unstow R0 and R1 (object and key)
EmitUnstowICValues(masm, 2);
regs.add(R0);
regs.takeUnchecked(objReg);
Register holderReg;
if (obj_->is<UnboxedPlainObject>()) {
// We are loading off the expando object, so use that for the holder.
holderReg = regs.takeAny();
masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg);
if (!isFixedSlot_)
masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), holderReg);
} else if (isFixedSlot_) {
holderReg = objReg;
} else {
holderReg = regs.takeAny();
masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), holderReg);
}
// Perform the store.
masm.load32(Address(ICStubReg, ICSetProp_Native::offsetOfOffset()), scratch);
EmitPreBarrier(masm, BaseIndex(holderReg, scratch, TimesOne), MIRType::Value);
masm.storeValue(R1, BaseIndex(holderReg, scratch, TimesOne));
if (holderReg != objReg)
regs.add(holderReg);
if (cx->runtime()->gc.nursery.exists()) {
Register scr = regs.takeAny();
LiveGeneralRegisterSet saveRegs;
saveRegs.add(R1);
emitPostWriteBarrierSlot(masm, objReg, R1, scr, saveRegs);
regs.add(scr);
}
EmitReturnFromIC(masm);
// Failure case - jump to next stub
masm.bind(&failure);
EmitStubGuardFailure(masm);
return true;
}
ICUpdatedStub*
ICSetPropNativeAddCompiler::getStub(ICStubSpace* space)
{
@ -3323,7 +3228,7 @@ ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler& masm)
Register scr = regs.takeAny();
LiveGeneralRegisterSet saveRegs;
saveRegs.add(R1);
emitPostWriteBarrierSlot(masm, objReg, R1, scr, saveRegs);
BaselineEmitPostWriteBarrierSlot(masm, objReg, R1, scr, saveRegs, cx);
}
EmitReturnFromIC(masm);
@ -3380,7 +3285,7 @@ ICSetProp_Unboxed::Compiler::generateStubCode(MacroAssembler& masm)
saveRegs.add(R1);
saveRegs.addUnchecked(object);
saveRegs.add(ICStubReg);
emitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs);
BaselineEmitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs, cx);
}
// Compute the address being written to.
@ -3447,7 +3352,7 @@ ICSetProp_TypedObject::Compiler::generateStubCode(MacroAssembler& masm)
saveRegs.add(R1);
saveRegs.addUnchecked(object);
saveRegs.add(ICStubReg);
emitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs);
BaselineEmitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs, cx);
}
// Save the rhs on the stack so we can get a second scratch register.
@ -6682,28 +6587,6 @@ ICInstanceOf_Function::ICInstanceOf_Function(JitCode* stubCode, Shape* shape,
slot_(slot)
{ }
ICSetProp_Native::ICSetProp_Native(JitCode* stubCode, ObjectGroup* group, Shape* shape,
uint32_t offset)
: ICUpdatedStub(SetProp_Native, stubCode),
group_(group),
shape_(shape),
offset_(offset)
{ }
ICSetProp_Native*
ICSetProp_Native::Compiler::getStub(ICStubSpace* space)
{
RootedObjectGroup group(cx, JSObject::getGroup(cx, obj_));
if (!group)
return nullptr;
RootedShape shape(cx, LastPropertyForSetProp(obj_));
ICSetProp_Native* stub = newStub<ICSetProp_Native>(space, getStubCode(), group, shape, offset_);
if (!stub || !stub->initUpdatingChain(cx, space))
return nullptr;
return stub;
}
ICSetProp_NativeAdd::ICSetProp_NativeAdd(JitCode* stubCode, ObjectGroup* group,
size_t protoChainDepth,
Shape* newShape,

Просмотреть файл

@ -1111,69 +1111,6 @@ class ICSetProp_Fallback : public ICFallbackStub
};
};
// Optimized SETPROP/SETGNAME/SETNAME stub.
class ICSetProp_Native : public ICUpdatedStub
{
friend class ICStubSpace;
protected: // Protected to silence Clang warning.
GCPtrObjectGroup group_;
GCPtrShape shape_;
uint32_t offset_;
ICSetProp_Native(JitCode* stubCode, ObjectGroup* group, Shape* shape, uint32_t offset);
public:
GCPtrObjectGroup& group() {
return group_;
}
GCPtrShape& shape() {
return shape_;
}
void notePreliminaryObject() {
extra_ = 1;
}
bool hasPreliminaryObject() const {
return extra_;
}
static size_t offsetOfGroup() {
return offsetof(ICSetProp_Native, group_);
}
static size_t offsetOfShape() {
return offsetof(ICSetProp_Native, shape_);
}
static size_t offsetOfOffset() {
return offsetof(ICSetProp_Native, offset_);
}
class Compiler : public ICStubCompiler {
RootedObject obj_;
bool isFixedSlot_;
uint32_t offset_;
protected:
virtual int32_t getKey() const {
return static_cast<int32_t>(engine_) |
(static_cast<int32_t>(kind) << 1) |
(static_cast<int32_t>(isFixedSlot_) << 17) |
(static_cast<int32_t>(obj_->is<UnboxedPlainObject>()) << 18);
}
MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
public:
Compiler(JSContext* cx, HandleObject obj, bool isFixedSlot, uint32_t offset)
: ICStubCompiler(cx, ICStub::SetProp_Native, Engine::Baseline),
obj_(cx, obj),
isFixedSlot_(isFixedSlot),
offset_(offset)
{}
ICSetProp_Native* getStub(ICStubSpace* space);
};
};
template <size_t ProtoChainDepth> class ICSetProp_NativeAddImpl;
class ICSetProp_NativeAdd : public ICUpdatedStub

Просмотреть файл

@ -69,7 +69,6 @@ namespace jit {
_(GetIntrinsic_Constant) \
\
_(SetProp_Fallback) \
_(SetProp_Native) \
_(SetProp_NativeAdd) \
_(SetProp_Unboxed) \
_(SetProp_TypedObject) \

Просмотреть файл

@ -171,6 +171,49 @@ GetCacheIRReceiverForUnboxedProperty(ICCacheIR_Monitored* stub, ReceiverGuard* r
return reader.matchOp(CacheOp::LoadUnboxedPropertyResult, objId);
}
static bool
GetCacheIRReceiverForNativeSetSlot(ICCacheIR_Updated* stub, ReceiverGuard* receiver)
{
// We match either:
//
// GuardIsObject 0
// GuardGroup 0
// GuardShape 0
// StoreFixedSlot 0 or StoreDynamicSlot 0
//
// or
//
// GuardIsObject 0
// GuardGroup 0
// 1: GuardAndLoadUnboxedExpando 0
// GuardShape 1
// StoreFixedSlot 1 or StoreDynamicSlot 1
*receiver = ReceiverGuard();
CacheIRReader reader(stub->stubInfo());
ObjOperandId objId = ObjOperandId(0);
if (!reader.matchOp(CacheOp::GuardIsObject, objId))
return false;
if (!reader.matchOp(CacheOp::GuardGroup, objId))
return false;
ObjectGroup* group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset());
if (reader.matchOp(CacheOp::GuardAndLoadUnboxedExpando, objId))
objId = reader.objOperandId();
if (!reader.matchOp(CacheOp::GuardShape, objId))
return false;
Shape* shape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset());
if (!reader.matchOpEither(CacheOp::StoreFixedSlot, CacheOp::StoreDynamicSlot))
return false;
*receiver = ReceiverGuard(group, shape);
return true;
}
bool
BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers,
ObjectGroupVector& convertUnboxedGroups)
@ -199,9 +242,11 @@ BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receiv
receivers.clear();
return true;
}
} else if (stub->isSetProp_Native()) {
receiver = ReceiverGuard(stub->toSetProp_Native()->group(),
stub->toSetProp_Native()->shape());
} else if (stub->isCacheIR_Updated()) {
if (!GetCacheIRReceiverForNativeSetSlot(stub->toCacheIR_Updated(), &receiver)) {
receivers.clear();
return true;
}
} else if (stub->isSetProp_Unboxed()) {
receiver = ReceiverGuard(stub->toSetProp_Unboxed()->group(), nullptr);
} else {

Просмотреть файл

@ -1523,3 +1523,141 @@ IRGenerator::maybeGuardInt32Index(const Value& index, ValOperandId indexId,
return false;
}
SetPropIRGenerator::SetPropIRGenerator(JSContext* cx, jsbytecode* pc, CacheKind cacheKind,
bool* isTemporarilyUnoptimizable, HandleValue lhsVal,
HandleValue idVal, HandleValue rhsVal)
: IRGenerator(cx, pc, cacheKind),
lhsVal_(lhsVal),
idVal_(idVal),
rhsVal_(rhsVal),
isTemporarilyUnoptimizable_(isTemporarilyUnoptimizable),
preliminaryObjectAction_(PreliminaryObjectAction::None),
updateStubId_(cx, JSID_EMPTY),
needUpdateStub_(false)
{}
bool
SetPropIRGenerator::tryAttachStub()
{
AutoAssertNoPendingException aanpe(cx_);
ValOperandId lhsValId(writer.setInputOperandId(0));
ValOperandId rhsValId(writer.setInputOperandId(1));
RootedId id(cx_);
bool nameOrSymbol;
if (!ValueToNameOrSymbolId(cx_, idVal_, &id, &nameOrSymbol)) {
cx_->clearPendingException();
return false;
}
if (lhsVal_.isObject()) {
RootedObject obj(cx_, &lhsVal_.toObject());
if (obj->watched())
return false;
ObjOperandId objId = writer.guardIsObject(lhsValId);
if (nameOrSymbol) {
if (tryAttachNativeSetSlot(obj, objId, id, rhsValId))
return true;
if (tryAttachUnboxedExpandoSetSlot(obj, objId, id, rhsValId))
return true;
}
return false;
}
return false;
}
static void
EmitStoreSlotAndReturn(CacheIRWriter& writer, ObjOperandId objId, NativeObject* nobj, Shape* shape,
ValOperandId rhsId)
{
if (nobj->isFixedSlot(shape->slot())) {
size_t offset = NativeObject::getFixedSlotOffset(shape->slot());
writer.storeFixedSlot(objId, offset, rhsId);
} else {
size_t offset = nobj->dynamicSlotIndex(shape->slot()) * sizeof(Value);
writer.storeDynamicSlot(objId, offset, rhsId);
}
writer.returnFromIC();
}
static Shape*
LookupShapeForSetSlot(NativeObject* obj, jsid id)
{
Shape* shape = obj->lookupPure(id);
if (shape && shape->hasSlot() && shape->hasDefaultSetter() && shape->writable())
return shape;
return nullptr;
}
bool
SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId, HandleId id,
ValOperandId rhsId)
{
if (!obj->isNative())
return false;
RootedShape propShape(cx_, LookupShapeForSetSlot(&obj->as<NativeObject>(), id));
if (!propShape)
return false;
RootedObjectGroup group(cx_, JSObject::getGroup(cx_, obj));
if (!group) {
cx_->recoverFromOutOfMemory();
return false;
}
// For some property writes, such as the initial overwrite of global
// properties, TI will not mark the property as having been
// overwritten. Don't attach a stub in this case, so that we don't
// execute another write to the property without TI seeing that write.
EnsureTrackPropertyTypes(cx_, obj, id);
if (!PropertyHasBeenMarkedNonConstant(obj, id)) {
*isTemporarilyUnoptimizable_ = true;
return false;
}
// For Baseline, we have to guard on both the shape and group, because the
// type update IC applies to a single group. When we port the Ion IC, we can
// do a bit better and avoid the group guard if we don't have to guard on
// the property types.
NativeObject* nobj = &obj->as<NativeObject>();
writer.guardGroup(objId, nobj->group());
writer.guardShape(objId, nobj->lastProperty());
if (IsPreliminaryObject(obj))
preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary;
else
preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
setUpdateStubInfo(id);
EmitStoreSlotAndReturn(writer, objId, nobj, propShape, rhsId);
return true;
}
bool
SetPropIRGenerator::tryAttachUnboxedExpandoSetSlot(HandleObject obj, ObjOperandId objId,
HandleId id, ValOperandId rhsId)
{
if (!obj->is<UnboxedPlainObject>())
return false;
UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
if (!expando)
return false;
Shape* propShape = LookupShapeForSetSlot(expando, id);
if (!propShape)
return false;
writer.guardGroup(objId, obj->group());
ObjOperandId expandoId = writer.guardAndLoadUnboxedExpando(objId);
writer.guardShape(expandoId, expando->lastProperty());
setUpdateStubInfo(id);
EmitStoreSlotAndReturn(writer, expandoId, expando, propShape, rhsId);
return true;
}

Просмотреть файл

@ -134,6 +134,7 @@ enum class CacheKind : uint8_t
GetProp,
GetElem,
GetName,
SetProp,
};
#define CACHE_IR_OPS(_) \
@ -168,6 +169,9 @@ enum class CacheKind : uint8_t
_(LoadDOMExpandoValueIgnoreGeneration)\
_(GuardDOMExpandoMissingOrGuardShape) \
\
_(StoreFixedSlot) \
_(StoreDynamicSlot) \
\
/* The *Result ops load a value into the cache's result register. */ \
_(LoadFixedSlotResult) \
_(LoadDynamicSlotResult) \
@ -547,6 +551,17 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter
return res;
}
void storeFixedSlot(ObjOperandId obj, size_t offset, ValOperandId rhs) {
writeOpWithOperandId(CacheOp::StoreFixedSlot, obj);
addStubField(offset, StubField::Type::RawWord);
writeOperandId(rhs);
}
void storeDynamicSlot(ObjOperandId obj, size_t offset, ValOperandId rhs) {
writeOpWithOperandId(CacheOp::StoreDynamicSlot, obj);
addStubField(offset, StubField::Type::RawWord);
writeOperandId(rhs);
}
void loadUndefinedResult() {
writeOp(CacheOp::LoadUndefinedResult);
}
@ -824,6 +839,51 @@ class MOZ_RAII GetNameIRGenerator : public IRGenerator
bool tryAttachStub();
};
// SetPropIRGenerator generates CacheIR for a SetProp IC.
class MOZ_RAII SetPropIRGenerator : public IRGenerator
{
HandleValue lhsVal_;
HandleValue idVal_;
HandleValue rhsVal_;
bool* isTemporarilyUnoptimizable_;
enum class PreliminaryObjectAction { None, Unlink, NotePreliminary };
PreliminaryObjectAction preliminaryObjectAction_;
// If Baseline needs an update stub, this contains information to create it.
RootedId updateStubId_;
bool needUpdateStub_;
void setUpdateStubInfo(jsid id) {
MOZ_ASSERT(!needUpdateStub_);
needUpdateStub_ = true;
updateStubId_ = id;
}
bool tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId, HandleId id,
ValOperandId rhsId);
bool tryAttachUnboxedExpandoSetSlot(HandleObject obj, ObjOperandId objId, HandleId id,
ValOperandId rhsId);
public:
SetPropIRGenerator(JSContext* cx, jsbytecode* pc, CacheKind cacheKind,
bool* isTemporarilyUnoptimizable, HandleValue lhsVal, HandleValue idVal,
HandleValue rhsVal);
bool tryAttachStub();
bool shouldUnlinkPreliminaryObjectStubs() const {
return preliminaryObjectAction_ == PreliminaryObjectAction::Unlink;
}
bool shouldNotePreliminaryObjectStub() const {
return preliminaryObjectAction_ == PreliminaryObjectAction::NotePreliminary;
}
jsid updateStubId() const {
MOZ_ASSERT(needUpdateStub_);
return updateStubId_;
}
};
} // namespace jit
} // namespace js

Просмотреть файл

@ -61,6 +61,42 @@ CacheRegisterAllocator::useValueRegister(MacroAssembler& masm, ValOperandId op)
MOZ_CRASH();
}
ValueOperand
CacheRegisterAllocator::useFixedValueRegister(MacroAssembler& masm, ValOperandId valId,
ValueOperand reg)
{
allocateFixedValueRegister(masm, reg);
OperandLocation& loc = operandLocations_[valId.id()];
switch (loc.kind()) {
case OperandLocation::ValueReg:
masm.moveValue(loc.valueReg(), reg);
MOZ_ASSERT(!currentOpRegs_.aliases(loc.valueReg()), "Register shouldn't be in use");
availableRegs_.add(loc.valueReg());
break;
case OperandLocation::ValueStack:
popValue(masm, &loc, reg);
break;
case OperandLocation::Constant:
masm.moveValue(loc.constant(), reg);
break;
case OperandLocation::PayloadReg:
masm.tagValue(loc.payloadType(), loc.payloadReg(), reg);
MOZ_ASSERT(!currentOpRegs_.has(loc.payloadReg()), "Register shouldn't be in use");
availableRegs_.add(loc.payloadReg());
break;
case OperandLocation::PayloadStack:
popPayload(masm, &loc, reg.scratchReg());
masm.tagValue(loc.payloadType(), reg.scratchReg(), reg);
break;
case OperandLocation::Uninitialized:
MOZ_CRASH();
}
loc.setValueReg(reg);
return reg;
}
Register
CacheRegisterAllocator::useRegister(MacroAssembler& masm, TypedOperandId typedId)
{

Просмотреть файл

@ -339,6 +339,7 @@ class MOZ_RAII CacheRegisterAllocator
// Returns the register for the given operand. If the operand is currently
// not in a register, it will load it into one.
ValueOperand useValueRegister(MacroAssembler& masm, ValOperandId val);
ValueOperand useFixedValueRegister(MacroAssembler& masm, ValOperandId valId, ValueOperand reg);
Register useRegister(MacroAssembler& masm, TypedOperandId typedId);
// Allocates an output register for the given operand.

Просмотреть файл

@ -257,6 +257,7 @@ CodeGenerator::visitOutOfLineICFallback(OutOfLineICFallback* ool)
return;
}
case CacheKind::GetName:
case CacheKind::SetProp:
MOZ_CRASH("Baseline-specific for now");
}
MOZ_CRASH();

Просмотреть файл

@ -5712,14 +5712,25 @@ IonBuilder::compareTrySharedStub(bool* emitted, JSOp op, MDefinition* left, MDef
return Ok();
}
static bool
IsCallOpcode(JSOp op)
{
// TODO: Support tracking optimizations for inlining a call and regular
// optimization tracking at the same time.
return op == JSOP_CALL || op == JSOP_CALLITER || op == JSOP_NEW || op == JSOP_SUPERCALL ||
op == JSOP_EVAL || op == JSOP_STRICTEVAL;
}
AbortReasonOr<Ok>
IonBuilder::newArrayTryTemplateObject(bool* emitted, JSObject* templateObject, uint32_t length)
{
MOZ_ASSERT(*emitted == false);
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationAttempt(TrackedStrategy::NewArray_TemplateObject);
if (!templateObject) {
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationOutcome(TrackedOutcome::NoTemplateObject);
return Ok();
}
@ -5727,6 +5738,7 @@ IonBuilder::newArrayTryTemplateObject(bool* emitted, JSObject* templateObject, u
if (templateObject->is<UnboxedArrayObject>()) {
MOZ_ASSERT(templateObject->as<UnboxedArrayObject>().capacity() >= length);
if (!templateObject->as<UnboxedArrayObject>().hasInlineElements()) {
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationOutcome(TrackedOutcome::TemplateObjectIsUnboxedWithoutInlineElements);
return Ok();
}
@ -5738,6 +5750,7 @@ IonBuilder::newArrayTryTemplateObject(bool* emitted, JSObject* templateObject, u
gc::GetGCKindSlots(templateObject->asTenured().getAllocKind()) - ObjectElements::VALUES_PER_HEADER;
if (length > arraySlots) {
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationOutcome(TrackedOutcome::LengthTooBig);
return Ok();
}
@ -5752,6 +5765,7 @@ IonBuilder::newArrayTryTemplateObject(bool* emitted, JSObject* templateObject, u
current->add(ins);
current->push(ins);
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationSuccess();
*emitted = true;
return Ok();
@ -5770,6 +5784,7 @@ IonBuilder::newArrayTrySharedStub(bool* emitted)
if (*pc != JSOP_NEWINIT && *pc != JSOP_NEWARRAY)
return Ok();
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationAttempt(TrackedStrategy::NewArray_SharedCache);
MInstruction* stub = MNullarySharedStub::New(alloc());
@ -5782,6 +5797,7 @@ IonBuilder::newArrayTrySharedStub(bool* emitted)
current->add(unbox);
current->push(unbox);
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationSuccess();
*emitted = true;
@ -5794,6 +5810,7 @@ IonBuilder::newArrayTryVM(bool* emitted, JSObject* templateObject, uint32_t leng
MOZ_ASSERT(*emitted == false);
// Emit a VM call.
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationAttempt(TrackedStrategy::NewArray_Call);
gc::InitialHeap heap = gc::DefaultHeap;
@ -5810,6 +5827,7 @@ IonBuilder::newArrayTryVM(bool* emitted, JSObject* templateObject, uint32_t leng
current->add(ins);
current->push(ins);
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationSuccess();
*emitted = true;
return Ok();
@ -5835,6 +5853,7 @@ AbortReasonOr<Ok>
IonBuilder::jsop_newarray(JSObject* templateObject, uint32_t length)
{
bool emitted = false;
if (!IsCallOpcode(JSOp(*pc)))
startTrackingOptimizations();
if (!forceInlineCaches()) {
@ -5881,13 +5900,16 @@ IonBuilder::newObjectTryTemplateObject(bool* emitted, JSObject* templateObject)
{
MOZ_ASSERT(*emitted == false);
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationAttempt(TrackedStrategy::NewObject_TemplateObject);
if (!templateObject) {
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationOutcome(TrackedOutcome::NoTemplateObject);
return Ok();
}
if (templateObject->is<PlainObject>() && templateObject->as<PlainObject>().hasDynamicSlots()) {
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationOutcome(TrackedOutcome::TemplateObjectIsPlainObjectWithDynamicSlots);
return Ok();
}
@ -5910,6 +5932,7 @@ IonBuilder::newObjectTryTemplateObject(bool* emitted, JSObject* templateObject)
MOZ_TRY(resumeAfter(ins));
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationSuccess();
*emitted = true;
return Ok();
@ -5925,6 +5948,7 @@ IonBuilder::newObjectTrySharedStub(bool* emitted)
if (JitOptions.disableSharedStubs)
return Ok();
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationAttempt(TrackedStrategy::NewObject_SharedCache);
MInstruction* stub = MNullarySharedStub::New(alloc());
@ -5937,6 +5961,7 @@ IonBuilder::newObjectTrySharedStub(bool* emitted)
current->add(unbox);
current->push(unbox);
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationSuccess();
*emitted = true;
return Ok();
@ -5948,6 +5973,7 @@ IonBuilder::newObjectTryVM(bool* emitted, JSObject* templateObject)
// Emit a VM call.
MOZ_ASSERT(JSOp(*pc) == JSOP_NEWOBJECT || JSOp(*pc) == JSOP_NEWINIT);
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationAttempt(TrackedStrategy::NewObject_Call);
gc::InitialHeap heap = gc::DefaultHeap;
@ -5967,6 +5993,7 @@ IonBuilder::newObjectTryVM(bool* emitted, JSObject* templateObject)
MOZ_TRY(resumeAfter(ins));
if (!IsCallOpcode(JSOp(*pc)))
trackOptimizationSuccess();
*emitted = true;
return Ok();

Просмотреть файл

@ -820,6 +820,18 @@ IonCacheIRCompiler::emitLoadEnvironmentDynamicSlotResult()
MOZ_CRASH("Baseline-specific op");
}
bool
IonCacheIRCompiler::emitStoreFixedSlot()
{
MOZ_CRASH("Baseline-specific op");
}
bool
IonCacheIRCompiler::emitStoreDynamicSlot()
{
MOZ_CRASH("Baseline-specific op");
}
bool
IonCacheIRCompiler::emitLoadTypedObjectResult()
{

Просмотреть файл

@ -101,19 +101,18 @@ ControlFlowGraph::init(TempAllocator& alloc, const CFGBlockVector& blocks)
switch (ins->type()) {
case CFGControlInstruction::Type_Goto: {
CFGBlock* successor = &blocks_[ins->getSuccessor(0)->id()];
copy = CFGGoto::New(alloc, successor, ins->toGoto()->popAmount());
copy = CFGGoto::CopyWithNewTargets(alloc, ins->toGoto(), successor);
break;
}
case CFGControlInstruction::Type_BackEdge: {
CFGBlock* successor = &blocks_[ins->getSuccessor(0)->id()];
copy = CFGBackEdge::New(alloc, successor);
copy = CFGBackEdge::CopyWithNewTargets(alloc, ins->toBackEdge(), successor);
break;
}
case CFGControlInstruction::Type_LoopEntry: {
CFGLoopEntry* old = ins->toLoopEntry();
CFGBlock* successor = &blocks_[ins->getSuccessor(0)->id()];
copy = CFGLoopEntry::New(alloc, successor, old->canOsr(), old->stackPhiCount(),
old->loopStopPc());
copy = CFGLoopEntry::CopyWithNewTargets(alloc, old, successor);
break;
}
case CFGControlInstruction::Type_Throw: {
@ -124,7 +123,7 @@ ControlFlowGraph::init(TempAllocator& alloc, const CFGBlockVector& blocks)
CFGTest* old = ins->toTest();
CFGBlock* trueBranch = &blocks_[old->trueBranch()->id()];
CFGBlock* falseBranch = &blocks_[old->falseBranch()->id()];
copy = CFGTest::New(alloc, trueBranch, falseBranch, old->mustKeepCondition());
copy = CFGTest::CopyWithNewTargets(alloc, old, trueBranch, falseBranch);
break;
}
case CFGControlInstruction::Type_Compare: {
@ -148,7 +147,7 @@ ControlFlowGraph::init(TempAllocator& alloc, const CFGBlockVector& blocks)
CFGBlock* merge = nullptr;
if (old->numSuccessors() == 2)
merge = &blocks_[old->afterTryCatchBlock()->id()];
copy = CFGTry::New(alloc, tryBlock, old->catchStartPc(), merge);
copy = CFGTry::CopyWithNewTargets(alloc, old, tryBlock, merge);
break;
}
case CFGControlInstruction::Type_TableSwitch: {
@ -338,6 +337,7 @@ ControlFlowGenerator::snoopControlFlow(JSOp op)
return processTry();
case JSOP_OPTIMIZE_SPREADCALL:
case JSOP_THROWMSG:
// Not implemented yet.
return ControlStatus::Abort;

Просмотреть файл

@ -219,6 +219,12 @@ class CFGTry : public CFGControlInstruction
CFG_CONTROL_HEADER(Try)
TRIVIAL_CFG_NEW_WRAPPERS
static CFGTry* CopyWithNewTargets(TempAllocator& alloc, CFGTry* old,
CFGBlock* tryBlock, CFGBlock* merge)
{
return new(alloc) CFGTry(tryBlock, old->catchStartPc(), merge);
}
size_t numSuccessors() const final override {
return mergePoint_ ? 2 : 1;
}
@ -360,7 +366,6 @@ class CFGCompare : public CFGAryControlInstruction<2>
return new(alloc) CFGCompare(succ1, old->truePopAmount(), succ2, old->falsePopAmount());
}
CFGBlock* trueBranch() const {
return getSuccessor(0);
}
@ -406,6 +411,12 @@ class CFGTest : public CFGAryControlInstruction<2>
CFG_CONTROL_HEADER(Test);
TRIVIAL_CFG_NEW_WRAPPERS
static CFGTest* CopyWithNewTargets(TempAllocator& alloc, CFGTest* old,
CFGBlock* succ1, CFGBlock* succ2)
{
return new(alloc) CFGTest(succ1, succ2, old->mustKeepCondition());
}
CFGBlock* trueBranch() const {
return getSuccessor(0);
}
@ -483,7 +494,7 @@ class CFGUnaryControlInstruction : public CFGAryControlInstruction<1>
*/
class CFGGoto : public CFGUnaryControlInstruction
{
size_t popAmount_;
const size_t popAmount_;
explicit CFGGoto(CFGBlock* block)
: CFGUnaryControlInstruction(block),
@ -499,6 +510,11 @@ class CFGGoto : public CFGUnaryControlInstruction
CFG_CONTROL_HEADER(Goto);
TRIVIAL_CFG_NEW_WRAPPERS
static CFGGoto* CopyWithNewTargets(TempAllocator& alloc, CFGGoto* old, CFGBlock* block)
{
return new(alloc) CFGGoto(block, old->popAmount());
}
size_t popAmount() const {
return popAmount_;
}
@ -521,6 +537,11 @@ class CFGBackEdge : public CFGUnaryControlInstruction
public:
CFG_CONTROL_HEADER(BackEdge);
TRIVIAL_CFG_NEW_WRAPPERS
static CFGBackEdge* CopyWithNewTargets(TempAllocator& alloc, CFGBackEdge* old, CFGBlock* block)
{
return new(alloc) CFGBackEdge(block);
}
};
/**
@ -556,6 +577,13 @@ class CFGLoopEntry : public CFGUnaryControlInstruction
CFG_CONTROL_HEADER(LoopEntry);
TRIVIAL_CFG_NEW_WRAPPERS
static CFGLoopEntry* CopyWithNewTargets(TempAllocator& alloc, CFGLoopEntry* old,
CFGBlock* loopEntry)
{
return new(alloc) CFGLoopEntry(loopEntry, old->canOsr(), old->stackPhiCount(),
old->loopStopPc());
}
void setCanOsr() {
canOsr_ = true;
}

Просмотреть файл

@ -42,6 +42,7 @@ IonIC::scratchRegisterForEntryJump()
return output.hasValue() ? output.valueReg().scratchReg() : output.typedReg().gpr();
}
case CacheKind::GetName:
case CacheKind::SetProp:
MOZ_CRASH("Baseline-specific for now");
}

Просмотреть файл

@ -20,8 +20,8 @@ struct LabelBase
int32_t offset_ : 31;
bool bound_ : 1;
// Disallow assignment.
void operator =(const LabelBase& label);
void operator =(const LabelBase& label) = delete;
public:
static const int32_t INVALID_OFFSET = -1;

Просмотреть файл

@ -488,7 +488,7 @@ class MacroAssembler : public MacroAssemblerSpecific
CodeOffset call(Register reg) PER_SHARED_ARCH;
CodeOffset call(Label* label) PER_SHARED_ARCH;
void call(const Address& addr) DEFINED_ON(x86_shared);
void call(const Address& addr) DEFINED_ON(x86_shared, arm, arm64);
void call(ImmWord imm) PER_SHARED_ARCH;
// Call a target native function, which is neither traceable nor movable.
void call(ImmPtr imm) PER_SHARED_ARCH;

Просмотреть файл

@ -193,9 +193,14 @@ ICStub::NonCacheIRStubMakesGCCalls(Kind kind)
bool
ICStub::makesGCCalls() const
{
if (isCacheIR_Monitored())
switch (kind()) {
case CacheIR_Monitored:
return toCacheIR_Monitored()->stubInfo()->makesGCCalls();
case CacheIR_Updated:
return toCacheIR_Updated()->stubInfo()->makesGCCalls();
default:
return NonCacheIRStubMakesGCCalls(kind());
}
}
void
@ -351,12 +356,6 @@ ICStub::trace(JSTracer* trc)
TraceEdge(trc, &constantStub->value(), "baseline-getintrinsic-constant-value");
break;
}
case ICStub::SetProp_Native: {
ICSetProp_Native* propStub = toSetProp_Native();
TraceEdge(trc, &propStub->shape(), "baseline-setpropnative-stub-shape");
TraceEdge(trc, &propStub->group(), "baseline-setpropnative-stub-group");
break;
}
case ICStub::SetProp_NativeAdd: {
ICSetProp_NativeAdd* propStub = toSetProp_NativeAdd();
TraceEdge(trc, &propStub->group(), "baseline-setpropnativeadd-stub-group");
@ -425,6 +424,12 @@ ICStub::trace(JSTracer* trc)
case ICStub::CacheIR_Monitored:
TraceCacheIRStub(trc, this, toCacheIR_Monitored()->stubInfo());
break;
case ICStub::CacheIR_Updated: {
ICCacheIR_Updated* stub = toCacheIR_Updated();
TraceEdge(trc, &stub->updateStubId(), "baseline-updated-id");
TraceCacheIRStub(trc, this, stub->stubInfo());
break;
}
default:
break;
}
@ -731,8 +736,9 @@ ICStubCompiler::PushStubPayload(MacroAssembler& masm, Register scratch)
}
void
ICStubCompiler::emitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, ValueOperand val,
Register scratch, LiveGeneralRegisterSet saveRegs)
BaselineEmitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, ValueOperand val,
Register scratch, LiveGeneralRegisterSet saveRegs,
JSRuntime* rt)
{
Label skipBarrier;
masm.branchPtrInNurseryChunk(Assembler::Equal, obj, scratch, &skipBarrier);
@ -745,7 +751,7 @@ ICStubCompiler::emitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, Val
saveRegs.set() = GeneralRegisterSet::Intersect(saveRegs.set(), GeneralRegisterSet::Volatile());
masm.PushRegsInMask(saveRegs);
masm.setupUnalignedABICall(scratch);
masm.movePtr(ImmPtr(cx->runtime()), scratch);
masm.movePtr(ImmPtr(rt), scratch);
masm.passABIArg(scratch);
masm.passABIArg(obj);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier));
@ -1975,7 +1981,7 @@ StripPreliminaryObjectStubs(JSContext* cx, ICFallbackStub* stub)
for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) {
if (iter->isCacheIR_Monitored() && iter->toCacheIR_Monitored()->hasPreliminaryObject())
iter.unlink(cx);
else if (iter->isSetProp_Native() && iter->toSetProp_Native()->hasPreliminaryObject())
else if (iter->isCacheIR_Updated() && iter->toCacheIR_Updated()->hasPreliminaryObject())
iter.unlink(cx);
}
}

Просмотреть файл

@ -508,7 +508,7 @@ class ICStub
return (k > INVALID) && (k < LIMIT);
}
static bool IsCacheIRKind(Kind k) {
return k == CacheIR_Monitored;
return k == CacheIR_Monitored || k == CacheIR_Updated;
}
static const char* KindString(Kind k) {
@ -956,6 +956,36 @@ class ICUpdatedStub : public ICStub
}
};
class ICCacheIR_Updated : public ICUpdatedStub
{
const CacheIRStubInfo* stubInfo_;
GCPtrId updateStubId_;
public:
ICCacheIR_Updated(JitCode* stubCode, const CacheIRStubInfo* stubInfo)
: ICUpdatedStub(ICStub::CacheIR_Updated, stubCode),
stubInfo_(stubInfo),
updateStubId_(JSID_EMPTY)
{}
GCPtrId& updateStubId() {
return updateStubId_;
}
void notePreliminaryObject() {
extra_ = 1;
}
bool hasPreliminaryObject() const {
return extra_;
}
const CacheIRStubInfo* stubInfo() const {
return stubInfo_;
}
uint8_t* stubDataStart();
};
// Base class for stubcode compilers.
class ICStubCompiler
{
@ -1062,9 +1092,6 @@ class ICStubCompiler
}
protected:
void emitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, ValueOperand val,
Register scratch, LiveGeneralRegisterSet saveRegs);
template <typename T, typename... Args>
T* newStub(Args&&... args) {
return ICStub::New<T>(cx, mozilla::Forward<Args>(args)...);
@ -1086,6 +1113,10 @@ class ICStubCompiler
}
};
void BaselineEmitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, ValueOperand val,
Register scratch, LiveGeneralRegisterSet saveRegs,
JSRuntime* rt);
class SharedStubInfo
{
BaselineFrame* maybeFrame_;

Просмотреть файл

@ -38,6 +38,7 @@ namespace jit {
_(GetProp_Generic) \
\
_(CacheIR_Monitored) \
_(CacheIR_Updated) \
\
} // namespace jit

Просмотреть файл

@ -5021,6 +5021,13 @@ MacroAssembler::call(wasm::SymbolicAddress imm)
call(CallReg);
}
void
MacroAssembler::call(const Address& addr)
{
loadPtr(addr, CallReg);
call(CallReg);
}
void
MacroAssembler::call(JitCode* c)
{

Просмотреть файл

@ -528,6 +528,16 @@ MacroAssembler::call(wasm::SymbolicAddress imm)
call(scratch);
}
void
MacroAssembler::call(const Address& addr)
{
vixl::UseScratchRegisterScope temps(this);
const Register scratch = temps.AcquireX().asUnsized();
syncStackPtr();
loadPtr(addr, scratch);
call(scratch);
}
void
MacroAssembler::call(JitCode* c)
{

Просмотреть файл

@ -6920,7 +6920,7 @@ DispatchPointerFromMouseOrTouch(PresShell* aShell,
int16_t button = mouseEvent->button;
switch (mouseEvent->mMessage) {
case eMouseMove:
button = -1;
button = WidgetMouseEvent::eNoButton;
pointerMessage = ePointerMove;
break;
case eMouseUp:
@ -6951,14 +6951,18 @@ DispatchPointerFromMouseOrTouch(PresShell* aShell,
PostHandlePointerEventsPreventDefault(&event, aEvent);
} else if (aEvent->mClass == eTouchEventClass) {
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
int16_t button = WidgetMouseEvent::eLeftButton;
int16_t buttons = WidgetMouseEvent::eLeftButtonFlag;
// loop over all touches and dispatch pointer events on each touch
// copy the event
switch (touchEvent->mMessage) {
case eTouchMove:
pointerMessage = ePointerMove;
button = WidgetMouseEvent::eNoButton;
break;
case eTouchEnd:
pointerMessage = ePointerUp;
buttons = WidgetMouseEvent::eNoButtonFlag;
break;
case eTouchStart:
pointerMessage = ePointerDown;
@ -6989,9 +6993,8 @@ DispatchPointerFromMouseOrTouch(PresShell* aShell,
event.mTime = touchEvent->mTime;
event.mTimeStamp = touchEvent->mTimeStamp;
event.mFlags = touchEvent->mFlags;
event.button = WidgetMouseEvent::eLeftButton;
event.buttons = pointerMessage == ePointerUp ?
0 : WidgetMouseEvent::eLeftButtonFlag;
event.button = button;
event.buttons = buttons;
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
event.convertToPointer = touch->convertToPointer = false;
PreHandlePointerEventsPreventDefault(&event, aEvent);

Просмотреть файл

@ -273,8 +273,8 @@ function runTests() {
cwu.sendMouseEvent("mousemove", 4, 4, 0, 0, 0, false, 0, 0);
d1.onpointermove = function(e) {
is(e.buttons, 1, "Buttons must be 1 on pointer generated from touch event");
is(e.button, 0, "Button must be 0 on pointer generated from touch eventd");
is(e.buttons, 1, "Buttons must be 1 on pointermove generated from touch event");
is(e.button, -1, "Button must be -1 on pointermove generated from touch event");
is(e.pointerType, "touch", "Pointer type must be touch");
};
sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d1, cwu, 2), 0);

Просмотреть файл

@ -369,17 +369,7 @@ class RefTest(object):
# Enable leaks detection to its own log file.
self.leakLogFile = os.path.join(profileDir, "runreftest_leaks.log")
# Leak checking was broken in reftest unnoticed for a length of time. During
# this time, a leak slipped into the crashtest suite. The leak checking was
# fixed by bug 1325148, but it couldn't land until the regression in crashtest
# was also fixed or backed out. Rather than waiting and risking new regressions,
# temporarily disable leak checking in crashtest. Fix is tracked by bug 1325215.
if options.suite == 'crashtest' and mozinfo.info['os'] == 'linux':
self.log.warning('WARNING | leakcheck disabled due to bug 1325215')
else:
browserEnv["XPCOM_MEM_BLOAT_LOG"] = self.leakLogFile
return browserEnv
def killNamedOrphans(self, pname):

Просмотреть файл

@ -1,79 +0,0 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AnnexB.h"
#include "BigEndian.h"
#include <cstring>
using mozilla::BigEndian;
static const uint8_t kAnnexBDelimiter[] = { 0, 0, 0, 1 };
/* static */ void
AnnexB::ConvertFrameInPlace(std::vector<uint8_t>& aBuffer)
{
for (size_t i = 0; i < aBuffer.size() - 4 - sizeof(kAnnexBDelimiter) + 1; ) {
uint32_t nalLen = BigEndian::readUint32(&aBuffer[i]);
memcpy(&aBuffer[i], kAnnexBDelimiter, sizeof(kAnnexBDelimiter));
i += nalLen + 4;
}
}
static void
ConvertParamSetToAnnexB(std::vector<uint8_t>::const_iterator& aIter,
size_t aCount,
std::vector<uint8_t>& aOutAnnexB)
{
for (size_t i = 0; i < aCount; i++) {
aOutAnnexB.insert(aOutAnnexB.end(), kAnnexBDelimiter,
kAnnexBDelimiter + sizeof(kAnnexBDelimiter));
uint16_t len = BigEndian::readUint16(&*aIter); aIter += 2;
aOutAnnexB.insert(aOutAnnexB.end(), aIter, aIter + len); aIter += len;
}
}
/* static */ void
AnnexB::ConvertConfig(const std::vector<uint8_t>& aBuffer,
std::vector<uint8_t>& aOutAnnexB)
{
// Skip past irrelevant headers
auto it = aBuffer.begin() + 5;
if (it >= aBuffer.end()) {
return;
}
size_t count = *(it++) & 31;
// Check that we have enough bytes for the Annex B conversion
// and the next size field. Bail if not.
if (it + count * 2 >= aBuffer.end()) {
return;
}
ConvertParamSetToAnnexB(it, count, aOutAnnexB);
// Check that we have enough bytes for the Annex B conversion.
count = *(it++);
if (it + count * 2 > aBuffer.end()) {
aOutAnnexB.clear();
return;
}
ConvertParamSetToAnnexB(it, count, aOutAnnexB);
}

Просмотреть файл

@ -1,32 +0,0 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __AnnexB_h__
#define __AnnexB_h__
#include <cstdint>
#include <vector>
class AnnexB
{
public:
static void ConvertFrameInPlace(std::vector<uint8_t>& aBuffer);
static void ConvertConfig(const std::vector<uint8_t>& aBuffer,
std::vector<uint8_t>& aOutAnnexB);
};
#endif // __AnnexB_h__

Просмотреть файл

@ -1,45 +0,0 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ClearKeyAsyncShutdown.h"
#include "gmp-task-utils.h"
ClearKeyAsyncShutdown::ClearKeyAsyncShutdown(GMPAsyncShutdownHost *aHostAPI)
: mHost(aHostAPI)
{
CK_LOGD("ClearKeyAsyncShutdown::ClearKeyAsyncShutdown");
AddRef();
}
ClearKeyAsyncShutdown::~ClearKeyAsyncShutdown()
{
CK_LOGD("ClearKeyAsyncShutdown::~ClearKeyAsyncShutdown");
}
void ShutdownTask(ClearKeyAsyncShutdown* aSelf, GMPAsyncShutdownHost* aHost)
{
// Dumb implementation that just immediately reports completion.
// Real GMPs should ensure they are properly shutdown.
CK_LOGD("ClearKeyAsyncShutdown::BeginShutdown calling ShutdownComplete");
aHost->ShutdownComplete();
aSelf->Release();
}
void ClearKeyAsyncShutdown::BeginShutdown()
{
CK_LOGD("ClearKeyAsyncShutdown::BeginShutdown dispatching asynchronous shutdown task");
GetPlatform()->runonmainthread(WrapTaskNM(ShutdownTask, this, mHost));
}

Просмотреть файл

@ -1,37 +0,0 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeyAsyncShutdown_h__
#define __ClearKeyAsyncShutdown_h__
#include "gmp-api/gmp-async-shutdown.h"
#include "RefCounted.h"
class ClearKeyAsyncShutdown : public GMPAsyncShutdown
, public RefCounted
{
public:
explicit ClearKeyAsyncShutdown(GMPAsyncShutdownHost *aHostAPI);
void BeginShutdown() override;
private:
virtual ~ClearKeyAsyncShutdown();
GMPAsyncShutdownHost* mHost;
};
#endif // __ClearKeyAsyncShutdown_h__

Просмотреть файл

@ -0,0 +1,196 @@
#include "ClearKeyCDM.h"
#include "ClearKeyUtils.h"
using namespace cdm;
ClearKeyCDM::ClearKeyCDM(Host_8* aHost)
{
mHost = aHost;
mSessionManager = new ClearKeySessionManager(mHost);
}
void
ClearKeyCDM::Initialize(bool aAllowDistinctiveIdentifier,
bool aAllowPersistentState)
{
mSessionManager->Init(aAllowDistinctiveIdentifier,
aAllowPersistentState);
}
void
ClearKeyCDM::SetServerCertificate(uint32_t aPromiseId,
const uint8_t* aServerCertificateData,
uint32_t aServerCertificateDataSize)
{
mSessionManager->SetServerCertificate(aPromiseId,
aServerCertificateData,
aServerCertificateDataSize);
}
void
ClearKeyCDM::CreateSessionAndGenerateRequest(uint32_t aPromiseId,
SessionType aSessionType,
InitDataType aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize)
{
mSessionManager->CreateSession(aPromiseId,
aInitDataType,
aInitData,
aInitDataSize,
aSessionType);
}
void
ClearKeyCDM::LoadSession(uint32_t aPromiseId,
SessionType aSessionType,
const char* aSessionId,
uint32_t aSessionIdSize)
{
mSessionManager->LoadSession(aPromiseId,
aSessionId,
aSessionIdSize);
}
void
ClearKeyCDM::UpdateSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize,
const uint8_t* aResponse,
uint32_t aResponseSize)
{
mSessionManager->UpdateSession(aPromiseId,
aSessionId,
aSessionIdSize,
aResponse,
aResponseSize);
}
void
ClearKeyCDM::CloseSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize)
{
mSessionManager->CloseSession(aPromiseId,
aSessionId,
aSessionIdSize);
}
void
ClearKeyCDM::RemoveSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize)
{
mSessionManager->RemoveSession(aPromiseId,
aSessionId,
aSessionIdSize);
}
void
ClearKeyCDM::TimerExpired(void* aContext)
{
// Clearkey is not interested in timers, so this method has not been
// implemented.
assert(false);
}
Status
ClearKeyCDM::Decrypt(const InputBuffer& aEncryptedBuffer,
DecryptedBlock* aDecryptedBuffer)
{
return mSessionManager->Decrypt(aEncryptedBuffer, aDecryptedBuffer);
}
Status
ClearKeyCDM::InitializeAudioDecoder(
const AudioDecoderConfig& aAudioDecoderConfig)
{
// Audio decoding is not supported by Clearkey because Widevine doesn't
// support it and Clearkey's raison d'etre is to provide test coverage
// for paths that Widevine will exercise in the wild.
return Status::kDecodeError;
}
Status
ClearKeyCDM::InitializeVideoDecoder(
const VideoDecoderConfig& aVideoDecoderConfig)
{
#ifdef ENABLE_WMF
mVideoDecoder = new VideoDecoder(mHost);
return mVideoDecoder->InitDecode(aVideoDecoderConfig);
#else
return Status::kDecodeError;
#endif
}
void
ClearKeyCDM::DeinitializeDecoder(StreamType aDecoderType)
{
#ifdef ENABLE_WMF
if (aDecoderType == StreamType::kStreamTypeVideo) {
mVideoDecoder->DecodingComplete();
mVideoDecoder = nullptr;
}
#endif
}
void
ClearKeyCDM::ResetDecoder(StreamType aDecoderType)
{
#ifdef ENABLE_WMF
if (aDecoderType == StreamType::kStreamTypeVideo) {
mVideoDecoder->Reset();
}
#endif
}
Status
ClearKeyCDM::DecryptAndDecodeFrame(const InputBuffer& aEncryptedBuffer,
VideoFrame* aVideoFrame)
{
#ifdef ENABLE_WMF
return mVideoDecoder->Decode(aEncryptedBuffer, aVideoFrame);
#else
return Status::kDecodeError;
#endif
}
Status
ClearKeyCDM::DecryptAndDecodeSamples(const InputBuffer& aEncryptedBuffer,
AudioFrames* aAudioFrame)
{
// Audio decoding is not supported by Clearkey because Widevine doesn't
// support it and Clearkey's raison d'etre is to provide test coverage
// for paths that Widevine will exercise in the wild.
return Status::kDecodeError;
}
void
ClearKeyCDM::OnPlatformChallengeResponse(
const PlatformChallengeResponse& aResponse)
{
// This function should never be called and is not supported.
assert(false);
}
void
ClearKeyCDM::OnQueryOutputProtectionStatus(QueryResult aResult,
uint32_t aLinkMask,
uint32_t aOutputProtectionMask)
{
// This function should never be called and is not supported.
assert(false);
}
void
ClearKeyCDM::Destroy()
{
mSessionManager->DecryptingComplete();
#ifdef ENABLE_WMF
// If we have called 'DeinitializeDecoder' mVideoDecoder will be null.
if (mVideoDecoder) {
mVideoDecoder->DecodingComplete();
}
#endif
}

Просмотреть файл

@ -0,0 +1,98 @@
#ifndef ClearKeyCDM_h_
#define ClearKeyCDM_h_
#include "ClearKeySessionManager.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#ifdef ENABLE_WMF
#include "WMFUtils.h"
#include "VideoDecoder.h"
#endif
class ClearKeyCDM : public cdm::ContentDecryptionModule_8
{
private:
RefPtr<ClearKeySessionManager> mSessionManager;
#ifdef ENABLE_WMF
RefPtr<VideoDecoder> mVideoDecoder;
#endif
protected:
cdm::Host_8* mHost;
public:
explicit ClearKeyCDM(cdm::Host_8* mHost);
void Initialize(bool aAllowDistinctiveIdentifier,
bool aAllowPersistentState) override;
void SetServerCertificate(uint32_t aPromiseId,
const uint8_t* aServerCertificateData,
uint32_t aServerCertificateDataSize)
override;
void CreateSessionAndGenerateRequest(uint32_t aPromiseId,
cdm::SessionType aSessionType,
cdm::InitDataType aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize)
override;
void LoadSession(uint32_t aPromiseId,
cdm::SessionType aSessionType,
const char* aSessionId,
uint32_t aSessionIdSize) override;
void UpdateSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize,
const uint8_t* aResponse,
uint32_t aResponseSize) override;
void CloseSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize) override;
void RemoveSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdSize) override;
void TimerExpired(void* aContext) override;
cdm::Status Decrypt(const cdm::InputBuffer& aEncryptedBuffer,
cdm::DecryptedBlock* aDecryptedBuffer) override;
cdm::Status InitializeAudioDecoder(
const cdm::AudioDecoderConfig& aAudioDecoderConfig) override;
cdm::Status InitializeVideoDecoder(
const cdm::VideoDecoderConfig& aVideoDecoderConfig) override;
void DeinitializeDecoder(cdm::StreamType aDecoderType) override;
void ResetDecoder(cdm::StreamType aDecoderType) override;
cdm::Status DecryptAndDecodeFrame(
const cdm::InputBuffer& aEncryptedBuffer,
cdm::VideoFrame* aVideoFrame) override;
cdm::Status DecryptAndDecodeSamples(
const cdm::InputBuffer& aEncryptedBuffer,
cdm::AudioFrames* aAudioFrame) override;
void OnPlatformChallengeResponse(
const cdm::PlatformChallengeResponse& aResponse) override;
void
OnQueryOutputProtectionStatus(cdm::QueryResult aResult,
uint32_t aLinkMask,
uint32_t aOutputProtectionMask) override;
void Destroy() override;
};
#endif

Просмотреть файл

@ -14,13 +14,15 @@
* limitations under the License.
*/
#include "ClearKeyDecryptionManager.h"
#include "psshparser/PsshParser.h"
#include <assert.h>
#include <string.h>
#include <vector>
#include "ClearKeyDecryptionManager.h"
#include "psshparser/PsshParser.h"
#include "gmp-api/gmp-decryption.h"
#include <assert.h>
using namespace cdm;
class ClearKeyDecryptor : public RefCounted
{
@ -30,7 +32,7 @@ public:
void InitKey(const Key& aKey);
bool HasKey() const { return !!mKey.size(); }
GMPErr Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
Status Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata);
const Key& DecryptionKey() const { return mKey; }
@ -42,7 +44,8 @@ private:
};
/* static */ ClearKeyDecryptionManager* ClearKeyDecryptionManager::sInstance = nullptr;
/* static */ ClearKeyDecryptionManager*
ClearKeyDecryptionManager::sInstance = nullptr;
/* static */ ClearKeyDecryptionManager*
ClearKeyDecryptionManager::Get()
@ -73,14 +76,17 @@ ClearKeyDecryptionManager::~ClearKeyDecryptionManager()
bool
ClearKeyDecryptionManager::HasSeenKeyId(const KeyId& aKeyId) const
{
CK_LOGD("ClearKeyDecryptionManager::SeenKeyId %s", mDecryptors.find(aKeyId) != mDecryptors.end() ? "t" : "f");
CK_LOGD("ClearKeyDecryptionManager::SeenKeyId %s",
mDecryptors.find(aKeyId) != mDecryptors.end() ? "t" : "f");
return mDecryptors.find(aKeyId) != mDecryptors.end();
}
bool
ClearKeyDecryptionManager::IsExpectingKeyForKeyId(const KeyId& aKeyId) const
{
CK_LOGD("ClearKeyDecryptionManager::IsExpectingKeyForId %08x...", *(uint32_t*)&aKeyId[0]);
CK_LOGARRAY("ClearKeyDecryptionManager::IsExpectingKeyForId ",
aKeyId.data(),
aKeyId.size());
const auto& decryptor = mDecryptors.find(aKeyId);
return decryptor != mDecryptors.end() && !decryptor->second->HasKey();
}
@ -103,16 +109,23 @@ ClearKeyDecryptionManager::GetDecryptionKey(const KeyId& aKeyId)
void
ClearKeyDecryptionManager::InitKey(KeyId aKeyId, Key aKey)
{
CK_LOGD("ClearKeyDecryptionManager::InitKey %08x...", *(uint32_t*)&aKeyId[0]);
CK_LOGD("ClearKeyDecryptionManager::InitKey ",
aKeyId.data(),
aKeyId.size());
if (IsExpectingKeyForKeyId(aKeyId)) {
CK_LOGARRAY("Initialized Key ", aKeyId.data(), aKeyId.size());
mDecryptors[aKeyId]->InitKey(aKey);
} else {
CK_LOGARRAY("Failed to initialize key ", aKeyId.data(), aKeyId.size());
}
}
void
ClearKeyDecryptionManager::ExpectKeyId(KeyId aKeyId)
{
CK_LOGD("ClearKeyDecryptionManager::ExpectKeyId %08x...", *(uint32_t*)&aKeyId[0]);
CK_LOGD("ClearKeyDecryptionManager::ExpectKeyId ",
aKeyId.data(),
aKeyId.size());
if (!HasSeenKeyId(aKeyId)) {
mDecryptors[aKeyId] = new ClearKeyDecryptor();
}
@ -131,23 +144,31 @@ ClearKeyDecryptionManager::ReleaseKeyId(KeyId aKeyId)
}
}
GMPErr
Status
ClearKeyDecryptionManager::Decrypt(std::vector<uint8_t>& aBuffer,
const CryptoMetaData& aMetadata)
{
return Decrypt(&aBuffer[0], aBuffer.size(), aMetadata);
}
GMPErr
Status
ClearKeyDecryptionManager::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata)
{
CK_LOGD("ClearKeyDecryptionManager::Decrypt");
if (!HasKeyForKeyId(aMetadata.mKeyId)) {
return GMPNoKeyErr;
CK_LOGARRAY("Unable to find decryptor for keyId: ",
aMetadata.mKeyId.data(),
aMetadata.mKeyId.size());
return Status::kNoKey;
}
return mDecryptors[aMetadata.mKeyId]->Decrypt(aBuffer, aBufferSize, aMetadata);
CK_LOGARRAY("Found decryptor for keyId: ",
aMetadata.mKeyId.data(),
aMetadata.mKeyId.size());
return mDecryptors[aMetadata.mKeyId]->Decrypt(aBuffer,
aBufferSize,
aMetadata);
}
ClearKeyDecryptor::ClearKeyDecryptor()
@ -158,7 +179,9 @@ ClearKeyDecryptor::ClearKeyDecryptor()
ClearKeyDecryptor::~ClearKeyDecryptor()
{
if (HasKey()) {
CK_LOGD("ClearKeyDecryptor dtor; key = %08x...", *(uint32_t*)&mKey[0]);
CK_LOGARRAY("ClearKeyDecryptor dtor; key = ",
mKey.data(),
mKey.size());
} else {
CK_LOGD("ClearKeyDecryptor dtor");
}
@ -170,7 +193,7 @@ ClearKeyDecryptor::InitKey(const Key& aKey)
mKey = aKey;
}
GMPErr
Status
ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata)
{
@ -189,7 +212,7 @@ ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
uint32_t cipherBytes = aMetadata.mCipherBytes[i];
if (data + cipherBytes > aBuffer + aBufferSize) {
// Trying to read past the end of the buffer!
return GMPCryptoErr;
return Status::kDecryptError;
}
memcpy(iter, data, cipherBytes);
@ -227,5 +250,5 @@ ClearKeyDecryptor::Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
memcpy(aBuffer, &tmp[0], aBufferSize);
}
return GMPNoErr;
return Status::kSuccess;
}

Просмотреть файл

@ -17,32 +17,43 @@
#ifndef __ClearKeyDecryptionManager_h__
#define __ClearKeyDecryptionManager_h__
#include <map>
#include "ClearKeyUtils.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "RefCounted.h"
#include <map>
class ClearKeyDecryptor;
class CryptoMetaData {
class CryptoMetaData
{
public:
CryptoMetaData() {}
explicit CryptoMetaData(const GMPEncryptedBufferMetadata* aCrypto)
explicit CryptoMetaData(const cdm::InputBuffer* aInputBuffer)
{
Init(aCrypto);
Init(aInputBuffer);
}
void Init(const GMPEncryptedBufferMetadata* aCrypto)
void Init(const cdm::InputBuffer* aInputBuffer)
{
if (!aCrypto) {
if (!aInputBuffer) {
assert(!IsValid());
return;
}
Assign(mKeyId, aCrypto->KeyId(), aCrypto->KeyIdSize());
Assign(mIV, aCrypto->IV(), aCrypto->IVSize());
Assign(mClearBytes, aCrypto->ClearBytes(), aCrypto->NumSubsamples());
Assign(mCipherBytes, aCrypto->CipherBytes(), aCrypto->NumSubsamples());
Assign(mKeyId, aInputBuffer->key_id, aInputBuffer->key_id_size);
Assign(mIV, aInputBuffer->iv, aInputBuffer->iv_size);
for (uint32_t i = 0; i < aInputBuffer->num_subsamples; ++i) {
const cdm::SubsampleEntry& subsample = aInputBuffer->subsamples[i];
mCipherBytes.push_back(subsample.cipher_bytes);
mClearBytes.push_back(subsample.clear_bytes);
}
}
bool IsValid() const {
@ -59,7 +70,7 @@ public:
std::vector<uint8_t> mKeyId;
std::vector<uint8_t> mIV;
std::vector<uint16_t> mClearBytes;
std::vector<uint32_t> mClearBytes;
std::vector<uint32_t> mCipherBytes;
};
@ -85,13 +96,11 @@ public:
void ReleaseKeyId(KeyId aKeyId);
// Decrypts buffer *in place*.
GMPErr Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
cdm::Status Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
const CryptoMetaData& aMetadata);
GMPErr Decrypt(std::vector<uint8_t>& aBuffer,
cdm::Status Decrypt(std::vector<uint8_t>& aBuffer,
const CryptoMetaData& aMetadata);
void Shutdown();
private:
bool IsExpectingKeyForKeyId(const KeyId& aKeyId) const;

Просмотреть файл

@ -15,81 +15,123 @@
*/
#include "ClearKeyPersistence.h"
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "ClearKeySessionManager.h"
#include "RefCounted.h"
#include <stdint.h>
#include <string.h>
#include <set>
#include <vector>
#include <sstream>
#include <assert.h>
#include <stdint.h>
#include <sstream>
#include <string.h>
using namespace std;
using namespace cdm;
// Whether we've loaded the persistent session ids from GMPStorage yet.
enum PersistentKeyState {
UNINITIALIZED,
LOADING,
LOADED
};
static PersistentKeyState sPersistentKeyState = UNINITIALIZED;
void
ClearKeyPersistence::ReadAllRecordsFromIndex(function<void()>&& aOnComplete) {
// Clear what we think the index file contains, we're about to read it again.
mPersistentSessionIds.clear();
// Set of session Ids of the persistent sessions created or residing in
// storage.
static set<uint32_t> sPersistentSessionIds;
// Hold a reference to the persistence manager, so it isn't released before
// we try and use it.
RefPtr<ClearKeyPersistence> self(this);
function<void(const uint8_t*, uint32_t)> onIndexSuccess =
[self, aOnComplete] (const uint8_t* data, uint32_t size)
{
CK_LOGD("ClearKeyPersistence: Loaded index file!");
const char* charData = (const char*)data;
static vector<GMPTask*> sTasksBlockedOnSessionIdLoad;
static void
ReadAllRecordsFromIterator(GMPRecordIterator* aRecordIterator,
void* aUserArg,
GMPErr aStatus)
{
assert(sPersistentKeyState == LOADING);
if (GMP_SUCCEEDED(aStatus)) {
// Extract the record names which are valid uint32_t's; they're
// the persistent session ids.
const char* name = nullptr;
uint32_t len = 0;
while (GMP_SUCCEEDED(aRecordIterator->GetName(&name, &len))) {
if (ClearKeyUtils::IsValidSessionId(name, len)) {
assert(name[len] == 0);
sPersistentSessionIds.insert(atoi(name));
}
aRecordIterator->NextRecord();
stringstream ss(string(charData, charData + size));
string name;
while (getline(ss, name)) {
if (ClearKeyUtils::IsValidSessionId(name.data(), name.size())) {
self->mPersistentSessionIds.insert(atoi(name.c_str()));
}
}
sPersistentKeyState = LOADED;
aRecordIterator->Close();
for (size_t i = 0; i < sTasksBlockedOnSessionIdLoad.size(); i++) {
sTasksBlockedOnSessionIdLoad[i]->Run();
sTasksBlockedOnSessionIdLoad[i]->Destroy();
}
sTasksBlockedOnSessionIdLoad.clear();
self->mPersistentKeyState = PersistentKeyState::LOADED;
aOnComplete();
};
function<void()> onIndexFailed =
[self, aOnComplete] ()
{
CK_LOGD("ClearKeyPersistence: Failed to load index file (it might not exist");
self->mPersistentKeyState = PersistentKeyState::LOADED;
aOnComplete();
};
string filename = "index";
ReadData(mHost, filename, move(onIndexSuccess), move(onIndexFailed));
}
/* static */ void
ClearKeyPersistence::EnsureInitialized()
{
if (sPersistentKeyState == UNINITIALIZED) {
sPersistentKeyState = LOADING;
if (GMP_FAILED(EnumRecordNames(&ReadAllRecordsFromIterator))) {
sPersistentKeyState = LOADED;
void
ClearKeyPersistence::WriteIndex() {
function <void()> onIndexSuccess =
[] ()
{
CK_LOGD("ClearKeyPersistence: Wrote index file");
};
function <void()> onIndexFail =
[] ()
{
CK_LOGD("ClearKeyPersistence: Failed to write index file (this is bad)");
};
stringstream ss;
for (const uint32_t& sessionId : mPersistentSessionIds) {
ss << sessionId;
ss << '\n';
}
string dataString = ss.str();
uint8_t* dataArray = (uint8_t*)dataString.data();
vector<uint8_t> data(dataArray, dataArray + dataString.size());
string filename = "index";
WriteData(mHost,
filename,
data,
move(onIndexSuccess),
move(onIndexFail));
}
ClearKeyPersistence::ClearKeyPersistence(Host_8* aHost)
{
this->mHost = aHost;
}
void
ClearKeyPersistence::EnsureInitialized(bool aPersistentStateAllowed,
function<void()>&& aOnInitialized)
{
if (aPersistentStateAllowed &&
mPersistentKeyState == PersistentKeyState::UNINITIALIZED) {
mPersistentKeyState = LOADING;
ReadAllRecordsFromIndex(move(aOnInitialized));
} else {
mPersistentKeyState = PersistentKeyState::LOADED;
aOnInitialized();
}
}
/* static */ string
ClearKeyPersistence::GetNewSessionId(GMPSessionType aSessionType)
bool ClearKeyPersistence::IsLoaded() const
{
return mPersistentKeyState == PersistentKeyState::LOADED;
}
string
ClearKeyPersistence::GetNewSessionId(SessionType aSessionType)
{
static uint32_t sNextSessionId = 1;
// Ensure we don't re-use a session id that was persisted.
while (Contains(sPersistentSessionIds, sNextSessionId)) {
while (Contains(mPersistentSessionIds, sNextSessionId)) {
sNextSessionId++;
}
@ -98,8 +140,11 @@ ClearKeyPersistence::GetNewSessionId(GMPSessionType aSessionType)
ss << sNextSessionId;
ss >> sessionId;
if (aSessionType == kGMPPersistentSession) {
sPersistentSessionIds.insert(sNextSessionId);
if (aSessionType == SessionType::kPersistentLicense) {
mPersistentSessionIds.insert(sNextSessionId);
// Save the updated index file.
WriteIndex();
}
sNextSessionId++;
@ -107,154 +152,17 @@ ClearKeyPersistence::GetNewSessionId(GMPSessionType aSessionType)
return sessionId;
}
class CreateSessionTask : public GMPTask {
public:
CreateSessionTask(ClearKeySessionManager* aTarget,
uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const string& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType)
: mTarget(aTarget)
, mCreateSessionToken(aCreateSessionToken)
, mPromiseId(aPromiseId)
, mInitDataType(aInitDataType)
, mSessionType(aSessionType)
{
mInitData.insert(mInitData.end(),
aInitData,
aInitData + aInitDataSize);
}
virtual void Run() override {
mTarget->CreateSession(mCreateSessionToken,
mPromiseId,
mInitDataType.c_str(),
mInitDataType.size(),
&mInitData.front(),
mInitData.size(),
mSessionType);
}
virtual void Destroy() override {
delete this;
}
private:
RefPtr<ClearKeySessionManager> mTarget;
uint32_t mCreateSessionToken;
uint32_t mPromiseId;
const string mInitDataType;
vector<uint8_t> mInitData;
GMPSessionType mSessionType;
};
/* static */ bool
ClearKeyPersistence::DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const string& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType)
{
if (sPersistentKeyState >= LOADED) {
return false;
}
GMPTask* t = new CreateSessionTask(aInstance,
aCreateSessionToken,
aPromiseId,
aInitDataType,
aInitData,
aInitDataSize,
aSessionType);
sTasksBlockedOnSessionIdLoad.push_back(t);
return true;
}
class LoadSessionTask : public GMPTask {
public:
LoadSessionTask(ClearKeySessionManager* aTarget,
uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
: mTarget(aTarget)
, mPromiseId(aPromiseId)
, mSessionId(aSessionId, aSessionId + aSessionIdLength)
{
}
virtual void Run() override {
mTarget->LoadSession(mPromiseId,
mSessionId.c_str(),
mSessionId.size());
}
virtual void Destroy() override {
delete this;
}
private:
RefPtr<ClearKeySessionManager> mTarget;
uint32_t mPromiseId;
string mSessionId;
};
/* static */ bool
ClearKeyPersistence::DeferLoadSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength)
{
if (sPersistentKeyState >= LOADED) {
return false;
}
GMPTask* t = new LoadSessionTask(aInstance,
aPromiseId,
aSessionId,
aSessionIdLength);
sTasksBlockedOnSessionIdLoad.push_back(t);
return true;
}
/* static */ bool
bool
ClearKeyPersistence::IsPersistentSessionId(const string& aSessionId)
{
return Contains(sPersistentSessionIds, atoi(aSessionId.c_str()));
return Contains(mPersistentSessionIds, atoi(aSessionId.c_str()));
}
class LoadSessionFromKeysTask : public ReadContinuation {
public:
LoadSessionFromKeysTask(ClearKeySessionManager* aTarget,
const string& aSessionId,
uint32_t aPromiseId)
: mTarget(aTarget)
, mSessionId(aSessionId)
, mPromiseId(aPromiseId)
{
}
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aLength) override
{
mTarget->PersistentSessionDataLoaded(aStatus, mPromiseId, mSessionId, aData, aLength);
}
private:
RefPtr<ClearKeySessionManager> mTarget;
string mSessionId;
uint32_t mPromiseId;
};
/* static */ void
ClearKeyPersistence::LoadSessionData(ClearKeySessionManager* aInstance,
const string& aSid,
uint32_t aPromiseId)
void
ClearKeyPersistence::PersistentSessionRemoved(string& aSessionId)
{
LoadSessionFromKeysTask* loadTask =
new LoadSessionFromKeysTask(aInstance, aSid, aPromiseId);
ReadData(aSid, loadTask);
}
mPersistentSessionIds.erase(atoi(aSessionId.c_str()));
/* static */ void
ClearKeyPersistence::PersistentSessionRemoved(const string& aSessionId)
{
sPersistentSessionIds.erase(atoi(aSessionId.c_str()));
// Update the index file.
WriteIndex();
}

Просмотреть файл

@ -17,37 +17,51 @@
#ifndef __ClearKeyPersistence_h__
#define __ClearKeyPersistence_h__
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "RefCounted.h"
#include <functional>
#include <set>
#include <string>
#include "gmp-api/gmp-decryption.h"
#include <vector>
class ClearKeySessionManager;
class ClearKeyPersistence {
// Whether we've loaded the persistent session ids yet.
enum PersistentKeyState {
UNINITIALIZED,
LOADING,
LOADED
};
class ClearKeyPersistence : public RefCounted
{
public:
static void EnsureInitialized();
explicit ClearKeyPersistence(cdm::Host_8* aHost);
static std::string GetNewSessionId(GMPSessionType aSessionType);
void EnsureInitialized(bool aPersistentStateAllowed,
std::function<void()>&& aOnInitialized);
static bool DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const std::string& aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType);
bool IsLoaded() const;
static bool DeferLoadSessionIfNotReady(ClearKeySessionManager* aInstance,
uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength);
std::string GetNewSessionId(cdm::SessionType aSessionType);
static bool IsPersistentSessionId(const std::string& aSid);
bool IsPersistentSessionId(const std::string& aSid);
static void LoadSessionData(ClearKeySessionManager* aInstance,
const std::string& aSid,
uint32_t aPromiseId);
void PersistentSessionRemoved(std::string& aSid);
private:
cdm::Host_8* mHost = nullptr;
static void PersistentSessionRemoved(const std::string& aSid);
PersistentKeyState mPersistentKeyState = PersistentKeyState::UNINITIALIZED;
std::set<uint32_t> mPersistentSessionIds;
void ReadAllRecordsFromIndex(std::function<void()>&& aOnComplete);
void WriteIndex();
};
#endif // __ClearKeyPersistence_h__

Просмотреть файл

@ -20,18 +20,17 @@
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "psshparser/PsshParser.h"
#include "gmp-task-utils.h"
#include "gmp-api/gmp-decryption.h"
#include <assert.h>
#include <string.h>
using namespace mozilla;
using namespace cdm;
using namespace std;
ClearKeySession::ClearKeySession(const std::string& aSessionId,
GMPDecryptorCallback* aCallback,
GMPSessionType aSessionType)
SessionType aSessionType)
: mSessionId(aSessionId)
, mCallback(aCallback)
, mSessionType(aSessionType)
{
CK_LOGD("ClearKeySession ctor %p", this);
@ -40,30 +39,21 @@ ClearKeySession::ClearKeySession(const std::string& aSessionId,
ClearKeySession::~ClearKeySession()
{
CK_LOGD("ClearKeySession dtor %p", this);
std::vector<GMPMediaKeyInfo> key_infos;
for (const KeyId& keyId : mKeyIds) {
assert(ClearKeyDecryptionManager::Get()->HasSeenKeyId(keyId));
ClearKeyDecryptionManager::Get()->ReleaseKeyId(keyId);
key_infos.push_back(GMPMediaKeyInfo(&keyId[0], keyId.size(), kGMPUnknown));
}
mCallback->BatchedKeyStatusChanged(&mSessionId[0], mSessionId.size(),
key_infos.data(), key_infos.size());
}
void
ClearKeySession::Init(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const std::string& aInitDataType,
const uint8_t* aInitData, uint32_t aInitDataSize)
bool
ClearKeySession::Init(InitDataType aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize)
{
CK_LOGD("ClearKeySession::Init");
if (aInitDataType == "cenc") {
if (aInitDataType == InitDataType::kCenc) {
ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
} else if (aInitDataType == "keyids") {
} else if (aInitDataType == InitDataType::kKeyIds) {
ClearKeyUtils::ParseKeyIdsInitData(aInitData, aInitDataSize, mKeyIds);
} else if (aInitDataType == "webm" && aInitDataSize <= kMaxWebmInitDataSize) {
} else if (aInitDataType == InitDataType::kWebM &&
aInitDataSize <= kMaxWebmInitDataSize) {
// "webm" initData format is simply the raw bytes of the keyId.
vector<uint8_t> keyId;
keyId.assign(aInitData, aInitData+aInitDataSize);
@ -71,17 +61,13 @@ ClearKeySession::Init(uint32_t aCreateSessionToken,
}
if (!mKeyIds.size()) {
const char message[] = "Couldn't parse init data";
mCallback->RejectPromise(aPromiseId, kGMPTypeError, message, strlen(message));
return;
return false;
}
mCallback->SetSessionId(aCreateSessionToken, &mSessionId[0], mSessionId.length());
mCallback->ResolvePromise(aPromiseId);
return true;
}
GMPSessionType
SessionType
ClearKeySession::Type() const
{
return mSessionType;

Просмотреть файл

@ -18,30 +18,28 @@
#define __ClearKeySession_h__
#include "ClearKeyUtils.h"
#include "gmp-api/gmp-decryption.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
class GMPBuffer;
class GMPDecryptorCallback;
class GMPDecryptorHost;
class GMPEncryptedBufferMetadata;
#include <string>
#include <vector>
class ClearKeySession
{
public:
explicit ClearKeySession(const std::string& aSessionId,
GMPDecryptorCallback* aCallback,
GMPSessionType aSessionType);
cdm::SessionType aSessionType);
~ClearKeySession();
const std::vector<KeyId>& GetKeyIds() const { return mKeyIds; }
void Init(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const string& aInitDataType,
bool Init(cdm::InitDataType aInitDataType,
const uint8_t* aInitData, uint32_t aInitDataSize);
GMPSessionType Type() const;
cdm::SessionType Type() const;
void AddKeyId(const KeyId& aKeyId);
@ -51,8 +49,7 @@ private:
const std::string mSessionId;
std::vector<KeyId> mKeyIds;
GMPDecryptorCallback* mCallback;
const GMPSessionType mSessionType;
const cdm::SessionType mSessionType;
};
#endif // __ClearKeySession_h__

Просмотреть файл

@ -14,30 +14,33 @@
* limitations under the License.
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "ClearKeyDecryptionManager.h"
#include "ClearKeySessionManager.h"
#include "ClearKeyUtils.h"
#include "ClearKeyStorage.h"
#include "ClearKeyPersistence.h"
#include "gmp-task-utils.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "psshparser/PsshParser.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
using namespace std;
using namespace cdm;
ClearKeySessionManager::ClearKeySessionManager()
ClearKeySessionManager::ClearKeySessionManager(Host_8* aHost)
: mDecryptionManager(ClearKeyDecryptionManager::Get())
{
CK_LOGD("ClearKeySessionManager ctor %p", this);
AddRef();
if (GetPlatform()->createthread(&mThread) != GMPNoErr) {
CK_LOGD("failed to create thread in clearkey cdm");
mThread = nullptr;
}
mHost = aHost;
mPersistence = new ClearKeyPersistence(mHost);
}
ClearKeySessionManager::~ClearKeySessionManager()
@ -46,56 +49,107 @@ ClearKeySessionManager::~ClearKeySessionManager()
}
void
ClearKeySessionManager::Init(GMPDecryptorCallback* aCallback,
bool aDistinctiveIdentifierAllowed,
ClearKeySessionManager::Init(bool aDistinctiveIdentifierAllowed,
bool aPersistentStateAllowed)
{
CK_LOGD("ClearKeySessionManager::Init");
mCallback = aCallback;
ClearKeyPersistence::EnsureInitialized();
RefPtr<ClearKeySessionManager> self(this);
function<void()> onPersistentStateLoaded =
[self] ()
{
while (!self->mDeferredInitialize.empty()) {
function<void()> func = self->mDeferredInitialize.front();
self->mDeferredInitialize.pop();
func();
}
};
mPersistence->EnsureInitialized(aPersistentStateAllowed,
move(onPersistentStateLoaded));
}
void
ClearKeySessionManager::CreateSession(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const char* aInitDataType,
uint32_t aInitDataTypeSize,
ClearKeySessionManager::CreateSession(uint32_t aPromiseId,
InitDataType aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType)
SessionType aSessionType)
{
CK_LOGD("ClearKeySessionManager::CreateSession type:%s", aInitDataType);
CK_LOGD("ClearKeySessionManager::CreateSession type:%u", aInitDataType);
string initDataType(aInitDataType, aInitDataType + aInitDataTypeSize);
// initDataType must be "cenc", "keyids", or "webm".
if (initDataType != "cenc" &&
initDataType != "keyids" &&
initDataType != "webm") {
string message = "'" + initDataType + "' is an initDataType unsupported by ClearKey";
mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
message.c_str(), message.size());
// Copy the init data so it is correctly captured by the lambda
vector<uint8_t> initData(aInitData, aInitData + aInitDataSize);
RefPtr<ClearKeySessionManager> self(this);
function<void()> deferrer =
[self, aPromiseId, aInitDataType, initData, aSessionType] ()
{
self->CreateSession(aPromiseId,
aInitDataType,
initData.data(),
initData.size(),
aSessionType);
};
// If we haven't loaded, don't do this yet
if (MaybeDeferTillInitialized(deferrer)) {
CK_LOGD("Deferring CreateSession");
return;
}
if (ClearKeyPersistence::DeferCreateSessionIfNotReady(this,
aCreateSessionToken,
aPromiseId,
initDataType,
CK_LOGARRAY("ClearKeySessionManager::CreateSession initdata: ",
aInitData,
aInitDataSize,
aSessionType)) {
aInitDataSize);
// If 'DecryptingComplete' has been called mHost will be null so we can't
// won't be able to resolve our promise
if (!mHost) {
CK_LOGD("ClearKeySessionManager::CreateSession: mHost is nullptr");
return;
}
string sessionId = ClearKeyPersistence::GetNewSessionId(aSessionType);
// initDataType must be "cenc", "keyids", or "webm".
if (aInitDataType != InitDataType::kCenc &&
aInitDataType != InitDataType::kKeyIds &&
aInitDataType != InitDataType::kWebM) {
string message = "initDataType is not supported by ClearKey";
mHost->OnRejectPromise(aPromiseId,
Error::kNotSupportedError,
0,
message.c_str(),
message.size());
return;
}
string sessionId = mPersistence->GetNewSessionId(aSessionType);
assert(mSessions.find(sessionId) == mSessions.end());
ClearKeySession* session = new ClearKeySession(sessionId, mCallback, aSessionType);
session->Init(aCreateSessionToken, aPromiseId, initDataType, aInitData, aInitDataSize);
ClearKeySession* session = new ClearKeySession(sessionId,
aSessionType);
if (!session->Init(aInitDataType, aInitData, aInitDataSize)) {
CK_LOGD("Failed to initialize session: %s", sessionId.c_str());
const static char* message = "Failed to initialize session";
mHost->OnRejectPromise(aPromiseId,
Error::kUnknownError,
0,
message,
strlen(message));
return;
}
mSessions[sessionId] = session;
const vector<KeyId>& sessionKeys = session->GetKeyIds();
vector<KeyId> neededKeys;
for (auto it = sessionKeys.begin(); it != sessionKeys.end(); it++) {
// Need to request this key ID from the client. We always send a key
// request, whether or not another session has sent a request with the same
@ -113,9 +167,19 @@ ClearKeySessionManager::CreateSession(uint32_t aCreateSessionToken,
// Send a request for needed key data.
string request;
ClearKeyUtils::MakeKeyRequest(neededKeys, request, aSessionType);
mCallback->SessionMessage(&sessionId[0], sessionId.length(),
kGMPLicenseRequest,
(uint8_t*)&request[0], request.length());
// Resolve the promise with the new session information.
mHost->OnResolveNewSessionPromise(aPromiseId,
sessionId.c_str(),
sessionId.size());
mHost->OnSessionMessage(sessionId.c_str(),
sessionId.size(),
MessageType::kLicenseRequest,
request.c_str(),
request.size(),
nullptr,
0);
}
void
@ -125,51 +189,89 @@ ClearKeySessionManager::LoadSession(uint32_t aPromiseId,
{
CK_LOGD("ClearKeySessionManager::LoadSession");
// Copy the sessionId into a string so the lambda captures it properly.
string sessionId(aSessionId, aSessionId + aSessionIdLength);
// Hold a reference to the SessionManager so that it isn't released before
// we try to use it.
RefPtr<ClearKeySessionManager> self(this);
function<void()> deferrer =
[self, aPromiseId, sessionId] ()
{
self->LoadSession(aPromiseId, sessionId.data(), sessionId.size());
};
if (MaybeDeferTillInitialized(deferrer)) {
CK_LOGD("Deferring LoadSession");
return;
}
// If the SessionManager has been shutdown mHost will be null and we won't
// be able to resolve the promise.
if (!mHost) {
return;
}
if (!ClearKeyUtils::IsValidSessionId(aSessionId, aSessionIdLength)) {
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
return;
}
if (ClearKeyPersistence::DeferLoadSessionIfNotReady(this,
aPromiseId,
aSessionId,
aSessionIdLength)) {
if (!mPersistence->IsPersistentSessionId(sessionId)) {
mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
return;
}
string sid(aSessionId, aSessionId + aSessionIdLength);
if (!ClearKeyPersistence::IsPersistentSessionId(sid)) {
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
function<void(const uint8_t*, uint32_t)> success =
[self, sessionId, aPromiseId] (const uint8_t* data, uint32_t size)
{
self->PersistentSessionDataLoaded(aPromiseId,
sessionId,
data,
size);
};
function<void()> failure = [self, sessionId, aPromiseId] {
if (!self->mHost) {
return;
}
// As per the API described in ContentDecryptionModule_8
self->mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
};
// Callsback PersistentSessionDataLoaded with results...
ClearKeyPersistence::LoadSessionData(this, sid, aPromiseId);
ReadData(mHost, sessionId, move(success), move(failure));
}
void
ClearKeySessionManager::PersistentSessionDataLoaded(GMPErr aStatus,
uint32_t aPromiseId,
ClearKeySessionManager::PersistentSessionDataLoaded(uint32_t aPromiseId,
const string& aSessionId,
const uint8_t* aKeyData,
uint32_t aKeyDataSize)
{
CK_LOGD("ClearKeySessionManager::PersistentSessionDataLoaded");
if (GMP_FAILED(aStatus) ||
Contains(mSessions, aSessionId) ||
// Check that the SessionManager has not been shut down before we try and
// resolve any promises.
if (!mHost) {
return;
}
if (Contains(mSessions, aSessionId) ||
(aKeyDataSize % (2 * CENC_KEY_LEN)) != 0) {
mCallback->ResolveLoadSessionPromise(aPromiseId, false);
// As per the instructions in ContentDecryptionModule_8
mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
return;
}
ClearKeySession* session = new ClearKeySession(aSessionId,
mCallback,
kGMPPersistentSession);
SessionType::kPersistentLicense);
mSessions[aSessionId] = session;
uint32_t numKeys = aKeyDataSize / (2 * CENC_KEY_LEN);
vector<GMPMediaKeyInfo> key_infos;
vector<KeyInformation> keyInfos;
vector<KeyIdPair> keyPairs;
for (uint32_t i = 0; i < numKeys; i ++) {
const uint8_t* base = aKeyData + 2 * CENC_KEY_LEN * i;
@ -187,16 +289,25 @@ ClearKeySessionManager::PersistentSessionDataLoaded(GMPErr aStatus,
mDecryptionManager->ExpectKeyId(keyPair.mKeyId);
mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
mKeyIds.insert(keyPair.mKey);
keyPairs.push_back(keyPair);
key_infos.push_back(GMPMediaKeyInfo(&keyPairs[i].mKeyId[0],
keyPairs[i].mKeyId.size(),
kGMPUsable));
}
mCallback->BatchedKeyStatusChanged(&aSessionId[0], aSessionId.size(),
key_infos.data(), key_infos.size());
mCallback->ResolveLoadSessionPromise(aPromiseId, true);
KeyInformation keyInfo = KeyInformation();
keyInfo.key_id = &keyPairs.back().mKeyId[0];
keyInfo.key_id_size = keyPair.mKeyId.size();
keyInfo.status = KeyStatus::kUsable;
keyInfos.push_back(keyInfo);
}
mHost->OnSessionKeysChange(&aSessionId[0],
aSessionId.size(),
true,
keyInfos.data(),
keyInfos.size());
mHost->OnResolveNewSessionPromise(aPromiseId,
aSessionId.c_str(),
aSessionId.size());
}
void
@ -207,12 +318,48 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
uint32_t aResponseSize)
{
CK_LOGD("ClearKeySessionManager::UpdateSession");
// Copy the method arguments so we can capture them in the lambda
string sessionId(aSessionId, aSessionId + aSessionIdLength);
vector<uint8_t> response(aResponse, aResponse + aResponseSize);
// Hold a reference to the SessionManager so it isn't released before we
// callback.
RefPtr<ClearKeySessionManager> self(this);
function<void()> deferrer =
[self, aPromiseId, sessionId, response] ()
{
self->UpdateSession(aPromiseId,
sessionId.data(),
sessionId.size(),
response.data(),
response.size());
};
// If we haven't fully loaded, defer calling this method
if (MaybeDeferTillInitialized(deferrer)) {
CK_LOGD("Deferring LoadSession");
return;
}
// Make sure the SessionManager has not been shutdown before we try and
// resolve any promises.
if (!mHost) {
return;
}
CK_LOGD("Updating session: %s", sessionId.c_str());
auto itr = mSessions.find(sessionId);
if (itr == mSessions.end() || !(itr->second)) {
CK_LOGW("ClearKey CDM couldn't resolve session ID in UpdateSession.");
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
CK_LOGD("Unable to find session: %s", sessionId.c_str());
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
0,
nullptr,
0);
return;
}
ClearKeySession* session = itr->second;
@ -220,32 +367,56 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
// Verify the size of session response.
if (aResponseSize >= kMaxSessionResponseLength) {
CK_LOGW("Session response size is not within a reasonable size.");
mCallback->RejectPromise(aPromiseId, kGMPTypeError, nullptr, 0);
CK_LOGD("Failed to parse response for session %s", sessionId.c_str());
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
0,
nullptr,
0);
return;
}
// Parse the response for any (key ID, key) pairs.
vector<KeyIdPair> keyPairs;
if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) {
if (!ClearKeyUtils::ParseJWK(aResponse,
aResponseSize,
keyPairs,
session->Type())) {
CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
mCallback->RejectPromise(aPromiseId, kGMPTypeError, nullptr, 0);
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
0,
nullptr,
0);
return;
}
vector<GMPMediaKeyInfo> key_infos;
vector<KeyInformation> keyInfos;
for (size_t i = 0; i < keyPairs.size(); i++) {
KeyIdPair& keyPair = keyPairs[i];
mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
mKeyIds.insert(keyPair.mKeyId);
key_infos.push_back(GMPMediaKeyInfo(&keyPair.mKeyId[0],
keyPair.mKeyId.size(),
kGMPUsable));
}
mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdLength,
key_infos.data(), key_infos.size());
if (session->Type() != kGMPPersistentSession) {
mCallback->ResolvePromise(aPromiseId);
KeyInformation keyInfo = KeyInformation();
keyInfo.key_id = &keyPair.mKeyId[0];
keyInfo.key_id_size = keyPair.mKeyId.size();
keyInfo.status = KeyStatus::kUsable;
keyInfos.push_back(keyInfo);
}
mHost->OnSessionKeysChange(aSessionId,
aSessionIdLength,
true,
keyInfos.data(),
keyInfos.size());
if (session->Type() != SessionType::kPersistentLicense) {
mHost->OnResolvePromise(aPromiseId);
return;
}
@ -253,15 +424,30 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
// and simply append each keyId followed by its key.
vector<uint8_t> keydata;
Serialize(session, keydata);
GMPTask* resolve = WrapTask(mCallback, &GMPDecryptorCallback::ResolvePromise, aPromiseId);
function<void()> resolve = [self, aPromiseId] ()
{
if (!self->mHost) {
return;
}
self->mHost->OnResolvePromise(aPromiseId);
};
function<void()> reject = [self, aPromiseId] ()
{
if (!self->mHost) {
return;
}
static const char* message = "Couldn't store cenc key init data";
GMPTask* reject = WrapTask(mCallback,
&GMPDecryptorCallback::RejectPromise,
aPromiseId,
kGMPInvalidStateError,
self->mHost->OnRejectPromise(aPromiseId,
Error::kInvalidStateError,
0,
message,
strlen(message));
StoreData(sessionId, keydata, resolve, reject);
};
WriteData(mHost, sessionId, keydata, move(resolve), move(reject));
}
void
@ -289,11 +475,38 @@ ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
{
CK_LOGD("ClearKeySessionManager::CloseSession");
// Copy the sessionId into a string so we capture it properly.
string sessionId(aSessionId, aSessionId + aSessionIdLength);
// Hold a reference to the session manager, so it doesn't get deleted
// before we need to use it.
RefPtr<ClearKeySessionManager> self(this);
function<void()> deferrer =
[self, aPromiseId, sessionId] ()
{
self->CloseSession(aPromiseId, sessionId.data(), sessionId.size());
};
// If we haven't loaded, call this method later.
if (MaybeDeferTillInitialized(deferrer)) {
CK_LOGD("Deferring CloseSession");
return;
}
// If DecryptingComplete has been called mHost will be null and we won't
// be able to resolve our promise.
if (!mHost) {
return;
}
auto itr = mSessions.find(sessionId);
if (itr == mSessions.end()) {
CK_LOGW("ClearKey CDM couldn't close non-existent session.");
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
0,
nullptr,
0);
return;
}
@ -301,8 +514,9 @@ ClearKeySessionManager::CloseSession(uint32_t aPromiseId,
assert(session);
ClearInMemorySessionData(session);
mCallback->SessionClosed(aSessionId, aSessionIdLength);
mCallback->ResolvePromise(aPromiseId);
mHost->OnSessionClosed(aSessionId, aSessionIdLength);
mHost->OnResolvePromise(aPromiseId);
}
void
@ -318,39 +532,81 @@ ClearKeySessionManager::RemoveSession(uint32_t aPromiseId,
uint32_t aSessionIdLength)
{
CK_LOGD("ClearKeySessionManager::RemoveSession");
// Copy the sessionId into a string so it can be captured for the lambda.
string sessionId(aSessionId, aSessionId + aSessionIdLength);
// Hold a reference to the SessionManager, so it isn't released before we
// try and use it.
RefPtr<ClearKeySessionManager> self(this);
function<void()> deferrer =
[self, aPromiseId, sessionId] ()
{
self->RemoveSession(aPromiseId, sessionId.data(), sessionId.size());
};
// If we haven't fully loaded, defer calling this method.
if (MaybeDeferTillInitialized(deferrer)) {
CK_LOGD("Deferring RemoveSession");
return;
}
// Check that the SessionManager has not been shutdown before we try and
// resolve any promises.
if (!mHost) {
return;
}
auto itr = mSessions.find(sessionId);
if (itr == mSessions.end()) {
CK_LOGW("ClearKey CDM couldn't remove non-existent session.");
mCallback->RejectPromise(aPromiseId, kGMPNotFoundError, nullptr, 0);
mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
0,
nullptr,
0);
return;
}
ClearKeySession* session = itr->second;
assert(session);
string sid = session->Id();
bool isPersistent = session->Type() == kGMPPersistentSession;
bool isPersistent = session->Type() == SessionType::kPersistentLicense;
ClearInMemorySessionData(session);
if (!isPersistent) {
mCallback->ResolvePromise(aPromiseId);
mHost->OnResolvePromise(aPromiseId);
return;
}
ClearKeyPersistence::PersistentSessionRemoved(sid);
mPersistence->PersistentSessionRemoved(sid);
// Overwrite the record storing the sessionId's key data with a zero
// length record to delete it.
vector<uint8_t> emptyKeydata;
GMPTask* resolve = WrapTask(mCallback, &GMPDecryptorCallback::ResolvePromise, aPromiseId);
function<void()> resolve = [self, aPromiseId, sessionId] ()
{
if (!self->mHost) {
return;
}
self->mHost->OnResolvePromise(aPromiseId);
};
function<void()> reject = [self, aPromiseId, sessionId] ()
{
if (!self->mHost) {
return;
}
static const char* message = "Could not remove session";
GMPTask* reject = WrapTask(mCallback,
&GMPDecryptorCallback::RejectPromise,
aPromiseId,
kGMPInvalidAccessError,
self->mHost->OnRejectPromise(aPromiseId,
Error::kInvalidAccessError,
0,
message,
strlen(message));
StoreData(sessionId, emptyKeydata, resolve, reject);
};
WriteData(mHost, sessionId, emptyKeydata, move(resolve), move(reject));
}
void
@ -360,48 +616,36 @@ ClearKeySessionManager::SetServerCertificate(uint32_t aPromiseId,
{
// ClearKey CDM doesn't support this method by spec.
CK_LOGD("ClearKeySessionManager::SetServerCertificate");
mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
nullptr /* message */, 0 /* messageLen */);
mHost->OnRejectPromise(aPromiseId,
Error::kNotSupportedError,
0,
nullptr /* message */,
0 /* messageLen */);
}
void
ClearKeySessionManager::Decrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata)
Status
ClearKeySessionManager::Decrypt(const InputBuffer& aBuffer,
DecryptedBlock* aDecryptedBlock)
{
CK_LOGD("ClearKeySessionManager::Decrypt");
if (!mThread) {
CK_LOGW("No decrypt thread");
mCallback->Decrypted(aBuffer, GMPGenericErr);
return;
}
CK_LOGARRAY("Key: ", aBuffer.key_id, aBuffer.key_id_size);
mThread->Post(WrapTaskRefCounted(this,
&ClearKeySessionManager::DoDecrypt,
aBuffer, aMetadata));
}
Buffer* buffer = mHost->Allocate(aBuffer.data_size);
assert(buffer != nullptr);
assert(buffer->Data() != nullptr);
assert(buffer->Capacity() >= aBuffer.data_size);
void
ClearKeySessionManager::DoDecrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata)
{
CK_LOGD("ClearKeySessionManager::DoDecrypt");
memcpy(buffer->Data(), aBuffer.data, aBuffer.data_size);
GMPErr rv = mDecryptionManager->Decrypt(aBuffer->Data(), aBuffer->Size(),
CryptoMetaData(aMetadata));
CK_LOGD("DeDecrypt finished with code %x\n", rv);
mCallback->Decrypted(aBuffer, rv);
}
Status status = mDecryptionManager->Decrypt(buffer->Data(),
buffer->Size(),
CryptoMetaData(&aBuffer));
void
ClearKeySessionManager::Shutdown()
{
CK_LOGD("ClearKeySessionManager::Shutdown %p", this);
aDecryptedBlock->SetDecryptedBuffer(buffer);
aDecryptedBlock->SetTimestamp(aBuffer.timestamp);
for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
delete it->second;
}
mSessions.clear();
return status;
}
void
@ -409,10 +653,23 @@ ClearKeySessionManager::DecryptingComplete()
{
CK_LOGD("ClearKeySessionManager::DecryptingComplete %p", this);
GMPThread* thread = mThread;
thread->Join();
for (auto it = mSessions.begin(); it != mSessions.end(); it++) {
delete it->second;
}
mSessions.clear();
Shutdown();
mDecryptionManager = nullptr;
mHost = nullptr;
Release();
}
bool ClearKeySessionManager::MaybeDeferTillInitialized(function<void()> aMaybeDefer)
{
if (mPersistence->IsLoaded()) {
return false;
}
mDeferredInitialize.emplace(move(aMaybeDefer));
return true;
}

Просмотреть файл

@ -1,80 +1,81 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __ClearKeyDecryptor_h__
#define __ClearKeyDecryptor_h__
#include <map>
#include <set>
#include <string>
#include <vector>
#include "ClearKeyDecryptionManager.h"
#include "ClearKeyPersistence.h"
#include "ClearKeySession.h"
#include "ClearKeyUtils.h"
#include "gmp-api/gmp-decryption.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "RefCounted.h"
class ClearKeySessionManager final : public GMPDecryptor
, public RefCounted
#include <functional>
#include <map>
#include <queue>
#include <set>
#include <string>
class ClearKeySessionManager final : public RefCounted
{
public:
ClearKeySessionManager();
explicit ClearKeySessionManager(cdm::Host_8* aHost);
virtual void Init(GMPDecryptorCallback* aCallback,
bool aDistinctiveIdentifierAllowed,
bool aPersistentStateAllowed) override;
void Init(bool aDistinctiveIdentifierAllowed,
bool aPersistentStateAllowed);
virtual void CreateSession(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const char* aInitDataType,
uint32_t aInitDataTypeSize,
void CreateSession(uint32_t aPromiseId,
cdm::InitDataType aInitDataType,
const uint8_t* aInitData,
uint32_t aInitDataSize,
GMPSessionType aSessionType) override;
cdm::SessionType aSessionType);
virtual void LoadSession(uint32_t aPromiseId,
void LoadSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength) override;
uint32_t aSessionIdLength);
virtual void UpdateSession(uint32_t aPromiseId,
void UpdateSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength,
const uint8_t* aResponse,
uint32_t aResponseSize) override;
uint32_t aResponseSize);
virtual void CloseSession(uint32_t aPromiseId,
void CloseSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength) override;
uint32_t aSessionIdLength);
virtual void RemoveSession(uint32_t aPromiseId,
void RemoveSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength) override;
uint32_t aSessionIdLength);
virtual void SetServerCertificate(uint32_t aPromiseId,
void SetServerCertificate(uint32_t aPromiseId,
const uint8_t* aServerCert,
uint32_t aServerCertSize) override;
uint32_t aServerCertSize);
virtual void Decrypt(GMPBuffer* aBuffer,
GMPEncryptedBufferMetadata* aMetadata) override;
cdm::Status
Decrypt(const cdm::InputBuffer& aBuffer,
cdm::DecryptedBlock* aDecryptedBlock);
virtual void DecryptingComplete() override;
void DecryptingComplete();
void PersistentSessionDataLoaded(GMPErr aStatus,
uint32_t aPromiseId,
void PersistentSessionDataLoaded(uint32_t aPromiseId,
const std::string& aSessionId,
const uint8_t* aKeyData,
uint32_t aKeyDataSize);
@ -82,19 +83,20 @@ public:
private:
~ClearKeySessionManager();
void DoDecrypt(GMPBuffer* aBuffer, GMPEncryptedBufferMetadata* aMetadata);
void Shutdown();
void ClearInMemorySessionData(ClearKeySession* aSession);
void Serialize(const ClearKeySession* aSession, std::vector<uint8_t>& aOutKeyData);
bool MaybeDeferTillInitialized(std::function<void()> aMaybeDefer);
void Serialize(const ClearKeySession* aSession,
std::vector<uint8_t>& aOutKeyData);
RefPtr<ClearKeyDecryptionManager> mDecryptionManager;
RefPtr<ClearKeyPersistence> mPersistence;
GMPDecryptorCallback* mCallback;
GMPThread* mThread;
cdm::Host_8* mHost = nullptr;
std::set<KeyId> mKeyIds;
std::map<std::string, ClearKeySession*> mSessions;
std::queue<std::function<void()>> mDeferredInitialize;
};
#endif // __ClearKeyDecryptor_h__

Просмотреть файл

@ -15,180 +15,212 @@
*/
#include "ClearKeyStorage.h"
#include "ClearKeyUtils.h"
#include "gmp-task-utils.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include <assert.h>
#include "ArrayUtils.h"
#include <vector>
static GMPErr
RunOnMainThread(GMPTask* aTask)
{
return GetPlatform()->runonmainthread(aTask);
}
using namespace cdm;
using namespace std;
GMPErr
OpenRecord(const char* aName,
uint32_t aNameLength,
GMPRecord** aOutRecord,
GMPRecordClient* aClient)
class WriteRecordClient : public FileIOClient
{
return GetPlatform()->createrecord(aName, aNameLength, aOutRecord, aClient);
}
class WriteRecordClient : public GMPRecordClient {
public:
/*
* This function will take the memory ownership of the parameters and
* delete them when done.
*/
static void Write(const std::string& aRecordName,
const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure) {
(new WriteRecordClient(aData, aOnSuccess, aOnFailure))->Do(aRecordName);
static void Write(Host_8* aHost,
string& aRecordName,
const vector<uint8_t>& aData,
function<void()>&& aOnSuccess,
function<void()>&& aOnFailure)
{
WriteRecordClient* client = new WriteRecordClient(aData,
move(aOnSuccess),
move(aOnFailure));
client->Do(aRecordName, aHost);
}
virtual void OpenComplete(GMPErr aStatus) override {
if (GMP_FAILED(aStatus) ||
GMP_FAILED(mRecord->Write(&mData.front(), mData.size()))) {
Done(mOnFailure, mOnSuccess);
void OnOpenComplete(Status aStatus) override
{
// If we hit an error, fail.
if (aStatus != Status::kSuccess) {
Done(aStatus);
} else if (mFileIO) { // Otherwise, write our data to the file.
mFileIO->Write(&mData[0], mData.size());
}
}
virtual void ReadComplete(GMPErr aStatus,
void OnReadComplete(Status aStatus,
const uint8_t* aData,
uint32_t aDataSize) override {
assert(false); // Should not reach here.
uint32_t aDataSize) override
{
// This function should never be called, we only ever write data with this
// client.
assert(false);
}
virtual void WriteComplete(GMPErr aStatus) override {
if (GMP_FAILED(aStatus)) {
Done(mOnFailure, mOnSuccess);
} else {
Done(mOnSuccess, mOnFailure);
}
void OnWriteComplete(Status aStatus) override
{
Done(aStatus);
}
private:
WriteRecordClient(const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure)
: mRecord(nullptr)
, mOnSuccess(aOnSuccess)
, mOnFailure(aOnFailure)
explicit WriteRecordClient(const vector<uint8_t>& aData,
function<void()>&& aOnSuccess,
function<void()>&& aOnFailure)
: mFileIO(nullptr)
, mOnSuccess(move(aOnSuccess))
, mOnFailure(move(aOnFailure))
, mData(aData) {}
void Do(const std::string& aName) {
auto err = OpenRecord(aName.c_str(), aName.size(), &mRecord, this);
if (GMP_FAILED(err) ||
GMP_FAILED(mRecord->Open())) {
Done(mOnFailure, mOnSuccess);
}
void Do(const string& aName, Host_8* aHost)
{
// Initialize the FileIO.
mFileIO = aHost->CreateFileIO(this);
mFileIO->Open(aName.c_str(), aName.size());
}
void Done(GMPTask* aToRun, GMPTask* aToDestroy) {
void Done(cdm::FileIOClient::Status aStatus)
{
// Note: Call Close() before running continuation, in case the
// continuation tries to open the same record; if we call Close()
// after running the continuation, the Close() call will arrive
// just after the Open() call succeeds, immediately closing the
// record we just opened.
if (mRecord) {
mRecord->Close();
if (mFileIO) {
mFileIO->Close();
}
aToDestroy->Destroy();
RunOnMainThread(aToRun);
if (IO_SUCCEEDED(aStatus)) {
mOnSuccess();
} else {
mOnFailure();
}
delete this;
}
GMPRecord* mRecord;
GMPTask* mOnSuccess;
GMPTask* mOnFailure;
const std::vector<uint8_t> mData;
FileIO* mFileIO = nullptr;
function<void()> mOnSuccess;
function<void()> mOnFailure;
const vector<uint8_t> mData;
};
void
StoreData(const std::string& aRecordName,
const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure)
WriteData(Host_8* aHost,
string& aRecordName,
const vector<uint8_t>& aData,
function<void()>&& aOnSuccess,
function<void()>&& aOnFailure)
{
WriteRecordClient::Write(aRecordName, aData, aOnSuccess, aOnFailure);
WriteRecordClient::Write(aHost,
aRecordName,
aData,
move(aOnSuccess),
move(aOnFailure));
}
class ReadRecordClient : public GMPRecordClient {
class ReadRecordClient : public FileIOClient
{
public:
/*
* This function will take the memory ownership of the parameters and
* delete them when done.
*/
static void Read(const std::string& aRecordName,
ReadContinuation* aContinuation) {
assert(aContinuation);
(new ReadRecordClient(aContinuation))->Do(aRecordName);
static void Read(Host_8* aHost,
string& aRecordName,
function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
function<void()>&& aOnFailure)
{
(new ReadRecordClient(move(aOnSuccess), move(aOnFailure)))->
Do(aRecordName, aHost);
}
virtual void OpenComplete(GMPErr aStatus) override {
void OnOpenComplete(Status aStatus) override
{
auto err = aStatus;
if (GMP_FAILED(err) ||
GMP_FAILED(err = mRecord->Read())) {
if (aStatus != Status::kSuccess) {
Done(err, nullptr, 0);
} else {
mFileIO->Read();
}
}
virtual void ReadComplete(GMPErr aStatus,
void OnReadComplete(Status aStatus,
const uint8_t* aData,
uint32_t aDataSize) override {
uint32_t aDataSize) override
{
Done(aStatus, aData, aDataSize);
}
virtual void WriteComplete(GMPErr aStatus) override {
assert(false); // Should not reach here.
void OnWriteComplete(Status aStatus) override
{
// We should never reach here, this client only ever reads data.
assert(false);
}
private:
explicit ReadRecordClient(ReadContinuation* aContinuation)
: mRecord(nullptr)
, mContinuation(aContinuation) {}
explicit ReadRecordClient(function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
function<void()>&& aOnFailure)
: mFileIO(nullptr)
, mOnSuccess(move(aOnSuccess))
, mOnFailure(move(aOnFailure))
{}
void Do(const std::string& aName) {
auto err = OpenRecord(aName.c_str(), aName.size(), &mRecord, this);
if (GMP_FAILED(err) ||
GMP_FAILED(err = mRecord->Open())) {
Done(err, nullptr, 0);
}
void Do(const string& aName, Host_8* aHost)
{
mFileIO = aHost->CreateFileIO(this);
mFileIO->Open(aName.c_str(), aName.size());
}
void Done(GMPErr err, const uint8_t* aData, uint32_t aDataSize) {
void Done(cdm::FileIOClient::Status aStatus,
const uint8_t* aData,
uint32_t aDataSize)
{
// Note: Call Close() before running continuation, in case the
// continuation tries to open the same record; if we call Close()
// after running the continuation, the Close() call will arrive
// just after the Open() call succeeds, immediately closing the
// record we just opened.
if (mRecord) {
mRecord->Close();
if (mFileIO) {
mFileIO->Close();
}
mContinuation->ReadComplete(err, aData, aDataSize);
delete mContinuation;
if (IO_SUCCEEDED(aStatus)) {
mOnSuccess(aData, aDataSize);
} else {
mOnFailure();
}
delete this;
}
GMPRecord* mRecord;
ReadContinuation* mContinuation;
FileIO* mFileIO = nullptr;
function<void(const uint8_t*, uint32_t)> mOnSuccess;
function<void()> mOnFailure;
};
void
ReadData(const std::string& aRecordName,
ReadContinuation* aContinuation)
ReadData(Host_8* mHost,
string& aRecordName,
function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
function<void()>&& aOnFailure)
{
ReadRecordClient::Read(aRecordName, aContinuation);
}
GMPErr
EnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc)
{
return GetPlatform()->getrecordenumerator(aRecvIteratorFunc, nullptr);
ReadRecordClient::Read(mHost,
aRecordName,
move(aOnSuccess),
move(aOnFailure));
}

Просмотреть файл

@ -17,32 +17,27 @@
#ifndef __ClearKeyStorage_h__
#define __ClearKeyStorage_h__
#include "gmp-api/gmp-errors.h"
#include "gmp-api/gmp-platform.h"
#include <functional>
#include <stdint.h>
#include <string>
#include <vector>
#include <stdint.h>
class GMPTask;
#include "ClearKeySessionManager.h"
// Responsible for ensuring that both aOnSuccess and aOnFailure are destroyed.
void StoreData(const std::string& aRecordName,
#define IO_SUCCEEDED(x) ((x) == cdm::FileIOClient::Status::kSuccess)
#define IO_FAILED(x) ((x) != cdm::FileIOClient::Status::kSuccess)
// Writes data to a file and fires the appropriate callback when complete.
void WriteData(cdm::Host_8* aHost,
std::string& aRecordName,
const std::vector<uint8_t>& aData,
GMPTask* aOnSuccess,
GMPTask* aOnFailure);
std::function<void()>&& aOnSuccess,
std::function<void()>&& aOnFailure);
class ReadContinuation {
public:
virtual void ReadComplete(GMPErr aStatus,
const uint8_t* aData,
uint32_t aLength) = 0;
virtual ~ReadContinuation() {}
};
// Deletes aContinuation after running it to report the result.
void ReadData(const std::string& aSessionId,
ReadContinuation* aContinuation);
GMPErr EnumRecordNames(RecvGMPRecordIteratorPtr aRecvIteratorFunc);
// Reads data from a file and fires the appropriate callback when complete.
void ReadData(cdm::Host_8* aHost,
std::string& aRecordName,
std::function<void(const uint8_t*, uint32_t)>&& aOnSuccess,
std::function<void()>&& aOnFailure);
#endif // __ClearKeyStorage_h__

Просмотреть файл

@ -14,33 +14,77 @@
* limitations under the License.
*/
#include "ClearKeyUtils.h"
#include <algorithm>
#include <assert.h>
#include <stdlib.h>
#include <cctype>
#include <ctype.h>
#include <memory.h>
#include <sstream>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <vector>
#include "ClearKeyUtils.h"
#include "ClearKeyBase64.h"
#include "ArrayUtils.h"
#include <assert.h>
#include <memory.h>
#include "BigEndian.h"
#include "ClearKeyBase64.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "openaes/oaes_lib.h"
#include "psshparser/PsshParser.h"
using namespace cdm;
using namespace std;
void
CK_Log(const char* aFmt, ...)
{
FILE* out = stdout;
if (getenv("CLEARKEY_LOG_FILE")) {
out = fopen(getenv("CLEARKEY_LOG_FILE"), "a");
}
va_list ap;
va_start(ap, aFmt);
vprintf(aFmt, ap);
const size_t len = 1024;
char buf[len];
vsnprintf(buf, len, aFmt, ap);
va_end(ap);
printf("\n");
fflush(stdout);
fprintf(out, "%s\n", buf);
fflush(out);
if (out != stdout) {
fclose(out);
}
}
static bool
PrintableAsString(const uint8_t* aBytes, uint32_t aLength)
{
return all_of(aBytes, aBytes + aLength, [] (uint8_t c) {
return isprint(c) == 1;
});
}
void
CK_LogArray(const char* prepend,
const uint8_t* aData,
const uint32_t aDataSize)
{
// If the data is valid ascii, use that. Otherwise print the hex
string data = PrintableAsString(aData, aDataSize) ?
string(aData, aData + aDataSize) :
ClearKeyUtils::ToHexString(aData, aDataSize);
CK_LOGD("%s%s", prepend, data.c_str());
}
static void
@ -115,7 +159,9 @@ EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded)
// Cast idx to size_t before using it as an array-index,
// to pacify clang 'Wchar-subscripts' warning:
size_t idx = static_cast<size_t>(out[i]);
assert(idx < MOZ_ARRAY_LENGTH(sAlphabet)); // out of bounds index for 'sAlphabet'
// out of bounds index for 'sAlphabet'
assert(idx < MOZ_ARRAY_LENGTH(sAlphabet));
out[i] = sAlphabet[idx];
}
@ -125,7 +171,7 @@ EncodeBase64Web(vector<uint8_t> aBinary, string& aEncoded)
/* static */ void
ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
string& aOutRequest,
GMPSessionType aSessionType)
SessionType aSessionType)
{
assert(aKeyIDs.size() && aOutRequest.empty());
@ -389,7 +435,7 @@ ParseKeys(ParserContext& aCtx, vector<KeyIdPair>& aOutKeys)
/* static */ bool
ClearKeyUtils::ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
vector<KeyIdPair>& aOutKeys,
GMPSessionType aSessionType)
SessionType aSessionType)
{
ParserContext ctx;
ctx.mIter = aKeyData;
@ -505,13 +551,14 @@ ClearKeyUtils::ParseKeyIdsInitData(const uint8_t* aInitData,
}
/* static */ const char*
ClearKeyUtils::SessionTypeToString(GMPSessionType aSessionType)
ClearKeyUtils::SessionTypeToString(SessionType aSessionType)
{
switch (aSessionType) {
case kGMPTemporySession: return "temporary";
case kGMPPersistentSession: return "persistent-license";
case SessionType::kTemporary: return "temporary";
case SessionType::kPersistentLicense: return "persistent-license";
default: {
assert(false); // Should not reach here.
// We don't support any other license types.
assert(false);
return "invalid";
}
}
@ -533,9 +580,15 @@ ClearKeyUtils::IsValidSessionId(const char* aBuff, uint32_t aLength)
return true;
}
GMPMutex* GMPCreateMutex() {
GMPMutex* mutex;
auto err = GetPlatform()->createmutex(&mutex);
assert(mutex);
return GMP_FAILED(err) ? nullptr : mutex;
string
ClearKeyUtils::ToHexString(const uint8_t * aBytes, uint32_t aLength)
{
stringstream ss;
ss << std::showbase << std::uppercase << std::hex;
for (uint32_t i = 0; i < aLength; ++i) {
ss << std::hex << static_cast<uint32_t>(aBytes[i]);
ss << " ";
}
return ss.str();
}

Просмотреть файл

@ -21,22 +21,29 @@
#include <string>
#include <vector>
#include <assert.h>
#include "gmp-api/gmp-decryption.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#if 0
void CK_Log(const char* aFmt, ...);
#define CK_LOGE(...) CK_Log(__VA_ARGS__)
#define CK_LOGD(...) CK_Log(__VA_ARGS__)
#define CK_LOGW(...) CK_Log(__VA_ARGS__)
#define CK_LOGARRAY(APREPEND, ADATA, ADATA_SIZE) CK_LogArray(APREPEND, \
ADATA, \
ADATA_SIZE)
#else
// Note: Enabling logging slows things down a LOT, especially when logging to
// a file.
#define CK_LOGE(...)
#define CK_LOGD(...)
#define CK_LOGW(...)
#define CK_LOGARRAY(APREPEND, ADATA, ADATA_SIZE)
#endif
struct GMPPlatformAPI;
extern GMPPlatformAPI* GetPlatform();
typedef std::vector<uint8_t> KeyId;
typedef std::vector<uint8_t> Key;
@ -48,6 +55,10 @@ static const uint32_t kMaxSessionResponseLength = 65536;
static const uint32_t kMaxWebmInitDataSize = 65536;
static const uint32_t kMaxKeyIdsLength = 512;
void CK_LogArray(const char* aPrepend,
const uint8_t* aData,
const uint32_t aDataSize);
struct KeyIdPair
{
KeyId mKeyId;
@ -66,14 +77,16 @@ public:
static void MakeKeyRequest(const std::vector<KeyId>& aKeyIds,
std::string& aOutRequest,
GMPSessionType aSessionType);
cdm::SessionType aSessionType);
static bool ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
std::vector<KeyIdPair>& aOutKeys,
GMPSessionType aSessionType);
static const char* SessionTypeToString(GMPSessionType aSessionType);
cdm::SessionType aSessionType);
static const char* SessionTypeToString(cdm::SessionType aSessionType);
static bool IsValidSessionId(const char* aBuff, uint32_t aLength);
static std::string ToHexString(const uint8_t * aBytes, uint32_t aLength);
};
template<class Container, class Element>
@ -83,27 +96,6 @@ Contains(const Container& aContainer, const Element& aElement)
return aContainer.find(aElement) != aContainer.end();
}
class AutoLock {
public:
explicit AutoLock(GMPMutex* aMutex)
: mMutex(aMutex)
{
assert(aMutex);
if (mMutex) {
mMutex->Acquire();
}
}
~AutoLock() {
if (mMutex) {
mMutex->Release();
}
}
private:
GMPMutex* mMutex;
};
GMPMutex* GMPCreateMutex();
template<typename T>
inline void
Assign(std::vector<T>& aVec, const T* aData, size_t aLength)

Просмотреть файл

@ -21,41 +21,7 @@
#include <assert.h>
#include "ClearKeyUtils.h"
#if defined(_MSC_VER)
#include <atomic>
typedef std::atomic<uint32_t> AtomicRefCount;
#else
class AtomicRefCount {
public:
explicit AtomicRefCount(uint32_t aValue)
: mCount(aValue)
, mMutex(GMPCreateMutex())
{
assert(mMutex);
}
~AtomicRefCount()
{
if (mMutex) {
mMutex->Destroy();
}
}
uint32_t operator--() {
AutoLock lock(mMutex);
return --mCount;
}
uint32_t operator++() {
AutoLock lock(mMutex);
return ++mCount;
}
operator uint32_t() {
AutoLock lock(mMutex);
return mCount;
}
private:
uint32_t mCount;
GMPMutex* mMutex;
};
#endif
// Note: Thread safe.
class RefCounted {
@ -81,27 +47,41 @@ protected:
{
assert(!mRefCount);
}
AtomicRefCount mRefCount;
std::atomic<uint32_t> mRefCount;
};
template<class T>
class RefPtr {
public:
explicit RefPtr(T* aPtr) : mPtr(nullptr) {
Assign(aPtr);
RefPtr(const RefPtr& src) {
Set(src.mPtr);
}
explicit RefPtr(T* aPtr) {
Set(aPtr);
}
RefPtr() { Set(nullptr); }
~RefPtr() {
Assign(nullptr);
Set(nullptr);
}
T* operator->() const { return mPtr; }
T** operator&() { return &mPtr; }
T* operator->() { return mPtr; }
operator T*() { return mPtr; }
T* Get() const { return mPtr; }
RefPtr& operator=(T* aVal) {
Assign(aVal);
Set(aVal);
return *this;
}
private:
void Assign(T* aPtr) {
T* Set(T* aPtr) {
if (mPtr == aPtr) {
return aPtr;
}
if (mPtr) {
mPtr->Release();
}
@ -109,8 +89,10 @@ private:
if (mPtr) {
aPtr->AddRef();
}
return mPtr;
}
T* mPtr;
T* mPtr = nullptr;
};
#endif // __RefCount_h__

Просмотреть файл

@ -14,247 +14,170 @@
* limitations under the License.
*/
#include <algorithm>
#include <cstdint>
#include <limits>
#include "AnnexB.h"
#include "BigEndian.h"
#include "ClearKeyDecryptionManager.h"
#include "ClearKeyUtils.h"
#include "gmp-task-utils.h"
#include "VideoDecoder.h"
using namespace wmf;
using namespace cdm;
VideoDecoder::VideoDecoder(GMPVideoHost *aHostAPI)
: mHostAPI(aHostAPI)
, mCallback(nullptr)
, mWorkerThread(nullptr)
, mMutex(nullptr)
, mNumInputTasks(0)
, mSentExtraData(false)
, mIsFlushing(false)
VideoDecoder::VideoDecoder(Host_8 *aHost)
: mHost(aHost)
, mHasShutdown(false)
{
CK_LOGD("VideoDecoder created");
// We drop the ref in DecodingComplete().
AddRef();
mDecoder = new WMFH264Decoder();
uint32_t cores = std::max(1u, std::thread::hardware_concurrency());
HRESULT hr = mDecoder->Init(cores);
}
VideoDecoder::~VideoDecoder()
{
if (mMutex) {
mMutex->Destroy();
}
CK_LOGD("VideoDecoder destroyed");
}
void
VideoDecoder::InitDecode(const GMPVideoCodec& aCodecSettings,
const uint8_t* aCodecSpecific,
uint32_t aCodecSpecificLength,
GMPVideoDecoderCallback* aCallback,
int32_t aCoreCount)
Status
VideoDecoder::InitDecode(const VideoDecoderConfig& aConfig)
{
mCallback = aCallback;
assert(mCallback);
mDecoder = new WMFH264Decoder();
HRESULT hr = mDecoder->Init(aCoreCount);
if (FAILED(hr)) {
CK_LOGD("VideoDecoder::InitDecode");
if (!mDecoder) {
CK_LOGD("VideoDecoder::InitDecode failed to init WMFH264Decoder");
mCallback->Error(GMPGenericErr);
return;
return Status::kDecodeError;
}
auto err = GetPlatform()->createmutex(&mMutex);
if (GMP_FAILED(err)) {
CK_LOGD("VideoDecoder::InitDecode failed to create GMPMutex");
mCallback->Error(GMPGenericErr);
return;
}
// The first byte is mPacketizationMode, which is only relevant for
// WebRTC/OpenH264 usecase.
const uint8_t* avcc = aCodecSpecific + 1;
const uint8_t* avccEnd = aCodecSpecific + aCodecSpecificLength;
mExtraData.insert(mExtraData.end(), avcc, avccEnd);
AnnexB::ConvertConfig(mExtraData, mAnnexB);
return Status::kSuccess;
}
void
VideoDecoder::EnsureWorker()
Status
VideoDecoder::Decode(const InputBuffer& aInputBuffer, VideoFrame* aVideoFrame)
{
if (!mWorkerThread) {
GetPlatform()->createthread(&mWorkerThread);
if (!mWorkerThread) {
mCallback->Error(GMPAllocErr);
return;
}
}
}
void
VideoDecoder::Decode(GMPVideoEncodedFrame* aInputFrame,
bool aMissingFrames,
const uint8_t* aCodecSpecificInfo,
uint32_t aCodecSpecificInfoLength,
int64_t aRenderTimeMs)
{
if (aInputFrame->BufferType() != GMP_BufferLength32) {
// Gecko should only send frames with 4 byte NAL sizes to GMPs.
mCallback->Error(GMPGenericErr);
return;
CK_LOGD("VideoDecoder::Decode");
// If the input buffer we have been passed has a null buffer, it means we
// should drain.
if (!aInputBuffer.data) {
// This will drain the decoder until there are no frames left to drain,
// whereupon it will return 'NeedsMoreData'.
CK_LOGD("VideoDecoder::Decode Input buffer null: Draining");
return Drain(aVideoFrame);
}
EnsureWorker();
{
AutoLock lock(mMutex);
mNumInputTasks++;
}
// Note: we don't need the codec specific info on a per-frame basis.
// It's mostly useful for WebRTC use cases.
// Make a copy of the data, so we can release aInputFrame ASAP,
// to avoid too many shmem handles being held by the GMP process.
// If the GMP process holds on to too many shmem handles, the Gecko
// side can fail to allocate a shmem to send more input. This is
// particularly a problem in Gecko mochitests, which can open multiple
// actors at once which share the same pool of shmems.
DecodeData* data = new DecodeData();
Assign(data->mBuffer, aInputFrame->Buffer(), aInputFrame->Size());
data->mTimestamp = aInputFrame->TimeStamp();
data->mDuration = aInputFrame->Duration();
data->mIsKeyframe = (aInputFrame->FrameType() == kGMPKeyFrame);
const GMPEncryptedBufferMetadata* crypto = aInputFrame->GetDecryptionData();
if (crypto) {
data->mCrypto.Init(crypto);
}
aInputFrame->Destroy();
mWorkerThread->Post(WrapTaskRefCounted(this,
&VideoDecoder::DecodeTask,
data));
}
Assign(data->mBuffer, aInputBuffer.data, aInputBuffer.data_size);
data->mTimestamp = aInputBuffer.timestamp;
data->mCrypto = CryptoMetaData(&aInputBuffer);
void
VideoDecoder::DecodeTask(DecodeData* aData)
{
CK_LOGD("VideoDecoder::DecodeTask");
AutoPtr<DecodeData> d(aData);
AutoPtr<DecodeData> d(data);
HRESULT hr;
{
AutoLock lock(mMutex);
mNumInputTasks--;
assert(mNumInputTasks >= 0);
}
if (mIsFlushing) {
CK_LOGD("VideoDecoder::DecodeTask rejecting frame: flushing.");
return;
}
if (!aData || !mHostAPI || !mDecoder) {
if (!data || !mDecoder) {
CK_LOGE("Decode job not set up correctly!");
return;
return Status::kDecodeError;
}
std::vector<uint8_t>& buffer = aData->mBuffer;
if (aData->mCrypto.IsValid()) {
// Plugin host should have set up its decryptor/key sessions
// before trying to decode!
GMPErr rv =
ClearKeyDecryptionManager::Get()->Decrypt(buffer, aData->mCrypto);
std::vector<uint8_t>& buffer = data->mBuffer;
if (GMP_FAILED(rv)) {
MaybeRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::Error, rv));
return;
if (data->mCrypto.IsValid()) {
Status rv =
ClearKeyDecryptionManager::Get()->Decrypt(buffer, data->mCrypto);
if (STATUS_FAILED(rv)) {
CK_LOGARRAY("Failed to decrypt video using key ",
aInputBuffer.key_id,
aInputBuffer.key_id_size);
return rv;
}
}
AnnexB::ConvertFrameInPlace(buffer);
if (aData->mIsKeyframe) {
// We must send the SPS and PPS to Windows Media Foundation's decoder.
// Note: We do this *after* decryption, otherwise the subsample info
// would be incorrect.
buffer.insert(buffer.begin(), mAnnexB.begin(), mAnnexB.end());
}
hr = mDecoder->Input(buffer.data(),
buffer.size(),
aData->mTimestamp,
aData->mDuration);
data->mTimestamp);
CK_LOGD("VideoDecoder::Decode() Input ret hr=0x%x", hr);
CK_LOGD("VideoDecoder::DecodeTask() Input ret hr=0x%x\n", hr);
if (FAILED(hr)) {
CK_LOGE("VideoDecoder::DecodeTask() decode failed ret=0x%x%s\n",
assert(hr != MF_E_TRANSFORM_NEED_MORE_INPUT);
CK_LOGE("VideoDecoder::Decode() decode failed ret=0x%x%s",
hr,
((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
return;
CK_LOGD("Decode failed. The decoder is not accepting input");
return Status::kDecodeError;
}
while (hr == S_OK) {
CComPtr<IMFSample> output;
hr = mDecoder->Output(&output);
CK_LOGD("VideoDecoder::DecodeTask() output ret=0x%x\n", hr);
if (hr == S_OK) {
MaybeRunOnMainThread(
WrapTaskRefCounted(this,
&VideoDecoder::ReturnOutput,
CComPtr<IMFSample>(output),
mDecoder->GetFrameWidth(),
mDecoder->GetFrameHeight(),
mDecoder->GetStride()));
}
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
AutoLock lock(mMutex);
if (mNumInputTasks == 0) {
// We have run all input tasks. We *must* notify Gecko so that it will
// send us more data.
MaybeRunOnMainThread(
WrapTask(mCallback,
&GMPVideoDecoderCallback::InputDataExhausted));
}
}
if (FAILED(hr)) {
CK_LOGE("VideoDecoder::DecodeTask() output failed hr=0x%x\n", hr);
}
}
return OutputFrame(aVideoFrame);
}
void
VideoDecoder::ReturnOutput(IMFSample* aSample,
int32_t aWidth,
int32_t aHeight,
int32_t aStride)
{
CK_LOGD("[%p] VideoDecoder::ReturnOutput()\n", this);
assert(aSample);
Status VideoDecoder::OutputFrame(VideoFrame* aVideoFrame) {
CK_LOGD("VideoDecoder::OutputFrame");
HRESULT hr;
HRESULT hr = S_OK;
GMPVideoFrame* f = nullptr;
auto err = mHostAPI->CreateFrame(kGMPI420VideoFrame, &f);
if (GMP_FAILED(err) || !f) {
CK_LOGE("Failed to create i420 frame!\n");
return;
}
if (HasShutdown()) {
// Note: GMPVideoHost::CreateFrame() can process messages before returning,
// including a message that calls VideoDecoder::DecodingComplete(), i.e.
// we can shutdown during the call!
CK_LOGD("Shutdown while waiting on GMPVideoHost::CreateFrame()!\n");
f->Destroy();
return;
// Read all the output from the decoder. Ideally, this would be a while loop
// where we read the output and check the result as the condition. However,
// this produces a memory leak connected to assigning a new CComPtr to the
// address of the old one, which avoids the CComPtr cleaning up.
while (true) {
CComPtr<IMFSample> output;
hr = mDecoder->Output(&output);
if (hr != S_OK) {
break;
}
auto vf = static_cast<GMPVideoi420Frame*>(f);
CK_LOGD("VideoDecoder::OutputFrame Decoder output ret=0x%x", hr);
hr = SampleToVideoFrame(aSample, aWidth, aHeight, aStride, vf);
ENSURE(SUCCEEDED(hr), /*void*/);
mOutputQueue.push(output);
CK_LOGD("VideoDecoder::OutputFrame: Queue size: %u", mOutputQueue.size());
}
mCallback->Decoded(vf);
// If we don't have any inputs, we need more data.
if (mOutputQueue.empty()) {
CK_LOGD("Decode failed. Not enought data; Requesting more input");
return Status::kNeedMoreData;
}
// We will get a MF_E_TRANSFORM_NEED_MORE_INPUT every time, as we always
// consume everything in the buffer.
if (hr != MF_E_TRANSFORM_NEED_MORE_INPUT && FAILED(hr)) {
CK_LOGD("Decode failed output ret=0x%x", hr);
return Status::kDecodeError;
}
CComPtr<IMFSample> result = mOutputQueue.front();
mOutputQueue.pop();
// The Chromium CDM API doesn't have support for negative strides, though
// they are theoretically possible in real world data.
if (mDecoder->GetStride() <= 0) {
CK_LOGD("VideoDecoder::OutputFrame Failed! (negative stride)");
return Status::kDecodeError;
}
hr = SampleToVideoFrame(result,
mDecoder->GetFrameWidth(),
mDecoder->GetFrameHeight(),
mDecoder->GetStride(),
aVideoFrame);
if (FAILED(hr)) {
CK_LOGD("VideoDecoder::OutputFrame Failed!");
return Status::kDecodeError;
}
CK_LOGD("VideoDecoder::OutputFrame Succeeded.");
return Status::kSuccess;
}
HRESULT
@ -262,14 +185,18 @@ VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
int32_t aWidth,
int32_t aHeight,
int32_t aStride,
GMPVideoi420Frame* aVideoFrame)
VideoFrame* aVideoFrame)
{
CK_LOGD("[%p] VideoDecoder::SampleToVideoFrame()", this);
ENSURE(aSample != nullptr, E_POINTER);
ENSURE(aVideoFrame != nullptr, E_POINTER);
HRESULT hr;
CComPtr<IMFMediaBuffer> mediaBuffer;
aVideoFrame->SetFormat(kI420);
// Must convert to contiguous mediaBuffer to use IMD2DBuffer interface.
hr = aSample->ConvertToContiguousBuffer(&mediaBuffer);
ENSURE(SUCCEEDED(hr), hr);
@ -285,46 +212,62 @@ VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
hr = twoDBuffer->Lock2D(&data, &stride);
ENSURE(SUCCEEDED(hr), hr);
} else {
hr = mediaBuffer->Lock(&data, NULL, NULL);
hr = mediaBuffer->Lock(&data, nullptr, nullptr);
ENSURE(SUCCEEDED(hr), hr);
stride = aStride;
}
// The V and U planes are stored 16-row-aligned, so we need to add padding
// The U and V planes are stored 16-row-aligned, so we need to add padding
// to the row heights to ensure the Y'CbCr planes are referenced properly.
// YV12, planar format: [YYYY....][VVVV....][UUUU....]
// i.e., Y, then V, then U.
// YV12, planar format: [YYYY....][UUUU....][VVVV....]
// i.e., Y, then U, then V.
uint32_t padding = 0;
if (aHeight % 16 != 0) {
padding = 16 - (aHeight % 16);
}
int32_t y_size = stride * (aHeight + padding);
int32_t v_size = stride * (aHeight + padding) / 4;
int32_t halfStride = (stride + 1) / 2;
int32_t halfHeight = (aHeight + 1) / 2;
uint32_t ySize = stride * (aHeight + padding);
uint32_t uSize = stride * (aHeight + padding) / 4;
uint32_t halfStride = (stride + 1) / 2;
uint32_t halfHeight = (aHeight + 1) / 2;
auto err = aVideoFrame->CreateEmptyFrame(stride, aHeight, stride, halfStride, halfStride);
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
aVideoFrame->SetStride(VideoFrame::kYPlane, stride);
aVideoFrame->SetStride(VideoFrame::kUPlane, halfStride);
aVideoFrame->SetStride(VideoFrame::kVPlane, halfStride);
err = aVideoFrame->SetWidth(aWidth);
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
err = aVideoFrame->SetHeight(aHeight);
ENSURE(GMP_SUCCEEDED(err), E_FAIL);
aVideoFrame->SetSize(Size(aWidth, aHeight));
uint8_t* outBuffer = aVideoFrame->Buffer(kGMPYPlane);
ENSURE(outBuffer != nullptr, E_FAIL);
assert(aVideoFrame->AllocatedSize(kGMPYPlane) >= stride*aHeight);
memcpy(outBuffer, data, stride*aHeight);
uint64_t bufferSize = ySize + 2 * uSize;
outBuffer = aVideoFrame->Buffer(kGMPUPlane);
ENSURE(outBuffer != nullptr, E_FAIL);
assert(aVideoFrame->AllocatedSize(kGMPUPlane) >= halfStride*halfHeight);
memcpy(outBuffer, data+y_size, halfStride*halfHeight);
// If the buffer is bigger than the max for a 32 bit, fail to avoid buffer
// overflows.
if (bufferSize > UINT32_MAX) {
CK_LOGD("VideoDecoder::SampleToFrame Buffersize bigger than UINT32_MAX");
return E_FAIL;
}
outBuffer = aVideoFrame->Buffer(kGMPVPlane);
ENSURE(outBuffer != nullptr, E_FAIL);
assert(aVideoFrame->AllocatedSize(kGMPVPlane) >= halfStride*halfHeight);
memcpy(outBuffer, data + y_size + v_size, halfStride*halfHeight);
// Get the buffer from the host.
Buffer* buffer = mHost->Allocate(bufferSize);
aVideoFrame->SetFrameBuffer(buffer);
// Make sure the buffer is non-null (allocate guarantees it will be of
// sufficient size).
if (!buffer) {
CK_LOGD("VideoDecoder::SampleToFrame Out of memory");
return E_OUTOFMEMORY;
}
uint8_t* outBuffer = buffer->Data();
aVideoFrame->SetPlaneOffset(VideoFrame::kYPlane, 0);
// Offset is the size of the copied y data.
aVideoFrame->SetPlaneOffset(VideoFrame::kUPlane, ySize);
// Offset is the size of the copied y data + the size of the copied u data.
aVideoFrame->SetPlaneOffset(VideoFrame::kVPlane, ySize + uSize);
// Copy the data.
memcpy(outBuffer, data, ySize + uSize * 2);
if (twoDBuffer) {
twoDBuffer->Unlock2D();
@ -335,84 +278,48 @@ VideoDecoder::SampleToVideoFrame(IMFSample* aSample,
LONGLONG hns = 0;
hr = aSample->GetSampleTime(&hns);
ENSURE(SUCCEEDED(hr), hr);
aVideoFrame->SetTimestamp(HNsToUsecs(hns));
hr = aSample->GetSampleDuration(&hns);
ENSURE(SUCCEEDED(hr), hr);
aVideoFrame->SetDuration(HNsToUsecs(hns));
aVideoFrame->SetTimestamp(HNsToUsecs(hns));
return S_OK;
}
void
VideoDecoder::ResetCompleteTask()
{
mIsFlushing = false;
if (mCallback) {
MaybeRunOnMainThread(WrapTask(mCallback,
&GMPVideoDecoderCallback::ResetComplete));
}
}
void
VideoDecoder::Reset()
{
mIsFlushing = true;
CK_LOGD("VideoDecoder::Reset");
if (mDecoder) {
mDecoder->Reset();
}
// Schedule ResetComplete callback to run after existing frames have been
// flushed out of the task queue.
EnsureWorker();
mWorkerThread->Post(WrapTaskRefCounted(this,
&VideoDecoder::ResetCompleteTask));
// Remove all the frames from the output queue.
while (!mOutputQueue.empty()) {
mOutputQueue.pop();
}
}
void
VideoDecoder::DrainTask()
Status
VideoDecoder::Drain(VideoFrame* aVideoFrame)
{
CK_LOGD("VideoDecoder::Drain()");
if (!mDecoder) {
CK_LOGD("Drain failed! Decoder was not initialized");
return Status::kDecodeError;
}
mDecoder->Drain();
// Return any pending output.
HRESULT hr = S_OK;
while (hr == S_OK) {
CComPtr<IMFSample> output;
hr = mDecoder->Output(&output);
CK_LOGD("VideoDecoder::DrainTask() output ret=0x%x\n", hr);
if (hr == S_OK) {
MaybeRunOnMainThread(
WrapTaskRefCounted(this,
&VideoDecoder::ReturnOutput,
CComPtr<IMFSample>(output),
mDecoder->GetFrameWidth(),
mDecoder->GetFrameHeight(),
mDecoder->GetStride()));
}
}
MaybeRunOnMainThread(WrapTask(mCallback, &GMPVideoDecoderCallback::DrainComplete));
}
void
VideoDecoder::Drain()
{
if (!mDecoder) {
if (mCallback) {
mCallback->DrainComplete();
}
return;
}
EnsureWorker();
mWorkerThread->Post(WrapTaskRefCounted(this,
&VideoDecoder::DrainTask));
return OutputFrame(aVideoFrame);
}
void
VideoDecoder::DecodingComplete()
{
if (mWorkerThread) {
mWorkerThread->Join();
}
CK_LOGD("VideoDecoder::DecodingComplete()");
mHasShutdown = true;
// Release the reference we added in the constructor. There may be
@ -420,36 +327,3 @@ VideoDecoder::DecodingComplete()
// us alive a little longer.
Release();
}
void
VideoDecoder::MaybeRunOnMainThread(GMPTask* aTask)
{
class MaybeRunTask : public GMPTask
{
public:
MaybeRunTask(VideoDecoder* aDecoder, GMPTask* aTask)
: mDecoder(aDecoder), mTask(aTask)
{ }
virtual void Run(void) {
if (mDecoder->HasShutdown()) {
CK_LOGD("Trying to dispatch to main thread after VideoDecoder has shut down");
return;
}
mTask->Run();
}
virtual void Destroy()
{
mTask->Destroy();
delete this;
}
private:
RefPtr<VideoDecoder> mDecoder;
GMPTask* mTask;
};
GetPlatform()->runonmainthread(new MaybeRunTask(this, aTask));
}

Просмотреть файл

@ -18,37 +18,28 @@
#define __VideoDecoder_h__
#include <atomic>
#include <queue>
#include <thread>
#include "gmp-task-utils.h"
#include "gmp-video-decode.h"
#include "gmp-video-host.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#include "WMFH264Decoder.h"
#include "mfobjects.h"
class VideoDecoder : public GMPVideoDecoder
, public RefCounted
class VideoDecoder : public RefCounted
{
public:
explicit VideoDecoder(GMPVideoHost *aHostAPI);
explicit VideoDecoder(cdm::Host_8 *aHost);
virtual void InitDecode(const GMPVideoCodec& aCodecSettings,
const uint8_t* aCodecSpecific,
uint32_t aCodecSpecificLength,
GMPVideoDecoderCallback* aCallback,
int32_t aCoreCount) override;
cdm::Status InitDecode(const cdm::VideoDecoderConfig& aConfig);
virtual void Decode(GMPVideoEncodedFrame* aInputFrame,
bool aMissingFrames,
const uint8_t* aCodecSpecific,
uint32_t aCodecSpecificLength,
int64_t aRenderTimeMs = -1);
cdm::Status Decode(const cdm::InputBuffer& aEncryptedBuffer,
cdm::VideoFrame* aVideoFrame);
virtual void Reset() override;
void Reset();
virtual void Drain() override;
virtual void DecodingComplete() override;
void DecodingComplete();
bool HasShutdown() { return mHasShutdown; }
@ -56,53 +47,26 @@ private:
virtual ~VideoDecoder();
void EnsureWorker();
void DrainTask();
cdm::Status Drain(cdm::VideoFrame* aVideoFrame);
struct DecodeData {
DecodeData()
: mTimestamp(0)
, mDuration(0)
, mIsKeyframe(false)
{}
std::vector<uint8_t> mBuffer;
uint64_t mTimestamp;
uint64_t mDuration;
bool mIsKeyframe;
uint64_t mTimestamp = 0;
CryptoMetaData mCrypto;
};
void DecodeTask(DecodeData* aData);
void ResetCompleteTask();
void ReturnOutput(IMFSample* aSample,
int32_t aWidth,
int32_t aHeight,
int32_t aStride);
cdm::Status OutputFrame(cdm::VideoFrame* aVideoFrame);
HRESULT SampleToVideoFrame(IMFSample* aSample,
int32_t aWidth,
int32_t aHeight,
int32_t aStride,
GMPVideoi420Frame* aVideoFrame);
cdm::VideoFrame* aVideoFrame);
void MaybeRunOnMainThread(GMPTask* aTask);
GMPVideoHost *mHostAPI; // host-owned, invalid at DecodingComplete
GMPVideoDecoderCallback* mCallback; // host-owned, invalid at DecodingComplete
GMPThread* mWorkerThread;
GMPMutex* mMutex;
cdm::Host_8* mHost;
wmf::AutoPtr<wmf::WMFH264Decoder> mDecoder;
std::vector<uint8_t> mExtraData;
std::vector<uint8_t> mAnnexB;
int32_t mNumInputTasks;
bool mSentExtraData;
std::atomic<bool> mIsFlushing;
std::queue<wmf::CComPtr<IMFSample>> mOutputQueue;
bool mHasShutdown;
};

Просмотреть файл

@ -196,7 +196,6 @@ HRESULT
WMFH264Decoder::CreateInputSample(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp,
Microseconds aDuration,
IMFSample** aOutSample)
{
HRESULT hr;
@ -231,8 +230,6 @@ WMFH264Decoder::CreateInputSample(const uint8_t* aData,
hr = sample->SetSampleTime(UsecsToHNs(aTimestamp));
ENSURE(SUCCEEDED(hr), hr);
sample->SetSampleDuration(UsecsToHNs(aDuration));
*aOutSample = sample.Detach();
return S_OK;
@ -301,12 +298,11 @@ WMFH264Decoder::GetOutputSample(IMFSample** aOutSample)
HRESULT
WMFH264Decoder::Input(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp,
Microseconds aDuration)
Microseconds aTimestamp)
{
HRESULT hr;
CComPtr<IMFSample> input = nullptr;
hr = CreateInputSample(aData, aDataSize, aTimestamp, aDuration, &input);
hr = CreateInputSample(aData, aDataSize, aTimestamp, &input);
ENSURE(SUCCEEDED(hr) && input!=nullptr, hr);
hr = mDecoder->ProcessInput(0, input, 0);

Просмотреть файл

@ -30,8 +30,7 @@ public:
HRESULT Input(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp,
Microseconds aDuration);
Microseconds aTimestamp);
HRESULT Output(IMFSample** aOutput);
@ -53,7 +52,6 @@ private:
HRESULT CreateInputSample(const uint8_t* aData,
uint32_t aDataSize,
Microseconds aTimestamp,
Microseconds aDuration,
IMFSample** aOutSample);
HRESULT CreateOutputSample(IMFSample** aOutSample);

Просмотреть файл

@ -119,8 +119,8 @@ typedef int64_t Microseconds;
#define ENSURE(condition, ret) \
{ if (!(condition)) { LOG("##condition## FAILED %S:%d\n", __FILE__, __LINE__); return ret; } }
#define GMP_SUCCEEDED(x) ((x) == GMPNoErr)
#define GMP_FAILED(x) ((x) != GMPNoErr)
#define STATUS_SUCCEEDED(x) ((x) == Status::kSuccess)
#define STATUS_FAILED(x) ((x) != Status::kSuccess)
#define MFPLAT_FUNC(_func, _dllname) \
extern decltype(::_func)* _func;

Просмотреть файл

@ -1,10 +0,0 @@
Name: clearkey
Description: ClearKey Gecko Media Plugin
Version: 1
#ifdef ENABLE_WMF
APIs: eme-decrypt-v9[org.w3.clearkey], decode-video[h264:org.w3.clearkey]
Libraries: dxva2.dll, d3d9.dll, msmpeg2vdec.dll, msmpeg2adec.dll, MSAudDecMFT.dll, evr.dll, mfheaacdec.dll, mfh264dec.dll, mfplat.dll
#else
APIs: eme-decrypt-v9[org.w3.clearkey]
Libraries:
#endif

Просмотреть файл

@ -18,68 +18,47 @@
#include <stdio.h>
#include <string.h>
#include "ClearKeyAsyncShutdown.h"
#include "ClearKeyCDM.h"
#include "ClearKeySessionManager.h"
#include "gmp-api/gmp-async-shutdown.h"
#include "gmp-api/gmp-decryption.h"
#include "gmp-api/gmp-platform.h"
// This include is required in order for content_decryption_module to work
// on Unix systems.
#include "stddef.h"
#include "content_decryption_module.h"
#if defined(ENABLE_WMF)
#ifdef ENABLE_WMF
#include "WMFUtils.h"
#include "VideoDecoder.h"
#endif
#if defined(WIN32)
#define GMP_EXPORT __declspec(dllexport)
#else
#define GMP_EXPORT __attribute__((visibility("default")))
#endif
static GMPPlatformAPI* sPlatform = nullptr;
GMPPlatformAPI*
GetPlatform()
{
return sPlatform;
}
#endif // ENABLE_WMF
extern "C" {
GMP_EXPORT GMPErr
GMPInit(GMPPlatformAPI* aPlatformAPI)
{
sPlatform = aPlatformAPI;
return GMPNoErr;
CDM_EXPORT
void INITIALIZE_CDM_MODULE() {
}
GMP_EXPORT GMPErr
GMPGetAPI(const char* aApiName, void* aHostAPI, void** aPluginAPI)
CDM_EXPORT
void* CreateCdmInstance(int cdm_interface_version,
const char* key_system,
uint32_t key_system_size,
GetCdmHostFunc get_cdm_host_func,
void* user_data)
{
CK_LOGD("ClearKey GMPGetAPI |%s|", aApiName);
assert(!*aPluginAPI);
if (!strcmp(aApiName, GMP_API_DECRYPTOR)) {
*aPluginAPI = new ClearKeySessionManager();
}
#if defined(ENABLE_WMF)
else if (!strcmp(aApiName, GMP_API_VIDEO_DECODER) &&
wmf::EnsureLibs()) {
*aPluginAPI = new VideoDecoder(static_cast<GMPVideoHost*>(aHostAPI));
CK_LOGE("ClearKey CreateCDMInstance");
#ifdef ENABLE_WMF
if (!wmf::EnsureLibs()) {
CK_LOGE("Required libraries were not found");
return nullptr;
}
#endif
else if (!strcmp(aApiName, GMP_API_ASYNC_SHUTDOWN)) {
*aPluginAPI = new ClearKeyAsyncShutdown(static_cast<GMPAsyncShutdownHost*> (aHostAPI));
} else {
CK_LOGE("GMPGetAPI couldn't resolve API name |%s|\n", aApiName);
}
return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
}
cdm::Host_8* host = static_cast<cdm::Host_8*>(
get_cdm_host_func(cdm_interface_version, user_data));
ClearKeyCDM* clearKey = new ClearKeyCDM(host);
GMP_EXPORT GMPErr
GMPShutdown(void)
{
CK_LOGD("ClearKey GMPShutdown");
return GMPNoErr;
}
CK_LOGE("Created ClearKeyCDM instance!");
return clearKey;
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,47 +0,0 @@
/*
* Copyright 2015, Mozilla Foundation and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Original author: ekr@rtfm.com
#ifndef gmp_task_utils_h_
#define gmp_task_utils_h_
#include "gmp-api/gmp-platform.h"
class gmp_task_args_base : public GMPTask {
public:
virtual void Destroy() { delete this; }
virtual void Run() = 0;
};
// The generated file contains four major function templates
// (in variants for arbitrary numbers of arguments up to 10,
// which is why it is machine generated). The four templates
// are:
//
// WrapTask(o, m, ...) -- wraps a member function m of an object ptr o
// WrapTaskRet(o, m, ..., r) -- wraps a member function m of an object ptr o
// the function returns something that can
// be assigned to *r
// WrapTaskNM(f, ...) -- wraps a function f
// WrapTaskNMRet(f, ..., r) -- wraps a function f that returns something
// that can be assigned to *r
//
// All of these template functions return a GMPTask* which can be passed
// to DispatchXX().
#include "gmp-task-utils-generated.h"
#endif // gmp_task_utils_h_

Просмотреть файл

@ -0,0 +1,13 @@
{
"name": "clearkey",
"description": "ClearKey Gecko Media Plugin",
"version": "1",
"x-cdm-module-versions": "4",
"x-cdm-interface-versions": "8",
"x-cdm-host-versions": "8",
#ifdef ENABLE_WMF
"x-cdm-codecs": "avc1"
#else
"x-cdm-codecs": ""
#endif
}

Просмотреть файл

@ -8,11 +8,11 @@ SharedLibrary('clearkey')
FINAL_TARGET = 'dist/bin/gmp-clearkey/0.1'
FINAL_TARGET_PP_FILES += ['clearkey.info.in']
FINAL_TARGET_PP_FILES += ['manifest.json.in']
UNIFIED_SOURCES += [
'ClearKeyAsyncShutdown.cpp',
'ClearKeyBase64.cpp',
'ClearKeyCDM.cpp',
'ClearKeyDecryptionManager.cpp',
'ClearKeyPersistence.cpp',
'ClearKeySession.cpp',
@ -28,7 +28,6 @@ SOURCES += [
if CONFIG['OS_ARCH'] == 'WINNT':
UNIFIED_SOURCES += [
'AnnexB.cpp',
'VideoDecoder.cpp',
'WMFH264Decoder.cpp',
]
@ -43,15 +42,13 @@ if CONFIG['OS_ARCH'] == 'WINNT':
DEFINES['ENABLE_WMF'] = True
DEFINES['CDM_IMPLEMENTATION'] = True
TEST_DIRS += [
'gtest',
]
LOCAL_INCLUDES += [
'/dom/media/gmp',
]
DISABLE_STL_WRAPPING = True
DEFINES['MOZ_NO_MOZALLOC'] = True

Просмотреть файл

@ -120,12 +120,14 @@ void
WebrtcVideoConduit::SendStreamStatistics::Update(
const webrtc::VideoSendStream::Stats& aStats)
{
CSFLogVerbose(logTag, "SendStreamStatistics::Update %s", __FUNCTION__);
StreamStatistics::Update(aStats.encode_frame_rate, aStats.media_bitrate_bps);
if (!aStats.substreams.empty()) {
const webrtc::FrameCounts& fc =
aStats.substreams.begin()->second.frame_counts;
mDroppedFrames = mSentFrames - fc.key_frames + fc.delta_frames;
CSFLogVerbose(logTag, "%s: framerate: %u, bitrate: %u, dropped frames delta: %u",
__FUNCTION__, aStats.encode_frame_rate, aStats.media_bitrate_bps,
(mSentFrames - (fc.key_frames + fc.delta_frames)) - mDroppedFrames);
mDroppedFrames = mSentFrames - (fc.key_frames + fc.delta_frames);
} else {
CSFLogVerbose(logTag, "%s aStats.substreams is empty", __FUNCTION__);
}

Просмотреть файл

@ -43,6 +43,7 @@ import org.mozilla.gecko.distribution.Distribution;
import org.mozilla.gecko.restrictions.Restrictions;
import org.mozilla.gecko.util.RawResource;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.preferences.GeckoPreferences;
/**
* {@code SuggestedSites} provides API to get a list of locale-specific
@ -68,10 +69,12 @@ public class SuggestedSites {
private static final String LOGTAG = "GeckoSuggestedSites";
// SharedPreference key for suggested sites that should be hidden.
public static final String PREF_SUGGESTED_SITES_HIDDEN = "suggestedSites.hidden";
public static final String PREF_SUGGESTED_SITES_HIDDEN = GeckoPreferences.NON_PREF_PREFIX + "suggestedSites.hidden";
public static final String PREF_SUGGESTED_SITES_HIDDEN_OLD = "suggestedSites.hidden";
// Locale used to generate the current suggested sites.
public static final String PREF_SUGGESTED_SITES_LOCALE = "suggestedSites.locale";
public static final String PREF_SUGGESTED_SITES_LOCALE = GeckoPreferences.NON_PREF_PREFIX + "suggestedSites.locale";
public static final String PREF_SUGGESTED_SITES_LOCALE_OLD = "suggestedSites.locale";
// File in profile dir with the list of suggested sites.
private static final String FILENAME = "suggestedsites.json";
@ -182,7 +185,16 @@ public class SuggestedSites {
private static boolean isNewLocale(Context context, Locale requestedLocale) {
final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
String locale = prefs.getString(PREF_SUGGESTED_SITES_LOCALE, null);
String locale = prefs.getString(PREF_SUGGESTED_SITES_LOCALE_OLD, null);
if (locale != null) {
// Migrate the old pref and remove it
final Editor editor = prefs.edit();
editor.remove(PREF_SUGGESTED_SITES_LOCALE_OLD);
editor.putString(PREF_SUGGESTED_SITES_LOCALE, locale);
editor.apply();
} else {
locale = prefs.getString(PREF_SUGGESTED_SITES_LOCALE, null);
}
if (locale == null) {
// Initialize config with the current locale
updateSuggestedSitesLocale(context);
@ -531,8 +543,17 @@ public class SuggestedSites {
Log.d(LOGTAG, "Loading blacklisted suggested sites from SharedPreferences.");
final Set<String> blacklist = new HashSet<String>();
final SharedPreferences preferences = GeckoSharedPrefs.forProfile(context);
final String sitesString = preferences.getString(PREF_SUGGESTED_SITES_HIDDEN, null);
final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
String sitesString = prefs.getString(PREF_SUGGESTED_SITES_HIDDEN_OLD, null);
if (sitesString != null) {
// Migrate the old pref and remove it
final Editor editor = prefs.edit();
editor.remove(PREF_SUGGESTED_SITES_HIDDEN_OLD);
editor.putString(PREF_SUGGESTED_SITES_HIDDEN, sitesString);
editor.apply();
} else {
sitesString = prefs.getString(PREF_SUGGESTED_SITES_HIDDEN, null);
}
if (sitesString != null) {
for (String site : sitesString.trim().split(" ")) {

Просмотреть файл

@ -1230,9 +1230,8 @@ final class GeckoEditable extends JNIObject
// with Gecko here.
mIgnoreSelectionChange = false;
} else if (indexInText == 0 && text.length() == action.mSequence.length() &&
oldEnd - start == action.mEnd - action.mStart) {
// The new change exactly matches our saved change, so do a direct replace.
} else if (indexInText == 0 && text.length() == action.mSequence.length()) {
// The new text exactly matches our sequence, so do a direct replace.
mText.currentReplace(start, oldEnd, action.mSequence);
// Ignore the next selection change because the selection change is a

Просмотреть файл

@ -6717,7 +6717,10 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st
// If we are using the transaction to serve content, we also save the
// time since async open in the cache entry so we can compare telemetry
// between cache and net response.
if (request == mTransactionPump && mCacheEntry &&
// Do not store the time of conditional requests because even if we
// fetch the data from the server, the time includes loading of the old
// cache entry which would skew the network load time.
if (request == mTransactionPump && mCacheEntry && !mDidReval &&
!mAsyncOpenTime.IsNull() && !mOnStartRequestTimestamp.IsNull()) {
nsAutoCString onStartTime;
onStartTime.AppendInt( (uint64_t) (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds());

Просмотреть файл

@ -1647,9 +1647,11 @@ toolbar#nav-bar {
"testing.browserTestHarness.timeout=%d" %
options.timeout)
# browser-chrome tests use a fairly short default timeout of 45 seconds;
# this is sometimes too short on asan, where we expect reduced performance.
if mozinfo.info["asan"] and options.flavor == 'browser' and options.timeout is None:
self.log.info("Increasing default timeout to 90 seconds on ASAN")
# this is sometimes too short on asan and debug, where we expect reduced
# performance.
if (mozinfo.info["asan"] or mozinfo.info["debug"]) and \
options.flavor == 'browser' and options.timeout is None:
self.log.info("Increasing default timeout to 90 seconds")
options.extraPrefs.append("testing.browserTestHarness.timeout=90")
options.extraPrefs.append(

Просмотреть файл

@ -82,12 +82,12 @@ function Readability(uri, doc, options) {
return rv + elDesc;
};
this.log = function () {
if (typeof dump !== undefined) {
if (typeof dump !== "undefined") {
var msg = Array.prototype.map.call(arguments, function(x) {
return (x && x.nodeName) ? logEl(x) : x;
}).join(" ");
dump("Reader: (Readability) " + msg + "\n");
} else if (typeof console !== undefined) {
} else if (typeof console !== "undefined") {
var args = ["Reader: (Readability) "].concat(arguments);
console.log.apply(console, args);
}
@ -119,7 +119,7 @@ Readability.prototype = {
// All of the regular expressions in use within readability.
// Defined up here so we don't instantiate them repeatedly in loops.
REGEXPS: {
unlikelyCandidates: /banner|combx|comment|community|cover-wrap|disqus|extra|foot|header|legends|menu|modal|related|remark|rss|shoutbox|sidebar|skyscraper|sponsor|ad-break|agegate|pagination|pager|popup|yom-remote/i,
unlikelyCandidates: /banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|foot|header|legends|menu|modal|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|ad-break|agegate|pagination|pager|popup|yom-remote/i,
okMaybeItsACandidate: /and|article|body|column|main|shadow/i,
positive: /article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story/i,
negative: /hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|modal|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i,
@ -477,6 +477,7 @@ Readability.prototype = {
// Clean out junk from the article content
this._cleanConditionally(articleContent, "form");
this._cleanConditionally(articleContent, "fieldset");
this._clean(articleContent, "object");
this._clean(articleContent, "embed");
this._clean(articleContent, "h1");
@ -494,6 +495,10 @@ Readability.prototype = {
this._clean(articleContent, "h2");
this._clean(articleContent, "iframe");
this._clean(articleContent, "input");
this._clean(articleContent, "textarea");
this._clean(articleContent, "select");
this._clean(articleContent, "button");
this._cleanHeaders(articleContent);
// Do these last as the previous stuff may have removed junk
@ -846,6 +851,33 @@ Readability.prototype = {
this._initializeNode(topCandidate);
} else if (topCandidate) {
// Find a better top candidate node if it contains (at least three) nodes which belong to `topCandidates` array
// and whose scores are quite closed with current `topCandidate` node.
var alternativeCandidateAncestors = [];
for (var i = 1; i < topCandidates.length; i++) {
if (topCandidates[i].readability.contentScore / topCandidate.readability.contentScore >= 0.75) {
alternativeCandidateAncestors.push(this._getNodeAncestors(topCandidates[i]));
}
}
var MINIMUM_TOPCANDIDATES = 3;
if (alternativeCandidateAncestors.length >= MINIMUM_TOPCANDIDATES) {
parentOfTopCandidate = topCandidate.parentNode;
while (parentOfTopCandidate.tagName !== "BODY") {
var listsContainingThisAncestor = 0;
for (var ancestorIndex = 0; ancestorIndex < alternativeCandidateAncestors.length && listsContainingThisAncestor < MINIMUM_TOPCANDIDATES; ancestorIndex++) {
listsContainingThisAncestor += Number(alternativeCandidateAncestors[ancestorIndex].includes(parentOfTopCandidate));
}
if (listsContainingThisAncestor >= MINIMUM_TOPCANDIDATES) {
topCandidate = parentOfTopCandidate;
break;
}
parentOfTopCandidate = parentOfTopCandidate.parentNode;
}
}
if (!topCandidate.readability) {
this._initializeNode(topCandidate);
}
// Because of our bonus system, parents of candidates might have scores
// themselves. They get half of the node. There won't be nodes with higher
// scores than our topCandidate, but if we see the score going *up* in the first
@ -857,7 +889,11 @@ Readability.prototype = {
var lastScore = topCandidate.readability.contentScore;
// The scores shouldn't get too low.
var scoreThreshold = lastScore / 3;
while (parentOfTopCandidate && parentOfTopCandidate.readability) {
while (parentOfTopCandidate.tagName !== "BODY") {
if (!parentOfTopCandidate.readability) {
parentOfTopCandidate = parentOfTopCandidate.parentNode;
continue;
}
var parentScore = parentOfTopCandidate.readability.contentScore;
if (parentScore < scoreThreshold)
break;
@ -1241,11 +1277,6 @@ Readability.prototype = {
segment = segment.split(".")[0];
}
// EW-CMS specific segment replacement. Ugly.
// Example: http://www.ew.com/ew/article/0,,20313460_20369436,00.html
if (segment.indexOf(',00') !== -1)
segment = segment.replace(',00', '');
// If our first or second segment has anything looking like a page number, remove it.
if (segment.match(/((_|-)?p[a-z]*|(_|-))[0-9]{1,2}$/i) && ((i === 1) || (i === 0)))
segment = segment.replace(/((_|-)?p[a-z]*|(_|-))[0-9]{1,2}$/i, "");
@ -1713,11 +1744,10 @@ Readability.prototype = {
var contentLength = this._getInnerText(node).length;
var haveToRemove =
// Make an exception for elements with no p's and exactly 1 img.
(img > p && !this._hasAncestorTag(node, "figure")) ||
(img > 1 && img > p && !this._hasAncestorTag(node, "figure")) ||
(!isList && li > p) ||
(input > Math.floor(p/3)) ||
(!isList && contentLength < 25 && (img === 0 || img > 2)) ||
(!isList && contentLength < 25 && (img === 0 || img > 2) && !this._hasAncestorTag(node, "figure")) ||
(!isList && weight < 25 && linkDensity > 0.2) ||
(weight >= 25 && linkDensity > 0.5) ||
((embedCount === 1 && contentLength < 75) || embedCount > 1);
@ -1912,3 +1942,7 @@ Readability.prototype = {
};
}
};
if (typeof module === "object") {
module.exports = Readability;
}

Просмотреть файл

@ -138,7 +138,7 @@ ParamTraits<mozilla::Telemetry::ScalarAction>
}
case nsITelemetry::SCALAR_STRING:
{
nsString val;
nsAutoString val;
nsresult rv = aParam.mData->GetAsAString(val);
if (NS_FAILED(rv)) {
MOZ_ASSERT(false, "Conversion failed.");
@ -189,7 +189,7 @@ ParamTraits<mozilla::Telemetry::ScalarAction>
}
case nsITelemetry::SCALAR_STRING:
{
nsString data;
nsAutoString data;
// De-serialize the data.
if (!ReadParam(aMsg, aIter, &data) ||
NS_FAILED(outVar->SetAsAString(data))) {

Просмотреть файл

@ -47,6 +47,7 @@ using namespace mozilla;
using mozilla::MutexAutoLock;
nsTArray<GfxDriverInfo>* GfxInfoBase::mDriverInfo;
nsTArray<dom::GfxInfoFeatureStatus>* GfxInfoBase::mFeatureStatus;
bool GfxInfoBase::mDriverInfoObserverInitialized;
bool GfxInfoBase::mShutdownOccurred;
@ -68,6 +69,9 @@ public:
delete GfxInfoBase::mDriverInfo;
GfxInfoBase::mDriverInfo = nullptr;
delete GfxInfoBase::mFeatureStatus;
GfxInfoBase::mFeatureStatus = nullptr;
for (uint32_t i = 0; i < DeviceFamilyMax; i++)
delete GfxDriverInfo::mDeviceFamilies[i];
@ -599,12 +603,17 @@ GfxInfoBase::GetFeatureStatus(int32_t aFeature, nsACString& aFailureId, int32_t*
}
if (XRE_IsContentProcess()) {
// Delegate to the parent process.
mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
bool success;
nsCString remoteFailureId;
cc->SendGetGraphicsFeatureStatus(aFeature, aStatus, &remoteFailureId, &success);
aFailureId = remoteFailureId;
// Use the cached data received from the parent process.
MOZ_ASSERT(mFeatureStatus);
bool success = false;
for (const auto& fs : *mFeatureStatus) {
if (fs.feature() == aFeature) {
aFailureId = fs.failureId();
*aStatus = fs.status();
success = true;
break;
}
}
return success ? NS_OK : NS_ERROR_FAILURE;
}
@ -847,6 +856,13 @@ GfxInfoBase::FindBlocklistedDeviceInList(const nsTArray<GfxDriverInfo>& info,
return status;
}
void
GfxInfoBase::SetFeatureStatus(const nsTArray<dom::GfxInfoFeatureStatus>& aFS)
{
MOZ_ASSERT(!mFeatureStatus);
mFeatureStatus = new nsTArray<dom::GfxInfoFeatureStatus>(aFS);
}
nsresult
GfxInfoBase::GetFeatureStatusImpl(int32_t aFeature,
int32_t* aStatus,

Просмотреть файл

@ -16,6 +16,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/Mutex.h"
#include "mozilla/dom/PContentParent.h"
#include "nsCOMPtr.h"
#include "nsIGfxInfo.h"
#include "nsIGfxInfoDebug.h"
@ -77,6 +78,7 @@ public:
static void RemoveCollector(GfxInfoCollectorBase* collector);
static nsTArray<GfxDriverInfo>* mDriverInfo;
static nsTArray<mozilla::dom::GfxInfoFeatureStatus>* mFeatureStatus;
static bool mDriverInfoObserverInitialized;
static bool mShutdownOccurred;
@ -93,6 +95,9 @@ public:
return NS_ERROR_NOT_IMPLEMENTED;
}
static void SetFeatureStatus(
const nsTArray<mozilla::dom::GfxInfoFeatureStatus>& aFS);
protected:
virtual ~GfxInfoBase();

Просмотреть файл

@ -121,6 +121,7 @@ public:
enum buttonType
{
eNoButton = -1,
eLeftButton = 0,
eMiddleButton = 1,
eRightButton = 2

Просмотреть файл

@ -121,6 +121,8 @@ interface nsIGfxInfo : nsISupports
const long FEATURE_DX_INTEROP2 = 19;
/* Whether the GPU process is supported, starting in 52. */
const long FEATURE_GPU_PROCESS = 20;
/* the maximum feature value. */
const long FEATURE_MAX_VALUE = FEATURE_GPU_PROCESS;
/*
* A set of return values from GetFeatureStatus

Просмотреть файл

@ -4020,39 +4020,14 @@ function runSetSelectionEventTest()
contenteditable.innerHTML = "a<blink>b</blink>c";
synthesizeSelectionSet(0, 3);
is(selection.anchorNode, contenteditable.firstChild,
"runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
"runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first text node");
is(selection.anchorOffset, 0,
"runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
"runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
is(selection.focusNode, contenteditable.lastChild,
"runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
"runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the last text node");
is(selection.focusOffset, contenteditable.lastChild.wholeText.length,
"runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
"runSetSelectionEventTest #15 (0, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
checkSelection(0, "abc", "runSetSelectionEventTest #16 (0, 3), \"" + contenteditable.innerHTML + "\"");
// #17 (bug 1319660 - incorrect adjustment of content iterator last node)
contenteditable.innerHTML = "<div>a</div><div><br></div>";
synthesizeSelectionSet(kLFLen, 1+kLFLen);
is(selection.anchorNode, contenteditable.firstChild,
"runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor node should be the first <div> element");
is(selection.anchorOffset, 0,
"runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
is(selection.focusNode, contenteditable.lastChild,
"runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
is(selection.focusOffset, 0,
"runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
checkSelection(kLFLen, "a" + kLF, "runSetSelectionEventTest #17 (kLFLen, 1+kLFLen), \"" + contenteditable.innerHTML + "\"");
synthesizeSelectionSet(1+2*kLFLen, 0);
is(selection.anchorNode, contenteditable.lastChild,
"runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor node should be the second <div> element");
is(selection.anchorOffset, 0,
"runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection anchor offset should be 0");
is(selection.focusNode, contenteditable.lastChild,
"runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus node should be the second <div> element");
is(selection.focusOffset, 0,
"runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\": selection focus offset should be the length of the last text node");
checkSelection(1+2*kLFLen, "", "runSetSelectionEventTest #17 (1+2*kLFLen, 0), \"" + contenteditable.innerHTML + "\"");
}
function runQueryTextContentEventTest()