зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to m-c a=merge
MozReview-Commit-ID: 3cGydlfeaPN
This commit is contained in:
Коммит
54c460dbd6
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
mControl->SerializeStream(aReadStreamOut, mStream, aStreamCleanupList);
|
||||
|
||||
{
|
||||
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,13 +125,19 @@ 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);
|
||||
}
|
||||
*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,9 +795,15 @@ AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState)
|
|||
|
||||
if (aPromise) {
|
||||
Promise* promise = reinterpret_cast<Promise*>(aPromise);
|
||||
promise->MaybeResolveWithUndefined();
|
||||
DebugOnly<bool> rv = mPromiseGripArray.RemoveElement(promise);
|
||||
MOZ_ASSERT(rv, "Promise wasn't in the grip array?");
|
||||
// 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) {
|
||||
|
|
|
@ -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,7 +114,8 @@ class MOZ_RAII AutoStubFrame
|
|||
|
||||
MOZ_ASSERT(!compiler.inStubFrame_);
|
||||
compiler.inStubFrame_ = true;
|
||||
compiler.makesGCCalls_ = true;
|
||||
if (canGC == CallCanGC::CanGC)
|
||||
compiler.makesGCCalls_ = true;
|
||||
}
|
||||
void leave(MacroAssembler& masm, bool calledIntoIon = false) {
|
||||
MOZ_ASSERT(compiler.inStubFrame_);
|
||||
|
@ -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++) {
|
||||
if (!iter->isCacheIR_Monitored())
|
||||
continue;
|
||||
|
||||
ICCacheIR_Monitored* otherStub = iter->toCacheIR_Monitored();
|
||||
if (otherStub->stubInfo() != stubInfo)
|
||||
continue;
|
||||
if (!writer.stubDataEquals(otherStub->stubDataStart()))
|
||||
continue;
|
||||
switch (stubKind) {
|
||||
case CacheIRStubKind::Monitored: {
|
||||
if (!iter->isCacheIR_Monitored())
|
||||
continue;
|
||||
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();
|
||||
auto newStub = new(newStubMem) ICCacheIR_Monitored(code, monitorStub, stubInfo);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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,22 +5712,34 @@ 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);
|
||||
|
||||
trackOptimizationAttempt(TrackedStrategy::NewArray_TemplateObject);
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationAttempt(TrackedStrategy::NewArray_TemplateObject);
|
||||
|
||||
if (!templateObject) {
|
||||
trackOptimizationOutcome(TrackedOutcome::NoTemplateObject);
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationOutcome(TrackedOutcome::NoTemplateObject);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
if (templateObject->is<UnboxedArrayObject>()) {
|
||||
MOZ_ASSERT(templateObject->as<UnboxedArrayObject>().capacity() >= length);
|
||||
if (!templateObject->as<UnboxedArrayObject>().hasInlineElements()) {
|
||||
trackOptimizationOutcome(TrackedOutcome::TemplateObjectIsUnboxedWithoutInlineElements);
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationOutcome(TrackedOutcome::TemplateObjectIsUnboxedWithoutInlineElements);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
@ -5738,7 +5750,8 @@ IonBuilder::newArrayTryTemplateObject(bool* emitted, JSObject* templateObject, u
|
|||
gc::GetGCKindSlots(templateObject->asTenured().getAllocKind()) - ObjectElements::VALUES_PER_HEADER;
|
||||
|
||||
if (length > arraySlots) {
|
||||
trackOptimizationOutcome(TrackedOutcome::LengthTooBig);
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationOutcome(TrackedOutcome::LengthTooBig);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
@ -5752,7 +5765,8 @@ IonBuilder::newArrayTryTemplateObject(bool* emitted, JSObject* templateObject, u
|
|||
current->add(ins);
|
||||
current->push(ins);
|
||||
|
||||
trackOptimizationSuccess();
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationSuccess();
|
||||
*emitted = true;
|
||||
return Ok();
|
||||
}
|
||||
|
@ -5770,7 +5784,8 @@ IonBuilder::newArrayTrySharedStub(bool* emitted)
|
|||
if (*pc != JSOP_NEWINIT && *pc != JSOP_NEWARRAY)
|
||||
return Ok();
|
||||
|
||||
trackOptimizationAttempt(TrackedStrategy::NewArray_SharedCache);
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationAttempt(TrackedStrategy::NewArray_SharedCache);
|
||||
|
||||
MInstruction* stub = MNullarySharedStub::New(alloc());
|
||||
current->add(stub);
|
||||
|
@ -5782,7 +5797,8 @@ IonBuilder::newArrayTrySharedStub(bool* emitted)
|
|||
current->add(unbox);
|
||||
current->push(unbox);
|
||||
|
||||
trackOptimizationSuccess();
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationSuccess();
|
||||
|
||||
*emitted = true;
|
||||
return Ok();
|
||||
|
@ -5794,7 +5810,8 @@ IonBuilder::newArrayTryVM(bool* emitted, JSObject* templateObject, uint32_t leng
|
|||
MOZ_ASSERT(*emitted == false);
|
||||
|
||||
// Emit a VM call.
|
||||
trackOptimizationAttempt(TrackedStrategy::NewArray_Call);
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationAttempt(TrackedStrategy::NewArray_Call);
|
||||
|
||||
gc::InitialHeap heap = gc::DefaultHeap;
|
||||
MConstant* templateConst = MConstant::New(alloc(), NullValue());
|
||||
|
@ -5810,7 +5827,8 @@ IonBuilder::newArrayTryVM(bool* emitted, JSObject* templateObject, uint32_t leng
|
|||
current->add(ins);
|
||||
current->push(ins);
|
||||
|
||||
trackOptimizationSuccess();
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationSuccess();
|
||||
*emitted = true;
|
||||
return Ok();
|
||||
}
|
||||
|
@ -5835,7 +5853,8 @@ AbortReasonOr<Ok>
|
|||
IonBuilder::jsop_newarray(JSObject* templateObject, uint32_t length)
|
||||
{
|
||||
bool emitted = false;
|
||||
startTrackingOptimizations();
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
startTrackingOptimizations();
|
||||
|
||||
if (!forceInlineCaches()) {
|
||||
MOZ_TRY(newArrayTryTemplateObject(&emitted, templateObject, length));
|
||||
|
@ -5881,14 +5900,17 @@ IonBuilder::newObjectTryTemplateObject(bool* emitted, JSObject* templateObject)
|
|||
{
|
||||
MOZ_ASSERT(*emitted == false);
|
||||
|
||||
trackOptimizationAttempt(TrackedStrategy::NewObject_TemplateObject);
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationAttempt(TrackedStrategy::NewObject_TemplateObject);
|
||||
if (!templateObject) {
|
||||
trackOptimizationOutcome(TrackedOutcome::NoTemplateObject);
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationOutcome(TrackedOutcome::NoTemplateObject);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
if (templateObject->is<PlainObject>() && templateObject->as<PlainObject>().hasDynamicSlots()) {
|
||||
trackOptimizationOutcome(TrackedOutcome::TemplateObjectIsPlainObjectWithDynamicSlots);
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationOutcome(TrackedOutcome::TemplateObjectIsPlainObjectWithDynamicSlots);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
@ -5910,7 +5932,8 @@ IonBuilder::newObjectTryTemplateObject(bool* emitted, JSObject* templateObject)
|
|||
|
||||
MOZ_TRY(resumeAfter(ins));
|
||||
|
||||
trackOptimizationSuccess();
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationSuccess();
|
||||
*emitted = true;
|
||||
return Ok();
|
||||
}
|
||||
|
@ -5925,7 +5948,8 @@ IonBuilder::newObjectTrySharedStub(bool* emitted)
|
|||
if (JitOptions.disableSharedStubs)
|
||||
return Ok();
|
||||
|
||||
trackOptimizationAttempt(TrackedStrategy::NewObject_SharedCache);
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationAttempt(TrackedStrategy::NewObject_SharedCache);
|
||||
|
||||
MInstruction* stub = MNullarySharedStub::New(alloc());
|
||||
current->add(stub);
|
||||
|
@ -5937,7 +5961,8 @@ IonBuilder::newObjectTrySharedStub(bool* emitted)
|
|||
current->add(unbox);
|
||||
current->push(unbox);
|
||||
|
||||
trackOptimizationSuccess();
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationSuccess();
|
||||
*emitted = true;
|
||||
return Ok();
|
||||
}
|
||||
|
@ -5948,7 +5973,8 @@ IonBuilder::newObjectTryVM(bool* emitted, JSObject* templateObject)
|
|||
// Emit a VM call.
|
||||
MOZ_ASSERT(JSOp(*pc) == JSOP_NEWOBJECT || JSOp(*pc) == JSOP_NEWINIT);
|
||||
|
||||
trackOptimizationAttempt(TrackedStrategy::NewObject_Call);
|
||||
if (!IsCallOpcode(JSOp(*pc)))
|
||||
trackOptimizationAttempt(TrackedStrategy::NewObject_Call);
|
||||
|
||||
gc::InitialHeap heap = gc::DefaultHeap;
|
||||
MConstant* templateConst = MConstant::New(alloc(), NullValue());
|
||||
|
@ -5967,7 +5993,8 @@ IonBuilder::newObjectTryVM(bool* emitted, JSObject* templateObject)
|
|||
|
||||
MOZ_TRY(resumeAfter(ins));
|
||||
|
||||
trackOptimizationSuccess();
|
||||
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();
|
||||
return NonCacheIRStubMakesGCCalls(kind());
|
||||
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
|
||||
|
||||
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,12 +96,10 @@ public:
|
|||
void ReleaseKeyId(KeyId aKeyId);
|
||||
|
||||
// Decrypts buffer *in place*.
|
||||
GMPErr Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
|
||||
const CryptoMetaData& aMetadata);
|
||||
GMPErr Decrypt(std::vector<uint8_t>& aBuffer,
|
||||
const CryptoMetaData& aMetadata);
|
||||
|
||||
void Shutdown();
|
||||
cdm::Status Decrypt(uint8_t* aBuffer, uint32_t aBufferSize,
|
||||
const CryptoMetaData& aMetadata);
|
||||
cdm::Status Decrypt(std::vector<uint8_t>& aBuffer,
|
||||
const CryptoMetaData& aMetadata);
|
||||
|
||||
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));
|
||||
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()));
|
||||
}
|
||||
aRecordIterator->NextRecord();
|
||||
}
|
||||
}
|
||||
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()
|
||||
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)
|
||||
{
|
||||
if (sPersistentKeyState == UNINITIALIZED) {
|
||||
sPersistentKeyState = LOADING;
|
||||
if (GMP_FAILED(EnumRecordNames(&ReadAllRecordsFromIterator))) {
|
||||
sPersistentKeyState = LOADED;
|
||||
}
|
||||
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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
CK_LOGARRAY("ClearKeySessionManager::CreateSession initdata: ",
|
||||
aInitData,
|
||||
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 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());
|
||||
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;
|
||||
}
|
||||
|
||||
if (ClearKeyPersistence::DeferCreateSessionIfNotReady(this,
|
||||
aCreateSessionToken,
|
||||
aPromiseId,
|
||||
initDataType,
|
||||
aInitData,
|
||||
aInitDataSize,
|
||||
aSessionType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
string sessionId = ClearKeyPersistence::GetNewSessionId(aSessionType);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
function<void(const uint8_t*, uint32_t)> success =
|
||||
[self, sessionId, aPromiseId] (const uint8_t* data, uint32_t size)
|
||||
{
|
||||
self->PersistentSessionDataLoaded(aPromiseId,
|
||||
sessionId,
|
||||
data,
|
||||
size);
|
||||
};
|
||||
|
||||
// Callsback PersistentSessionDataLoaded with results...
|
||||
ClearKeyPersistence::LoadSessionData(this, sid, aPromiseId);
|
||||
function<void()> failure = [self, sessionId, aPromiseId] {
|
||||
if (!self->mHost) {
|
||||
return;
|
||||
}
|
||||
// As per the API described in ContentDecryptionModule_8
|
||||
self->mHost->OnResolveNewSessionPromise(aPromiseId, nullptr, 0);
|
||||
};
|
||||
|
||||
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);
|
||||
static const char* message = "Couldn't store cenc key init data";
|
||||
GMPTask* reject = WrapTask(mCallback,
|
||||
&GMPDecryptorCallback::RejectPromise,
|
||||
aPromiseId,
|
||||
kGMPInvalidStateError,
|
||||
message,
|
||||
strlen(message));
|
||||
StoreData(sessionId, keydata, resolve, reject);
|
||||
|
||||
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";
|
||||
self->mHost->OnRejectPromise(aPromiseId,
|
||||
Error::kInvalidStateError,
|
||||
0,
|
||||
message,
|
||||
strlen(message));
|
||||
};
|
||||
|
||||
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);
|
||||
static const char* message = "Could not remove session";
|
||||
GMPTask* reject = WrapTask(mCallback,
|
||||
&GMPDecryptorCallback::RejectPromise,
|
||||
aPromiseId,
|
||||
kGMPInvalidAccessError,
|
||||
message,
|
||||
strlen(message));
|
||||
StoreData(sessionId, emptyKeydata, resolve, reject);
|
||||
|
||||
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";
|
||||
self->mHost->OnRejectPromise(aPromiseId,
|
||||
Error::kInvalidAccessError,
|
||||
0,
|
||||
message,
|
||||
strlen(message));
|
||||
};
|
||||
|
||||
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,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize,
|
||||
GMPSessionType aSessionType) override;
|
||||
void CreateSession(uint32_t aPromiseId,
|
||||
cdm::InitDataType aInitDataType,
|
||||
const uint8_t* aInitData,
|
||||
uint32_t aInitDataSize,
|
||||
cdm::SessionType aSessionType);
|
||||
|
||||
virtual void LoadSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength) override;
|
||||
void LoadSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength);
|
||||
|
||||
virtual void UpdateSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength,
|
||||
const uint8_t* aResponse,
|
||||
uint32_t aResponseSize) override;
|
||||
void UpdateSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength,
|
||||
const uint8_t* aResponse,
|
||||
uint32_t aResponseSize);
|
||||
|
||||
virtual void CloseSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength) override;
|
||||
void CloseSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength);
|
||||
|
||||
virtual void RemoveSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength) override;
|
||||
void RemoveSession(uint32_t aPromiseId,
|
||||
const char* aSessionId,
|
||||
uint32_t aSessionIdLength);
|
||||
|
||||
virtual void SetServerCertificate(uint32_t aPromiseId,
|
||||
const uint8_t* aServerCert,
|
||||
uint32_t aServerCertSize) override;
|
||||
void SetServerCertificate(uint32_t aPromiseId,
|
||||
const uint8_t* aServerCert,
|
||||
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,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize) override {
|
||||
assert(false); // Should not reach here.
|
||||
void OnReadComplete(Status aStatus,
|
||||
const uint8_t* aData,
|
||||
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,
|
||||
const uint8_t* aData,
|
||||
uint32_t aDataSize) override {
|
||||
void OnReadComplete(Status aStatus,
|
||||
const uint8_t* aData,
|
||||
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,15 +551,16 @@ 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";
|
||||
default: {
|
||||
assert(false); // Should not reach here.
|
||||
return "invalid";
|
||||
}
|
||||
case SessionType::kTemporary: return "temporary";
|
||||
case SessionType::kPersistentLicense: return "persistent-license";
|
||||
default: {
|
||||
// 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",
|
||||
hr,
|
||||
((hr == MF_E_NOTACCEPTING) ? " (MF_E_NOTACCEPTING)" : ""));
|
||||
return;
|
||||
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)" : ""));
|
||||
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;
|
||||
}
|
||||
|
||||
CK_LOGD("VideoDecoder::OutputFrame Decoder output ret=0x%x", hr);
|
||||
|
||||
mOutputQueue.push(output);
|
||||
CK_LOGD("VideoDecoder::OutputFrame: Queue size: %u", mOutputQueue.size());
|
||||
}
|
||||
|
||||
auto vf = static_cast<GMPVideoi420Frame*>(f);
|
||||
// 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;
|
||||
}
|
||||
|
||||
hr = SampleToVideoFrame(aSample, aWidth, aHeight, aStride, vf);
|
||||
ENSURE(SUCCEEDED(hr), /*void*/);
|
||||
// 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;
|
||||
}
|
||||
|
||||
mCallback->Decoded(vf);
|
||||
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,13 +603,18 @@ 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;
|
||||
return success ? NS_OK : NS_ERROR_FAILURE;
|
||||
// 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;
|
||||
}
|
||||
|
||||
nsString version;
|
||||
|
@ -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()
|
||||
|
|
Загрузка…
Ссылка в новой задаче